<?php

namespace App\Imports;

use App\AIContentRepository;
use App\Events\AutoTraderIdAssignedToVehicle;
use App\Events\NewVehicleImported;
use App\Events\VehicleUpdatedFromImport;
use App\Facades\Settings;
use App\Jobs\ImportImagesFromUrlList;
use App\Jobs\ImportImageUrlList;
use App\Master\Models\VehicleModel;
use App\Rules\ValidVehicleRegistrationNumber;
use App\TaxonomyMap;
use App\Traits\EnsuresVehicleAttribute;
use App\Traits\ImportChecksConditions;
use App\Traits\MapsTaxonomies;
use App\VehicleType;
use Carbon\Carbon;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Str;
use Mtc\AutoTraderStock\Contracts\AutoTraderApiImportContract;
use Mtc\MercuryDataModels\BodyStyleType;
use Mtc\MercuryDataModels\FuelType;
use Mtc\MercuryDataModels\TransmissionType;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleMake;

class AutoTraderApiToVehicleImport extends AutoTraderApiImportContract
{
    use DispatchesJobs;
    use MapsTaxonomies;
    use ImportChecksConditions;
    use EnsuresVehicleAttribute;

    private $dealershipId;
    private array $make = [];
    private array $model = [];
    private array $transmission = [];
    private array $body_style = [];
    private array $fuel_type = [];

    private array $vehicleAttributes;

    private bool $mappingLoaded = false;


    public function setDealershipId($dealership): void
    {
        $this->dealershipId = $dealership;
    }

    public function exists(array $vehicle_data, null|int $vehicleId = null): bool
    {
        if ($vehicleId) {
            return Vehicle::query()
                ->where('id', $vehicleId)
                ->withTrashed()
                ->exists();
        }

        $exists = Vehicle::query()
            ->withTrashed()
            ->where('stock_provider', 'auto-trader')
            ->where(function ($query) use ($vehicle_data) {
                $query->where('uuid', $vehicle_data['metadata']['stockId'])
                    ->orWhere('auto_trader_id', $vehicle_data['metadata']['stockId']);
            })
            ->orWhere(function ($query) use ($vehicle_data) {
                $query->where('registration_number', $vehicle_data['vehicle']['registration'] ?? null);
            })
            ->exists();

        if (!$exists && !empty($vehicle_data['vehicle']['vin'])) {
            $exists = Vehicle::query()
                ->where('vin', $vehicle_data['vehicle']['vin'])
                ->exists();
        }
        return $exists;
    }

    public function update(array $vehicle_data, null|int $vehicleId = null, bool $importImages = true): void
    {
        /** @var Vehicle $vehicle */
        if ($vehicleId) {
            $vehicle = Vehicle::query()
                ->where('id', $vehicleId)
                ->withTrashed()
                ->latest()
                ->first();
        } else {
            $vehicle = Vehicle::query()
                ->where('stock_provider', 'auto-trader')
                ->where(function ($query) use ($vehicle_data) {
                    $query->where('uuid', $vehicle_data['metadata']['stockId'])
                        ->orWhere('auto_trader_id', $vehicle_data['metadata']['stockId']);
                })
                ->withTrashed()
                ->latest()
                ->first();
        }

        if (!$vehicle && !empty($vehicle_data['vehicle']['vin'])) {
            $vehicle = Vehicle::query()
                ->where('vin', $vehicle_data['vehicle']['vin'])
                ->latest()
                ->first();

            if ($vehicle) {
                $vehicle->auto_trader_id = $vehicle_data['metadata']['stockId'];
                if ($vehicle->stock_provider === 'auto-trader') {
                    $vehicle->uuid = $vehicle_data['metadata']['stockId'];
                }
            }
        }

        if (!$vehicle) {
            $this->add($vehicle_data);
            return;
        }

        if ($vehicle->trashed()) {
            if ($this->isVehicleDeleted($vehicle_data)) {
                return;
            } else {
                $vehicle->restore();
            }
        }

        $vehicle->fill($this->mapDataToColumns($vehicle_data, $vehicle));

        $vehicle->is_published = $this->shouldBePublished(
            fn() => $vehicle->is_published,
            'auto-trader',
            $vehicle
        );

        $vehicle->save();
        $this->syncAdditionalData($vehicle, $vehicle_data, $importImages);

        Event::dispatch(new VehicleUpdatedFromImport($vehicle, $vehicle_data, $this->getProviderName()));
        if ($vehicle->wasChanged('auto_trader_id')) {
            Event::dispatch(new AutoTraderIdAssignedToVehicle($vehicle));
        }
    }

