<?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\ImportImageUrlList;
use App\Master\Models\VehicleModel;
use App\Rules\ValidVehicleRegistrationNumber;
use App\Services\AutoTraderDealershipResolver;
use App\TaxonomyMap;
use App\Traits\EnsuresVehicleAttribute;
use App\Traits\ImportChecksConditions;
use App\Traits\MapsTaxonomies;
use Mtc\MercuryDataModels\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\ContentManager\Facades\Media;
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 ?AutoTraderDealershipResolver $dealershipResolver = null;
    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;
    }

    /**
     * When make-based assignment is enabled, resolve the dealership from the vehicle make
     * within the shared AutoTrader location group (the advertiser id carried on the vehicle
     * payload). Returns null when disabled or unresolved, so callers fall back to the existing
     * pinned-dealership behaviour.
     */
    private function resolveDealershipId(?int $makeId, array $vehicleData): ?int
    {
        if (!Settings::get('autotrader-make-based-assignment')) {
            return null;
        }

        $advertiserId = $vehicleData['advertiser']['advertiserId'] ?? null;
        if ($advertiserId === null) {
            return null;
        }

        $this->dealershipResolver ??= app(AutoTraderDealershipResolver::class);

        return $this->dealershipResolver->resolve($advertiserId, $makeId)?->id;
    }

    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 && !empty($vehicle_data['vehicle']['registration'])) {
            $vehicle = Vehicle::query()
                ->where('registration_number', $vehicle_data['vehicle']['registration'])
                ->withTrashed()
                ->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 ($this->isSoldAndStale($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']);
        } else {
            // Images are unchanged so the import job above did not run and will not move the
            // primary itself. Reconcile the order and primary against the live feed so existing
            // stock whose images have not changed still has a drifted primary repaired on a full
            // sync. Changed-image vehicles are covered at the end of the queued ImportImageUrlList
            // job instead.
            $this->reconcilePrimaryFromFeed($vehicle, $vehicle_data['media']['images'] ?? []);
        }

        // Repair vehicles that have media but no primary use. This runs regardless of the image
        // checksum so a full stock sync fixes existing stock whose images have not changed (and so
        // never dispatch the image job above). Changed-image vehicles are also covered at the end
        // of the queued ImportImageUrlList job.
        Media::ensurePrimaryUse($vehicle);

        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'])) {
            $equipment_list = self::mergeFittedFeaturesWithTaxonomyPrices(
                $vehicle_data['features'] ?? [],
                $vehicle_data['enriched_data']['features']
            );
            $this->importEquipment($vehicle, $equipment_list);
        }

        $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;
        }

        if (!empty($vehicle_data['metadata']['dateOnForecourt'])) {
            $date_on_forecourt = Carbon::createFromFormat(
                'Y-m-d',
                $vehicle_data['metadata']['dateOnForecourt']
            )->startOfDay();
        } else {
            $date_on_forecourt = 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,
            // Date the advert went on the AutoTrader forecourt; never overwrite an existing value
            'stock_arrival_date' => $vehicle->stock_arrival_date ?? $date_on_forecourt,
            '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->resolveDealershipId($make_id, $vehicle_data)
                ?? $this->dealershipId ?? $vehicle->dealership_id,
            '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,
            ...$this->mapChargeTimes($vehicle_data['chargeTimes'] ?? []),
            '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']['yesterday']['advertViews'] ?? null,
            'ad_views_7d' => $data['responseMetrics']['lastWeek']['advertViews'] ?? null,
            'performance_rating' => $data['responseMetrics']['performanceRating']['rating'] ?? null,
            'lead_count_rating' => $data['responseMetrics']['leadCountRating']['rating'] ?? 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)) {
            $existing = $vehicle->autoTraderData()->first();
            if ($existing) {
                $existing->update($at_data);
            } else {
                $vehicle->autoTraderData()->create(
                    array_merge($at_data, $this->defaultPublishFlags($vehicle))
                );
            }
        }
    }

    private function defaultPublishFlags(Vehicle $vehicle): array
    {
        if (Settings::get('sales-channels-auto-trader-enabled') === false) {
            return [];
        }
        return [
            'publish_advert' => $vehicle->is_new
                ? (bool) Settings::get('auto-trader-connect-publish-new-cars')
                : (bool) Settings::get('auto-trader-connect-publish-used-cars'),
            'publish_profile' => $vehicle->is_new
                ? (bool) Settings::get('auto-trader-connect-profile-new-cars')
                : (bool) Settings::get('auto-trader-connect-profile-used-cars'),
        ];
    }

    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);
    }

    /**
     * Repair a drifted primary for stock whose images have not changed.
     *
     * Resolves the live feed image order to the media ids we currently hold, re-sequences those
     * uses to match the feed, then lets the order-driven rule elect the primary. Using the live
     * feed keeps the primary correct even when the stored order is stale, and it only writes when
     * the stored order or the primary actually disagree with the feed. When the feed lists nothing
     * we hold it does nothing, leaving the unconditional ensurePrimaryUse() to guarantee a primary.
     *
     * @param Vehicle $vehicle
     * @param array $imageList media.images entries in feed order
     * @return void
     */
    private function reconcilePrimaryFromFeed(Vehicle $vehicle, array $imageList): void
    {
        $feedImageIds = collect($imageList)->pluck('imageId')->filter()->values();

        if ($feedImageIds->isEmpty()) {
            return;
        }

        $uses = $vehicle->mediaUses()->with('media')->get();
        $desiredOrder = $this->feedOrderForHeldMedia($feedImageIds, $uses);

        if ($desiredOrder === [] || $this->feedOrderAlreadyApplied($desiredOrder, $uses)) {
            return;
        }

        Media::setUseOrdering($desiredOrder, $vehicle);
        Media::setPrimaryUseByOrder($vehicle);
    }

    /**
     * The feed order resolved to a [media_id => position] map for the media the owner holds,
     * skipping feed images not held locally so the first one we do hold takes position 0.
     *
     * @param Collection $feedImageIds AutoTrader image ids in feed order
     * @param Collection $uses The owner's media uses (with media loaded)
     * @return array<int, int>
     */
    private function feedOrderForHeldMedia(Collection $feedImageIds, Collection $uses): array
    {
        $mediaIdByAutoTraderId = $uses
            ->filter(fn ($use) => !empty($use->media?->auto_trader_id))
            ->mapWithKeys(fn ($use) => [$use->media->auto_trader_id => $use->media_id]);

        return $feedImageIds
            ->map(fn ($autoTraderId) => $mediaIdByAutoTraderId->get($autoTraderId))
            ->filter()
            ->values()
            ->flip()
            ->all();
    }

    /**
     * Whether the stored order and the order-driven primary already match the feed, so the
     * reconcile can return without writing. The primary is predicted the same way
     * setPrimaryUseByOrder elects it: the lowest (order, id) use once the feed order is applied.
     *
     * @param array<int, int> $desiredOrder [media_id => position]
     * @param Collection $uses The owner's media uses
     * @return bool
     */
    private function feedOrderAlreadyApplied(array $desiredOrder, Collection $uses): bool
    {
        $orderMatches = collect($desiredOrder)->every(
            fn ($position, $mediaId) => (int) $uses->firstWhere('media_id', $mediaId)->order === $position
        );

        $effectiveOrder = fn ($use) => $desiredOrder[$use->media_id] ?? $use->order;
        $desiredPrimary = $uses
            ->sort(fn ($a, $b) => [$effectiveOrder($a), $a->id] <=> [$effectiveOrder($b), $b->id])
            ->first();
        $primaries = $uses->filter(fn ($use) => (bool) $use->primary);

        return $orderMatches
            && $primaries->count() === 1
            && $primaries->first()->getKey() === $desiredPrimary->getKey();
    }

    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) {
                    // Handle unique constraint error
                }
            });
    }

    public static function mergeFittedFeaturesWithTaxonomyPrices(
        array $fittedFeatures,
        array $taxonomyFeatures
    ): array {
        $standard = collect($taxonomyFeatures)
            ->filter(fn ($f) => ($f['type'] ?? '') === 'Standard')
            ->values()
            ->all();

        $taxonomyOptionalByName = collect($taxonomyFeatures)
            ->filter(fn ($f) => ($f['type'] ?? '') === 'Optional')
            ->keyBy(fn ($f) => self::normaliseFeatureName($f['name'] ?? ''));

        $fittedOptional = collect($fittedFeatures)
            ->filter(fn ($f) => ($f['type'] ?? '') === 'Optional')
            ->map(function ($fitted) use ($taxonomyOptionalByName) {
                $match = $taxonomyOptionalByName->get(
                    self::normaliseFeatureName($fitted['name'] ?? '')
                );
                return [
                    'type' => 'Optional',
                    'name' => $fitted['name'] ?? ($match['name'] ?? null),
                    'category' => $match['category'] ?? $fitted['category'] ?? null,
                    'basicPrice' => $match['basicPrice'] ?? null,
                    'vatPrice' => $match['vatPrice'] ?? null,
                ];
            })
            ->values()
            ->all();

        return array_merge($standard, $fittedOptional);
    }

    private static function normaliseFeatureName(string $name): string
    {
        return strtolower(trim($name));
    }

    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) {
                    // Handle unique constraint error
                }
            });
    }

    /**
     * AUTO-1653: AutoTrader keeps emitting STOCK_UPDATE notifications for vehicles long after
     * they are sold. Once a vehicle is SOLD and its record has not been updated for over a
     * week, skip re-applying the stale data so long-sold vehicles are not churned. Falls back
     * to processing the update when the lifecycle state is not SOLD, the timestamp is missing,
     * or it cannot be parsed.
     */
    private function isSoldAndStale(array $vehicle_data): bool
    {
        if (($vehicle_data['metadata']['lifecycleState'] ?? null) !== 'SOLD') {
            return false;
        }

        $lastUpdated = $vehicle_data['metadata']['lastUpdated'] ?? null;
        if (empty($lastUpdated)) {
            return false;
        }

        try {
            return Carbon::parse($lastUpdated)->lessThan(Carbon::now()->subWeek());
        } catch (\Exception) {
            return false;
        }
    }

    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): array
    {
        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,
            'Caravan' => VehicleType::CARAVAN->value,
            'Motorhome' => VehicleType::MOTORHOME->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 mapChargeTimes(array $chargeTimes): array
    {
        if (empty($chargeTimes)) {
            return [];
        }

        $entries = collect($chargeTimes);

        // Slow charge = AC supply, pick lowest duration
        $slowCharge = $entries
            ->filter(fn ($ct) => str_contains($ct['chargerSupplyType'], 'AC'))
            ->sortBy('durationMinutes')
            ->first();

        // Quick charge = DC supply, pick lowest duration
        $quickCharge = $entries
            ->filter(fn ($ct) => $ct['chargerSupplyType'] === 'DC')
            ->sortBy('durationMinutes')
            ->first();

        $data = [];

        if ($slowCharge) {
            $data['battery_charge_time'] = $slowCharge['durationMinutes'];
            $data['plug_type'] = $slowCharge['connectorType'];
            $data['battery_slow_charge_description'] = trim(
                $slowCharge['chargerDescription'] . ' ' . $slowCharge['chargerSupplyType']
            );
        }

        if ($quickCharge) {
            $data['battery_quick_charge_time'] = $quickCharge['durationMinutes'];
            $data['rapid_charge_plug_type'] = $quickCharge['connectorType'];
            $data['battery_quick_charge_level'] = $quickCharge['endBatteryPercentage'];
            $data['battery_quick_charge_start_level'] = $quickCharge['startBatteryPercentage'];
            $data['battery_quick_charge_description'] = trim(
                $quickCharge['chargerDescription'] . ' ' . $quickCharge['chargerSupplyType']
            );
        }

        return $data;
    }

    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,
        };
    }
}
