<?php

namespace App\Modules\Stock;

use App\Contracts\StockProvider;
use App\Events\NewVehicleImported;
use App\Events\StockSyncFinished;
use App\Events\VehicleUpdatedFromImport;
use App\Facades\Settings;
use App\TaxonomyMap;
use App\Traits\MapsTaxonomies;
use App\Traits\StockSyncTraits;
use App\VehicleType;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Mtc\ContentManager\Facades\Media as MediaFacade;
use Mtc\MercuryDataModels\Media;
use Mtc\MercuryDataModels\Vehicle;
use SimpleXMLElement;
use Symfony\Component\Finder\SplFileInfo;
use ZipArchive;

class Modix implements StockProvider
{
    use StockSyncTraits;
    use MapsTaxonomies;

    private Collection $dealerships;

    public function enabled(): bool
    {
        return Settings::get('stock-modix-enabled') ?? false;
    }

    protected function getProviderName(): string
    {
        return 'modix';
    }

    public function name(): string
    {
        return __('labels.stock_providers.modix');
    }

    /**
     * Fields to add to dealership management
     *
     * @return array[]
     */
    public function dealershipAdditionalDataFields()
    {
        return [
            'modix-location-id' => [
                'type' => 'text',
                'label' => __('labels.stock_providers.modix_dealer_field'),
            ],
        ];
    }

    public function runScheduledImport(bool $fullSync = true): void
    {
        $this->markVehiclesToBeSynced();
        $this->getAllStockLocations('modix-location-id');
        $stock_path = $this->retrieveAndExtractStockData();
        $this->processImageFiles($stock_path);
        $this->processStockFile($stock_path);
        $this->removeLocalFiles($stock_path);
        $this->removeNotSynced();

        Event::dispatch(new StockSyncFinished($this->getProviderName()));
    }

    private function processStockFile(string $stock_path): void
    {
        /** @var SplFileInfo $stock_file */
        $stock_file = collect(File::files($stock_path))
            ->filter(fn($filename) => pathinfo($filename, PATHINFO_EXTENSION) === 'xml')
            ->first();

        $content = File::get($stock_file->getPathname());
        $xml_element = new SimpleXMLElement($content);

        collect($xml_element->xpath('//vehicles/vehicle'))
            ->map(fn($vehicle) => $this->mapVehicleData($vehicle))
            ->each(fn(array $vehicle_data) => $this->syncVehicle($vehicle_data));
    }

    private function processImageFiles(string $stock_path): void
    {
        collect(File::files($stock_path))
            ->filter(fn($filename) => pathinfo($filename, PATHINFO_EXTENSION) === 'jpg')
            ->chunk(50)
            ->each(fn(Collection $chunk) => $this->importImageBatch($chunk));
    }

    private function mapVehicleData(SimpleXMLElement $vehicle): array
    {
        $make_id = $this->getMappedTaxonomy(
            TaxonomyMap::MAKE,
            (string)$vehicle->mainData->manufacturer,
            (array)$vehicle
        );

        $vehicle_data = array_filter([
            'uuid' => (string)$vehicle['cipher'],
            'registration_number' => strtoupper((string)$vehicle['internalDescription']),
            'derivative' => (string)$vehicle->mainData->submodel,
            'type' => match ((string)$vehicle->mainData->category) {
                'Passenger car' => VehicleType::CAR->value,
                default => VehicleType::CAR->value,
            },
            'is_new' => match ((string)$vehicle->mainData->usageCategory) {
                'Used vehicle' => 0,
                default => 1,
            },
            'dealership_id' => $this->dealershipId((string)$vehicle['modkey']),
            'make_id' => $make_id,
            'model_id' => $this->getMappedTaxonomy(
                TaxonomyMap::MODEL,
                (string)$vehicle->mainData->modelText,
                (array)$vehicle
            ),
            'fuel_type_id' => $this->getMappedTaxonomy(
                TaxonomyMap::FUEL_TYPE,
                (string)$vehicle->mainData->fuel,
                (array)$vehicle
            ),
            'transmission_id' => $this->getMappedTaxonomy(
                TaxonomyMap::TRANSMISSION,
                (string)$vehicle->mainData->transmission,
                (array)$vehicle
            ),
            'body_style_id' => $this->getMappedTaxonomy(
                TaxonomyMap::BODY_STYLE,
                (string)$vehicle->mainData->bodyStyle,
                (array)$vehicle
            ),
            'bhp' => (int)$vehicle->mainData->enginePower,
            'odometer_mi' => (int)$vehicle->mainData->mileage,
            'engine_cc' => (int)$vehicle->mainData->cylinderCapacity,
            'is_published' => (bool)$vehicle->mainData->isOnline,
            'vin' => (string)$vehicle->identification->vin,
            'colour' => (string)$vehicle->colors->bodyColors->bodyColor->description
                ?: (string)$vehicle->colors->bodyColors->bodyColor->name,
            'description' => (string)$vehicle->description,
            'price' => (float)$vehicle->prices->price->grossPrice,
            'was_recently_synced' => true,
            'stock_provider' => $this->getProviderName(),
            'first_registration_date' => (string)$vehicle->dates->date[1],
        ]);

        $status = (int)$vehicle->mainData->vehicleStatus;
        if ($status === 120) {
            $vehicle['is_reserved'] = true;
        }
        if ($status === 130) {
            $vehicle['is_sold'] = true;
        }
        if ($status === 100) {
            $vehicle['is_reserved'] = false;
            $vehicle['is_sold'] = false;
        }

        $tech_data = [];
        foreach ($vehicle->options->option as $option) {
            $tech_data[(string)$option->description] = (string)$option->name ?: (string)$option->value;
        }
        if (!empty($tech_data['Doors'])) {
            $vehicle_data['door_count'] = (int)$tech_data['Doors'];
        }
        if (!empty($tech_data['Seats'])) {
            $vehicle_data['seats'] = (int)$tech_data['Seats'];
        }
        if (!empty($tech_data['WLTP consumption combined'])) {
            $vehicle_data['mpg'] = (int)$tech_data['WLTP consumption combined'];
        }
        if (!empty($tech_data['Standard equipment'])) {
            $vehicle_data['equipment'] = explode(', ', $tech_data['Standard equipment']);
        }
        if (!empty($tech_data['Model year'])) {
            $vehicle_data['model_year'] = (int)$tech_data['Model year'];
        }
        if (!empty($tech_data['Guarantee program'])) {
            $vehicle_data['attention_grabber'] = (string)$tech_data['Guarantee program'];
        }
        if (!empty($tech_data['Cap ID'])) {
            $vehicle_data['cap_id'] = (string)$tech_data['Cap ID'];
        }
        $media = [];
        foreach ($vehicle->media->images->image as $image) {
            $media[] = (string)$image['name'];
        }
        $vehicle_data['images'] = $media;

        return $vehicle_data;
    }

