<?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\Jobs\ImportImagesFromUrlList;
use App\TaxonomyMap;
use App\Traits\EnsuresVehicleAttribute;
use App\Traits\ImportChecksConditions;
use App\Traits\StockSyncTraits;
use App\Traits\TracksImportStats;
use App\VehicleType;
use Carbon\Carbon;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Mtc\ContentManager\Models\MediaUse;
use Mtc\MercuryDataModels\Vehicle;

class Pinewood implements StockProvider
{
    use DispatchesJobs;
    use StockSyncTraits;
    use ImportChecksConditions;
    use EnsuresVehicleAttribute;
    use TracksImportStats;

    public const NAME = 'pinewood';
    public const PINEWOOD_VEHICLE_TYPE_CAR = '1';
    public const PINEWOOD_VEHICLE_TYPE_LCV = '2';
    public const PINEWOOD_VEHICLE_TYPE_BIKE = '4';

    protected Collection $dealerships;
    private array $make = [];
    private array $model = [];
    private array $transmission = [];
    private array $body_style = [];
    private array $fuel_type = [];

    private array $isDemoProfiles = [
        'demo',
        'ex demo'
    ];

    private array $isPreRegProfiles = [
        'pre-reg',
        'pre reg'
    ];

    /**
     * Check if enabled
     *
     * @return bool
     */
    public function enabled(): bool
    {
        return Settings::get('stock-pinewood-enabled', false) ?? false;
    }

    /**
     * Name of the integration
     *
     * @return string
     */
    public function name(): string
    {
        return 'Pinewood DMS';
    }

    /**
     * Perform a scheduled import task
     *
     * @return void
     */
    public function runScheduledImport(bool $fullSync = true): void
    {
        $this->resetStats();
        $this->markVehiclesToBeSynced();
        $this->import($this->fetchVehicles());
    }

    /**
     * Fields to add to dealership management
     *
     * @return array[]
     */
    public function dealershipAdditionalDataFields()
    {
        return [
            'pinewood-location-id' => [
                'type' => 'text',
                'label' => 'Location ID within Pinewood DMS',
            ],
        ];
    }

    /**
     * Get the vehicle data for a location from API
     *
     * @param string $locationId
     * @return Collection
     */
    public function fetchVehiclesFromLocation(string $locationId): Collection
    {
        if (empty($locationId)) {
            return collect();
        }
        $params = [
            'includeNewVehicles' => Settings::get('stock-pinewood-sync-new-vehicles') ? 'true' : 'false',
            'includeUsedVehicles' => Settings::get('stock-pinewood-sync-used-vehicles') ? 'true' : 'false',
            'includeImageDetails' => Settings::get('stock-pinewood-sync-images') ? 'true' : 'false',
            'includeVideoDetails' => 'true',
            'includeVehicleWithInternetPriceOnly' => Settings::get('stock-pinewood-sync-only-with-internet-price')
                ? 'true'
                : 'false',
        ];
        $url = 'https://api.pinnacledms.net/APIv2/Vehicles/StockList?' . http_build_query($params);
        $response = Http::withHeaders([
            'Accept' => 'application/json',
            'UserName' => Settings::get('stock-pinewood-username'),
            'Password' => Settings::get('stock-pinewood-password'),
            'OrganisationalUnit_UID' => $locationId,
            'Accept-Encoding' => 'gzip,deflate',
        ])
            ->get($url);

        if ($response->successful()) {
            return collect($response->json())->map(fn($item) => array_merge(
                $item,
                ['mtc_location_id' => $locationId]
            ));
        }

        Log::warning('Pinewood sync issue', [
            'tenant' => tenant('id'),
            'location' => $locationId,
            'response_code' => $response->status(),
            'response' => $response->body(),
        ]);

        return collect([]);
    }

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

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

    /**
     * Fetch vehicles from API
     *
     * @return Collection
     */
    private function fetchVehicles(): Collection
    {
        return $this->getAllStockLocations('pinewood-location-id')
            ->map(fn($locationId) => $this->fetchVehiclesFromLocation($locationId))
            ->flatten(1)
            ->unique('Vehicle_UID');
    }

    /**
     * Import vehicles
     *
     * @param Collection $stock
     * @return void
     */
    private function import(Collection $stock)
    {
        $this->retrieveRequiredVehicleAttributes();

        $stock = $stock->filter(fn($vehicle) => $this->filterByPrice($vehicle));
        $stock->each(fn(array $vehicle) => $this->syncVehicle($vehicle));

        $this->removeNotSynced();
        Event::dispatch(new StockSyncFinished(self::NAME));
    }