    public function add(array $vehicle_data, bool $importImages = true)
    {
        if ($this->shouldSkipSync($vehicle_data)) {
            return;
        }

        /** @var Vehicle $vehicle */
        $vehicle = new Vehicle();
        $vehicle->fill($this->mapDataToColumns($vehicle_data, $vehicle, false));

        $vehicle->is_published = $this->shouldBePublished(
            fn() => $vehicle->is_published,
            'auto-trader',
            $vehicle,
        );
        $vehicle->save();

        $this->syncAdditionalData($vehicle, $vehicle_data, $importImages);

        Event::dispatch(new NewVehicleImported($vehicle, $vehicle_data, $this->getProviderName()));
        Event::dispatch(new AutoTraderIdAssignedToVehicle($vehicle));
    }

    private function syncAdditionalData(Vehicle $vehicle, array $vehicle_data, bool $importImages): void
    {
        $this->syncAutoTraderData($vehicle, $vehicle_data);

        if ($this->shouldSyncImages($vehicle, $vehicle_data['media']['images'], $importImages)) {
            $this->importImages($vehicle, $vehicle_data['media']['images']);
        }

        if (!empty($vehicle_data['highlights']) || !empty($vehicle_data['features'])) {
            // Merge in highlights
            $features = collect($vehicle_data['highlights'] ?? [])
                ->map(fn($highlight) => [
                    'type' => 'Highlight',
                    'name' => $highlight['name']
                ])
                ->merge($vehicle_data['features'])
                ->toArray();
            $this->importFeatures($vehicle, $features);
        }

        if (!empty($vehicle_data['enriched_data']['features'])) {
            $this->importEquipment($vehicle, $vehicle_data['enriched_data']['features']);
        }

        $this->storeUnmappedTaxonomy($vehicle);
        $this->updateVehicleAttributes($vehicle_data, $vehicle);
    }

    public function removeOld(Collection $vehicles): void
    {
        // Not used here
    }