    private function syncVehicle(array $vehicle_data): void
    {
        $vehicle = Vehicle::query()->firstOrNew([
            'uuid' => $vehicle_data['uuid'],
            'stock_provider' => $this->getProviderName(),
        ]);

        $vehicle->fill($vehicle_data)->save();

        if ($vehicle->wasChanged('price')) {
            $this->priceChangeEvents($vehicle, $this->getProviderName());
        }

        if (!empty($vehicle_data['equipment']) && $vehicle->equipment()->count() == 0) {
            $this->syncEquipment($vehicle, $vehicle_data['equipment']);
        }

        if (!empty($vehicle_data['images']) && $vehicle->mediaUses()->count() == 0) {
            $this->syncImages($vehicle, $vehicle_data['images']);
        }

        $this->storeUnmappedTaxonomy($vehicle);
        if ($vehicle->wasRecentlyCreated) {
            Event::dispatch(new NewVehicleImported($vehicle, $vehicle_data, $this->getProviderName()));
        } else {
            Event::dispatch(new VehicleUpdatedFromImport($vehicle, $vehicle_data, $this->getProviderName()));
        }
    }

    private function syncEquipment(Vehicle $vehicle, array $equipment): void
    {
        collect($equipment)
            ->filter()
            ->each(fn($entry) => $vehicle->equipment()->updateOrCreate(['description' => $entry]));
    }

    private function syncImages(Vehicle $vehicle, array $images): void
    {
        $media = Media::query()
            ->whereIn('source_filename', $images)
            ->where('image_provider', $this->getProviderName())
            ->pluck('id')
            ->toArray();
        MediaFacade::setUsesForModel($media, $vehicle, ['primary' => true]);
    }

    private function retrieveAndExtractStockData(): string
    {
        $disk = Storage::build([
            'driver' => 'ftp',
            'host' => Settings::get('stock-modix-ftp-host'),
            'username' => Settings::get('stock-modix-ftp-username'),
            'password' => Settings::get('stock-modix-ftp-password'),
            'ssl' => null,
        ]);

        if ($disk->exists(Settings::get('stock-modix-archive-path'))) {
            Storage::disk('local')->put(
                Settings::get('stock-modix-archive-path'),
                $disk->get(Settings::get('stock-modix-archive-path'))
            );
        }

        $archiveFolder = storage_path(uniqid('zip_extract_', true));
        $this->extractZipArchive(
            Storage::disk('local')->path(Settings::get('stock-modix-archive-path')),
            $archiveFolder
        );
        return $archiveFolder;
    }

    private function extractZipArchive(string $archivePath, string $extractPath): void
    {
        $zip = new ZipArchive();

        if ($zip->open($archivePath) === true) {
            $zip->extractTo($extractPath);
            $zip->close();
        } else {
            throw new \Exception("Unable to extract the ZIP file");
        }
    }

    private function importImageBatch(Collection $chunk): void
    {
        $file_names = $chunk->map(fn(SplFileInfo $file) => $file->getFilename());
        $existing = Media::query()
            ->where('media.image_provider', $this->getProviderName())
            ->whereIn('media.source_filename', $file_names)
            ->pluck('media.source_filename');

        $chunk->reject(fn(SplFileInfo $file) => $existing->search($file->getFilename()) !== false)
            ->each(fn(SplFileInfo $file) => MediaFacade::importImageFromFile($file, $this->getProviderName()));
    }

    private function removeLocalFiles(string $stock_path): void
    {
        File::deleteDirectory($stock_path);
    }
}