    /**
     * Filter out vehicles depending on price settings
     *
     * @param array $vehicle
     * @return bool
     */
    private function filterByPrice(array $vehicle): bool
    {
        if (Settings::get('stock-pinewood-sync-with-price') !== true) {
            return true;
        }

        if (Settings::get('stock-pinewood-sync-only-with-internet-price')) {
            return $vehicle['InternetPrice'] > 0;
        }

        return ($vehicle['InternetPrice'] ?: $vehicle['RetailPrice']) > 0;
    }

    /**
     * Sync vehicle record
     *
     * @param array $data
     * @return void
     */
    private function syncVehicle(array $data): void
    {
        try {
            /** @var Vehicle $vehicle */
            $vehicle = Vehicle::query()
                ->where('uuid', $data['Vehicle_UID'])
                ->firstOrNew([
                    'stock_provider' => self::NAME,
                    'uuid' => $data['Vehicle_UID'],
                ]);

            // Capture original price before update for numeric comparison
            $originalPrice = $vehicle->price;

            $video = collect($data['Videos'] ?? [])
                ->filter(fn($item) => !empty($item['VideoURL']))
                ->pluck('VideoURL')
                ->first();

            $type = $this->getVehicleType($data['VehicleType'] ?? '');
            $make_id = $this->getMappedTaxonomy(TaxonomyMap::MAKE, $data['Make']['Make'], $data);
            if ($type === VehicleType::MOTORCYCLE->value) {
                // Motorcycle model name is likely to be hard to understand, derivative is a much more accurate approach
                // e.g. CMX vs CMX1100 / CMX500
                $model_id = $this->getMappedTaxonomy(
                    TaxonomyMap::MODEL,
                    $data['Specification']['Specification'] ?? '',
                    $data,
                    $make_id
                );
            }
            if (empty($model_id)) {
                $model_id = $this->getMappedTaxonomy(TaxonomyMap::MODEL, $data['Model']['Model'], $data, $make_id);
            }
            $vehicle->fill([
                'type' => $type,
                'registration_number' => $data['RegistrationNumber'],
                'first_registration_date' => $this->getRegistrationDate($data['RegistrationDate'] ?? ''),
                'make_id' => $make_id,
                'model_id' => $model_id,
                'derivative' => $data['Specification']['Specification'] ?? '',
                'colour' => $data['Colour'],
                'fuel_type_id' => $this->getMappedTaxonomy(
                    TaxonomyMap::FUEL_TYPE,
                    $data['FuelType']['FuelType'],
                    $data
                ),
                'body_style_id' => $this->getMappedTaxonomy(
                    TaxonomyMap::BODY_STYLE,
                    $data['BodyStyle']['BodyStyle'],
                    $data
                ),
                'engine_size_cc' => $data['EngineSize'],
                'price' => $data['InternetPrice'] ?: $data['RetailPrice'],
                'is_new' => $data['IsNew'],
                'dealership_id' => $this->dealershipId($data['mtc_location_id']),
                'transmission_id' => $this->getMappedTaxonomy(
                    TaxonomyMap::TRANSMISSION,
                    $data['Transmission']['Transmission'],
                    $data
                ),
                'manufacture_year' => $this->modelYear($data),
                'vin' => $data['VIN'],
                'odometer_mi' => $data['Odometer'],
                'odometer_km' => $vehicle->milesToKm($data['Odometer']),
                'previous_owner_count' => $data['PreviousOwners'],
                'co2' => $data['CO2Emission'],
                'main_video_url' => $video,
                'is_demo' => $this->isDemo($data),
                'is_vat_applicable' => $this->getVatApplicable($data['VehicleType'], $data),
                'is_vat_qualifying' =>  $data['IsQualifying'] ?? false,
                'is_published' => $data['Status'] == 0,
                'was_recently_synced' => true,
            ]);

            $vehicle->is_published = $this->shouldBePublished(
                fn() => $vehicle->is_published,
                'pinewood',
                $vehicle,
            ) ?? false;

            $vehicle->save();

            $vehicleAttributes = [
                'pinewood-stock-number' => $data['StockNumber'] ?? '',
            ];

            if (Settings::get('vehicles-pre-reg-condition-enabled')) {
                $vehicleAttributes['is-pre-reg'] = $this->isPreReg($data);
            }

            $this->setVehicleAttributes($vehicleAttributes, $vehicle);

            if ($this->shouldSyncImages($vehicle, $data)) {
                $this->syncImages($vehicle, $data['Images']);
            }

            $this->storeUnmappedTaxonomy($vehicle);

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

            if ($vehicle->wasChanged('price')) {
                $this->priceChangeEvents($vehicle, self::NAME, $originalPrice);
            }
        } catch (\Exception $e) {
            $this->incrementFailed();
            throw $e;
        }
    }