    protected function mapDataToColumns($vehicle_data, Vehicle $vehicle, bool $existing_vehicle = true): array
    {
        if (!empty($vehicle_data['vehicle']['firstRegistrationDate'])) {
            $date_of_registration = Carbon::createFromFormat(
                'Y-m-d',
                $vehicle_data['vehicle']['firstRegistrationDate']
            );
        } else {
            $date_of_registration = null;
        }

        $price = $vehicle_data['adverts']['retailAdverts']['suppliedPrice']['amountGBP'] ?? null;
        $admin_fee = $vehicle_data['adverts']['retailAdverts']['admin_fee']['amountGBP'] ?? null;
        $rrp_price = $vehicle_data['adverts']['retailAdverts']['manufacturerRRP']['amountGBP'] ?? null;
        $make_id = $this->getMappedTaxonomy(TaxonomyMap::MAKE, $vehicle_data['vehicle']['make'], $vehicle_data);

        $data = array_filter([
            'was_recently_synced' => true,
            'type' => $this->getVehicleType($vehicle_data),
            'stock_provider' => $vehicle->stock_provider ?? $this->getProviderName(),
            'uuid' => $vehicle->uuid ?? $vehicle_data['metadata']['stockId'],
            'auto_trader_id' => $vehicle_data['metadata']['stockId'],
            'title' => $vehicle_data['vehicle']['make'] . ' ' . $vehicle_data['vehicle']['model'],
            'registration_number' => $vehicle_data['vehicle']['registration'] ?? null,
            'trim' => $vehicle_data['vehicle']['trim'] ?? null,
            'first_registration_date' => $date_of_registration,
            'make_id' => $make_id,
            'model_id' => $this->getMappedTaxonomy(
                TaxonomyMap::MODEL,
                $vehicle_data['vehicle']['standard']['model'] ?? $vehicle_data['vehicle']['model'],
                $vehicle_data,
                $make_id
            ),
            'derivative' => $vehicle_data['vehicle']['derivative'] ?? null,
            'colour' => $vehicle_data['vehicle']['standard']['colour'] ?? $vehicle_data['vehicle']['colour'] ?? null,
            'fuel_type_id' => $this->getMappedTaxonomy(
                TaxonomyMap::FUEL_TYPE,
                $vehicle_data['vehicle']['standard']['fuelType'] ?? $vehicle_data['vehicle']['fuelType'],
                $vehicle_data
            ),
            'body_style_id' => $this->getMappedTaxonomy(
                TaxonomyMap::BODY_STYLE,
                $vehicle_data['vehicle']['standard']['bodyType'] ?? $vehicle_data['vehicle']['bodyType'],
                $vehicle_data
            ),
            'engine_size_cc' => $vehicle_data['vehicle']['engineCapacityCC'] ?? null,
            'dealership_id' => $this->dealershipId,
            'rrp_price' => $rrp_price,
            'price' => $price,
            'admin_fee' => $admin_fee,
            'is_new' => $vehicle_data['vehicle']['ownershipCondition'] === 'New',
            'transmission_id' => $this->getMappedTaxonomy(
                TaxonomyMap::TRANSMISSION,
                $vehicle_data['vehicle']['standard']['transmissionType']
                ?? $vehicle_data['vehicle']['transmissionType'],
                $vehicle_data
            ),
            'vehicle_length' => $vehicle_data['vehicle']['lengthMM'] ?? null,
            'manufacture_year' => $vehicle_data['vehicle']['yearOfManufacture'] ?? null,
            'vin' => $vehicle_data['vehicle']['vin'] ?? null,
            'odometer_mi' => $vehicle_data['vehicle']['odometerReadingMiles'] ?? null,
            'odometer_km' => !empty($vehicle_data['vehicle']['odometerReadingMiles'])
                ? (new Vehicle())->milesToKm($vehicle_data['vehicle']['odometerReadingMiles'])
                : null,
            'co2' => $vehicle_data['vehicle']['co2EmissionGPKM'] ?? null,
            'seats' => $vehicle_data['vehicle']['seats'] ?? null,
            'bhp' => $vehicle_data['vehicle']['enginePowerBHP'] ?? null,
            'battery_capacity_kwh' => $vehicle_data['vehicle']['batteryCapacityKWH'] ?? null,
            'battery_range' => $vehicle_data['vehicle']['batteryRangeMiles'] ?? null,
            'door_count' => $vehicle_data['vehicle']['doors'] ?? null,
            'previous_owner_count' => $vehicle_data['vehicle']['previousOwners'] ?? null,
            'is_vat_applicable' => $this->isVatApplicable($vehicle_data),
            'attention_grabber' => $vehicle_data['adverts']['retailAdverts']['attentionGrabber'] ?? null,
            'data' => array_merge($vehicle->data ?? [], $this->getAdvertFields($vehicle_data))
        ], fn($value) => !is_null($value));

        $data['deleted_at'] = $this->isVehicleDeleted($vehicle_data) ? Carbon::now() : null;

        // If AI generates descriptions we do not want clear generated data
        if ($this->usesAIDescriptions() === false) {
            $data['description'] = trim(($vehicle_data['adverts']['retailAdverts']['description'] ?? '') . ' '
                . ($vehicle_data['adverts']['retailAdverts']['description2'] ?? ''));
        }

        switch ($vehicle_data['metadata']['lifecycleState'] ?? null) {
            case 'SALE_IN_PROGRESS':
                $data['is_reserved'] = true;
                if (empty($vehicle->reserved_at)) {
                    $data['reserved_at'] = Carbon::now();
                }
                break;
            case 'SOLD':
                $data['is_sold'] = true;
                if (empty($vehicle->sold_at)) {
                    $data['sold_at'] = $vehicle_data['adverts']['soldDate'] ?? Carbon::now();
                }
                break;
            case 'FORECOURT':
                $data['is_reserved'] = $data['is_sold'] = false;
                break;
        }

        if (Settings::get('auto-trader-sync-is-published')) {
            $advert = $vehicle_data['adverts']['retailAdverts']['advertiserAdvert'];
            $data['is_published'] = $advert['status'] === 'PUBLISHED';
        } elseif (!$existing_vehicle) {
            $data['is_published'] = true;
        }

        if (Settings::get('stock-auto-trader-sync-video')) {
            $data['main_video_url'] = $vehicle_data['media']['video']['href'] ?? '';
        }

        if (!empty($data['registration_number'])) {
            $registrationNumberValidator = new ValidVehicleRegistrationNumber();
            $data['personalized_number_plate'] = !$registrationNumberValidator
                ->passes('registration_number', $data['registration_number']);
        }

        return $data;
    }

    protected function getAdvertFields($vehicle_data): array
    {
        return array_filter([
            'autotraderAdvert' => $vehicle_data['adverts']['retailAdverts']['autotraderAdvert'] ?? null,
            'profileAdvert' => $vehicle_data['adverts']['retailAdverts']['profileAdvert'] ?? null
        ]);
    }