    private function getRegistrationDate(string $reg_date_string): ?string
    {
        return empty($reg_date_string)
            || Str::startsWith($reg_date_string, '0001')
            ? null
            : $reg_date_string;
    }

    private function isDemo(array $data): bool
    {
        return collect($data['Profiles'])
                ->filter(fn ($profile) => Str::contains($profile['Description'], $this->isDemoProfiles, true))
                ->count() > 0;
    }

    private function isPreReg(array $data): bool
    {
        return collect($data['Profiles'])
                ->filter(fn ($profile) => Str::contains($profile['Description'], $this->isPreRegProfiles, true))
                ->count() > 0;
    }

    /**
     * Check if images should be synced for vehicles
     *
     * @param Vehicle $vehicle
     * @param array $data
     * @return bool
     */
    private function shouldSyncImages(Vehicle $vehicle, array $data): bool
    {
        $images = collect($data['Images'])
            ->pluck('ImageURI')
            ->toArray();
        return !empty($images)
            && Settings::get('stock-pinewood-sync-images')
            && empty($vehicle->deleted_at)
            && $this->imageChecksumMismatch($images, $vehicle);
    }

    /**
     * Sync images for vehicle
     *
     * @param Vehicle $vehicle
     * @param array $images
     * @return void
     */
    private function syncImages(Vehicle $vehicle, array $images)
    {
        $imageList = collect($images)->map(fn ($image) => rtrim((string) $image['ImageURI'], '?'));

        $toRemove = $this->getImagesToRemove($vehicle, $imageList->toArray());
        MediaUse::query()->whereIn('id', $toRemove)->delete();

        $this->dispatch(new ImportImagesFromUrlList($imageList, $vehicle, false, 'pinewoood'));
    }

    private function getImagesToRemove(Vehicle $vehicle, array $images): Collection
    {
        $mediaUses = $vehicle->mediaUses()->with('media')
            ->whereHas('media', fn ($query) => $query->where('image_provider', 'pinewoood'))
            ->get();

        $notInList = $mediaUses
            ->filter(fn ($mediaUse) => !in_array($mediaUse->media->source_filename, $images))
            ->pluck('id');

        $duplicates = $mediaUses->groupBy(fn ($mediaUse) => $mediaUse->media->source_filename)
            ->filter(fn (Collection $group) => $group->count() > 1)
            ->map(fn (Collection $group) => $group->first())
            ->pluck('id');

        return $notInList->merge($duplicates);
    }

    /**
     * Find the value of model year from the registration date field
     *
     * @param $vehicle_data
     * @return string|null
     */
    private function modelYear($vehicle_data)
    {
        try {
            $year = Carbon::parse($vehicle_data['RegistrationDate'])->format('Y');
            return $year > 1900 ? $year : null;
        } catch (\Exception) {
            return null;
        }
    }

    private function getVehicleType(string $vehicle_type): string
    {
        return match ($vehicle_type) {
            self::PINEWOOD_VEHICLE_TYPE_CAR => VehicleType::CAR->value,
            self::PINEWOOD_VEHICLE_TYPE_LCV => VehicleType::LCV->value,
            self::PINEWOOD_VEHICLE_TYPE_BIKE => VehicleType::MOTORCYCLE->value,
            default => VehicleType::CAR->value,
        };
    }

    private function getVatApplicable(string $vehicle_type, array $data): bool
    {
        return match ($vehicle_type) {
            self::PINEWOOD_VEHICLE_TYPE_LCV => false,
            default => true,
        };
    }

    private function retrieveRequiredVehicleAttributes(): void
    {
        $this->vehicleAttributes = [
            'pinewood-stock-number' => $this->getVehicleAttribute(
                'Pinewood Stock Number',
                'text',
                'pinewood-stock-number',
            )->toArray(),
        ];

        if (Settings::get('vehicles-pre-reg-condition-enabled')) {
            $this->vehicleAttributes['is-pre-reg'] = $this->getVehicleAttribute(
                'Pre-Reg vehicle',
                'boolean',
                'is-pre-reg',
            )->toArray();
        }
    }
}