    protected function syncAutoTraderData(Vehicle $vehicle, array $data): void
    {
        $at_data = array_filter([
            'valuation' => $data['vehicle']['valuations']['marketAverage']['retail']['amountGBP'] ?? null,
            'price_point' => $data['adverts']['retailAdverts']['priceIndicatorRating'] ?? null,
            'price_position' => $data['vehicle']['vehicleMetrics']['local']['retail']['rating']['value'] ?? null,
            'search_results_7d' => $data['responseMetrics']['lastWeek']['searchViews'] ?? null,
            'ad_views' => $data['responseMetrics']['lastWeek']['advertViews'] ?? null,
            'advert_publish_state' => $data['adverts']['retailAdverts']['autotraderAdvert']['status'] ?? null,
            'profile_publish_state' => $data['adverts']['retailAdverts']['profileAdvert']['status'] ?? null,
            'lifecycle_state' => $data['metadata']['lifecycleState'] ?? null,
        ]);

        $at_data['at_export_error'] = null;
        if (!empty($at_data)) {
            $vehicle->autoTraderData()->updateOrCreate(['vehicle_id' => $vehicle->id], $at_data);
        }
    }

    protected function importImages(Vehicle $vehicle, $image_list): void
    {
        $imageList = collect($image_list)
            ->filter(fn($image) => isset($image['href']))
            ->mapWithKeys(fn($image) => [
                str_replace('{resize}', '', $image['href']) => [
                    'description' => $image['classificationTags'][0]['label'] ?? null,
                    'caption' => $image['classificationTags'][0]['category'] ?? null,
                    'interior' => ($image['classificationTags'][0]['category'] ?? '') === 'Interior',
                    'exterior' => ($image['classificationTags'][0]['category'] ?? '') === 'Exterior',
                    'auto_trader_id' => $image['imageId'] ?? null
                ]
            ]);

        $importImagesFromUrlListJob = (new ImportImageUrlList(
            $imageList->keys(),
            $vehicle,
            [
                'imageProvider' => 'auto-trader',
                'additionalData' => $imageList->toArray()
            ]
        ));

        $this->dispatch($importImagesFromUrlListJob);
    }

    public function importFeatures(Vehicle $vehicle, array $feature_list): void
    {
        $existing = $vehicle->features()->get();
        $features = collect($feature_list)->filter(fn($feature) => $this->filterApplicableFeatures($feature));
        $to_add = [];

        foreach ($features as $feature) {
            $matched = $existing->where('category', $feature['type'])
                ->where('name', $feature['name']);
            if ($matched->isNotEmpty()) {
                $existing->forget($matched->keys());
            } else {
                $to_add[] = $feature;
            }
        }

        if ($existing->count()) {
            $vehicle->features()
                ->whereIn('id', $existing->pluck('id'))
                ->delete();
        }
        // loop through and remove ones no longer in feature list
        // then loop through to fetch ones that need to be added
        collect($to_add)
            ->each(function ($feature) use ($vehicle) {
                try {
                    $vehicle->features()
                        ->updateOrCreate([
                            'vehicle_type' => 'vehicle',
                            'slug' => Str::slug($feature['name']),
                            'category' => $feature['type'],
                        ], [
                            'name' => $feature['name'],
                        ]);
                } catch (\Exception $exception) {
                    // Handle unique constraint error
                }
            });
    }


    public function importEquipment(Vehicle $vehicle, array $equipment_list): void
    {
        $existing = $vehicle->equipment()->get();
        $equipment = collect($equipment_list)->filter(fn($feature) => $this->filterApplicableEquipment($feature));
        $to_add = [];

        foreach ($equipment as $entry) {
            $matched = $existing->where('category', $entry['type'])
                ->where('description', $entry['name'])
                ->where('type', $entry['type']);
            if ($matched->isNotEmpty()) {
                $existing->forget($matched->keys());
            } else {
                $to_add[] = $entry;
            }
        }

        if ($existing->count()) {
            $vehicle->equipment()
                ->whereIn('id', $existing->pluck('id'))
                ->delete();
        }

        collect($to_add)
            ->each(function ($entry) use ($vehicle) {
                try {
                    $vehicle->equipment()
                        ->updateOrCreate([
                            'description' => $entry['name'],
                            'type' => $entry['type'],
                            'category' => $entry['category'],
                        ], [
                            'price' => $entry['basicPrice'],
                            'vat_amount' => $entry['vatPrice'],
                        ]);
                } catch (\Exception $exception) {
                    // Handle unique constraint error
                }
            });
    }

    public function isVehicleDeleted($vehicle_data): bool
    {
        if (
            (Settings::get('auto-trader-upsert-deleted-mark-sold', false) ?? false)
            && $vehicle_data['metadata']['lifecycleState'] === 'SOLD'
        ) {
            return true;
        }

        $deleted_states = [
            'DELETED',
            'WASTEBIN',
        ];

        return in_array($vehicle_data['metadata']['lifecycleState'], $deleted_states);
    }

    public function getProviderName(): string
    {
        return 'auto-trader';
    }

    protected function getDetailsForTaxonomyMap(array $record)
    {
        return [
            'tenant_id' => tenant('id'),
            'registration_number' => $record['vehicle']['registration'],
            'make' => $record['vehicle']['make'],
            'model' => $record['vehicle']['model'],
        ];
    }

    private function shouldSkipSync(array $vehicle_data): bool
    {
        return (
                Settings::get('stock-auto-trader-sync-new-vehicles') !== true
                && $vehicle_data['vehicle']['ownershipCondition'] === 'New'
            ) || (
                Settings::get('stock-auto-trader-sync-used-vehicles') !== true
                && $vehicle_data['vehicle']['ownershipCondition'] === 'Used'
            );
    }

    private function filterApplicableEquipment($feature): bool
    {
        return match ($feature['type'] ?? '') {
            'Standard' => (bool)Settings::get('autotrader-stock-sync-standard-features'),
            'Optional' => (bool)Settings::get('autotrader-stock-sync-optional-features'),
            default => false,
        };
    }

    private function filterApplicableFeatures($feature): bool
    {
        return match ($feature['type'] ?? '') {
            'Standard' => (bool)Settings::get('autotrader-stock-sync-standard-key-features'),
            'Optional' => (bool)Settings::get('autotrader-stock-sync-optional-key-features'),
            'Highlight' => true,
            default => false,
        };
    }

    private function getVehicleType($vehicle_data): string
    {
        return match ($vehicle_data['vehicle']['vehicleType']) {
            'Bike' => VehicleType::MOTORCYCLE->value,
            'Van' => VehicleType::LCV->value,
            default => VehicleType::CAR->value,
        };
    }

    private function eagerLoadTaxonomyMapping()
    {
        $this->body_style = BodyStyleType::query()->pluck('id', 'name')->toArray();
        $this->transmission = TransmissionType::query()->pluck('id', 'name')->toArray();
        $this->fuel_type = FuelType::query()->pluck('id', 'name')->toArray();
        $this->make = VehicleMake::query()->pluck('id', 'name')->toArray();
        $this->model = VehicleModel::query()->get()
            ->groupBy('make_id')
            ->map(fn($group) => $group->keyBy('name')->map(fn($entry) => $entry->id))
            ->toArray();
        $this->mappingLoaded = true;
    }

    private function shouldSyncImages(Vehicle $vehicle, array $image_list, bool $importImages): bool
    {
        $images = collect($image_list)
            ->filter(fn($image) => isset($image['href']))
            ->map(fn($image) => str_replace('{resize}', '', $image['href']))
            ->toArray();

        return Settings::get('stock-auto-trader-sync-images')
            && $importImages
            && empty($vehicle->deleted_at)
            && $this->imageChecksumMismatch($images, $vehicle);
    }

    private function usesAIDescriptions(): bool
    {
        return (new AIContentRepository())->hasEnabledIntegration();
    }

    private function updateVehicleAttributes(array $vehicle_data, Vehicle $vehicle): void
    {
        $vatStatus = $vehicle_data['adverts']['retailAdverts']['vatStatus'] ?? null;

        $this->vehicleAttributes = [
            'vat_status' => $this->getVehicleAttribute('VAT Status', 'text')->toArray(),
        ];

        $this->syncVehicleAttribute($vehicle, 'vat_status', $vatStatus);
    }

    private function isVatApplicable(array $vehicle_data): ?bool
    {
        if (Settings::get('auto-trader-stock-vat-calculation') === 'vat-status') {
            return ($vehicle_data['adverts']['retailAdverts']['vatStatus'] ?? 'Inc VAT') === 'Inc VAT';
        }

        $vat_status = $vehicle_data['adverts']['retailAdverts']['vatStatus'] ?? null;
        $vehicle_type = $this->getVehicleType($vehicle_data);

        if ($vat_status === false) {
            return false;
        }

        return match ($vehicle_type) {
            VehicleType::CAR->value => $vat_status === 'Inc VAT',
            VehicleType::LCV->value => $vat_status === 'Ex VAT',
            default => false,
        };
    }
}
