<?php

namespace App\Modules\Stock;

use App\Contracts\StockProvider;
use App\Events\NewVehicleImported;
use App\Events\NewVehicleOfferImported;
use App\Events\StockSyncFinished;
use App\Facades\Settings;
use App\Jobs\ImportImagesFromUrlList;
use App\TaxonomyMap;
use App\Traits\MapsTaxonomies;
use App\VehicleType;
use Exception;
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 Mtc\ContentManager\Models\Template;
use Mtc\MercuryDataModels\Dealership;
use Illuminate\Http\Client\ConnectionException;
use Mtc\MercuryDataModels\OfferType;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleOffer;

class Skoda implements StockProvider
{
    use DispatchesJobs;
    use MapsTaxonomies;

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

    public const NAME = 'skoda';

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

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

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

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

    /**
     * Get the offer data for a location from API
     *
     * @param string $locationId
     * @return Collection
     */
    public function fetchOffersFromLocation(string $locationId): Collection
    {
        $data = [];
        $params = [
            'Dealers' => $locationId,
            'PageNo' => 1,
        ];

        $url = config('services.skoda.endpoint') . 'search?';
        $response = Http::get($url . http_build_query($params));

        if ($response->successful()) {
            $data = array_merge($data, $response->json('results.cars'));

            while (count($response->json('results.cars')) > 0) {
                $params['PageNo']++;

                $response = Http::get($url . http_build_query($params));

                if ($response->successful()) {
                    $data = array_merge($data, $response->json('results.cars'));
                }
            }

            if (empty($data)) {
                if ($response->status() !== 200) {
                    Log::warning('Skoda sync issue', [
                        'tenant' => tenant('id'),
                        'location' => $locationId,
                        'response_code' => $response->status(),
                        'response' => $response->body(),
                    ]);
                }

                return collect();
            }

            $filteredVehicleOffers = array_filter($data, function ($vehicleOffer) {
                return !empty($vehicleOffer['vin']);
            });

            foreach ($data as $key => $vehicleOffer) {
                if (empty($vehicleOffer['vin'])) {
                    continue;
                }

                try {
                    $url = config('services.skoda.endpoint') . 'getCarDetail?';
                    $response = Http::get($url . http_build_query([
                            'id' => $vehicleOffer['id'],
                            'selectedDealerId' => $locationId,
                        ]));

                    if ($response->successful()) {
                        $stockCar = $response->json();

                        $data[$key] = array_merge($vehicleOffer, $stockCar);
                    }
                } catch (ConnectionException $exception) {
                    Log::warning('Skoda sync connection issue', [
                        'tenant' => tenant('id'),
                        'location' => $locationId,
                        'response_code' => $response->status(),
                        'response' => $response->body(),
                        'message' => $exception->getMessage(),
                    ]);
                }
            }
        }

        return collect($data);
    }

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

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

    /**
     * Fetch offers from API
     *
     * @return Collection
     */
    private function fetchOffers(): Collection
    {
        return $this->getAllStockLocations()
            ->map(fn($locationId) => $this->fetchOffersFromLocation($locationId))
            ->flatten(1)
            ->unique('id')
            ->filter(fn(array $vehicle) => $vehicle['type'] == 'N');
    }

    /**
     * Import offers
     *
     * @param Collection $stock
     * @return void
     */
    private function import(Collection $stock)
    {
        if (in_array(Settings::get('stock-skoda-sync-type'), ['vehicle-offers', 'both'])) {
            $stock->each(fn(array $offer) => $this->syncOffer($offer));
            if ($stock->count()) {
                $this->removeOldOffers($stock);
            }
        }

        if (in_array(Settings::get('stock-skoda-sync-type'), ['vehicles', 'both'])) {
            $stock->each(fn(array $vehicle) => $this->syncVehicle($vehicle));
            if ($stock->count()) {
                $this->removeOldVehicles($stock);
            }
        }

        Event::dispatch(new StockSyncFinished(self::NAME));
    }

    /**
     * Find all stock locations to fetch data from
     * These are stored against dealerships
     *
     * @return Collection
     */
    private function getAllStockLocations(): Collection
    {
        $this->dealerships = Dealership::all()
            ->each(function (Dealership $dealership) {
                $dealership->stock_location = $dealership->data['skoda-dealer-id'] ?? null;
            });


        return $this->dealerships
            ->map(fn(Dealership $dealership) => $dealership->data['skoda-dealer-id'] ?? null)
            ->filter();
    }

    /**
     * Sync vehicle record
     *
     * @param array $data
     * @return void
     */
    private function syncOffer(array $data): void
    {
        try {
            /** @var VehicleOffer $vehicleOffer */
            $vehicleOffer = VehicleOffer::query()
                ->where('uuid', $data['id'])
                ->firstOrNew([
                    'stock_provider' => $this->getProviderName(),
                    'uuid' => $data['id'],
                ], [
                    'stock_provider' => $this->getProviderName(),
                    'uuid' => $data['id'],
                    'name' => 'Skoda ' . ucfirst($data['model']) . ' ' . ucfirst($data['title']),
                    'vin' => $data['vin'],
                    'dealership_id' => $this->dealershipId($data['dealer']['id']),
                    'published' => true,
                    'colour' => $data['colors']['exterior']['text'],
                    'make_id' => $this->getMappedTaxonomy(TaxonomyMap::MAKE, 'Skoda', $data),
                    'model_id' => $this->getMappedTaxonomy(TaxonomyMap::MODEL, $data['model'], $data),
                    'template_id' => Template::query()->where('slug', 'new-vehicle-offer')->first()?->id,
                    'type_id' => OfferType::query()->where('slug', 'new')->first()?->id,
                    'transmission_id' => $this->getMappedTaxonomy(
                        TaxonomyMap::TRANSMISSION,
                        str_contains($data['technicalData']['transmissionName'], 'Manual') ? 'Manual' : 'Automatic',
                        $data
                    ),
                    'fuel_type_id' => $this->getMappedTaxonomy(
                        TaxonomyMap::FUEL_TYPE,
                        $data['technicalData']['fuel'],
                        $data
                    ),
                    'body_style_id' => $this->getMappedTaxonomy(TaxonomyMap::BODY_STYLE, $data['modelBody'], $data),
                ]);

            $vehicleOffer->fill([
                'price' => $data['salePrice']['value'] + Settings::get('stock-skoda-delivery-price', 0),
                'name' => 'Skoda ' . ucfirst($data['model']) . ' ' . ucfirst($data['title'])
            ])->save();

            $this->syncEquipment($vehicleOffer, $data);

            if ($this->shouldSyncImages($vehicleOffer, $data)) {
                $this->syncImages($vehicleOffer, $data);
            }

            $this->storeUnmappedTaxonomy($vehicleOffer);

            if ($vehicleOffer->wasRecentlyCreated) {
                Event::dispatch(new NewVehicleOfferImported($vehicleOffer, $data));
            }
        } catch (Exception $exception) {
            Log::warning('Skoda sync import issue', [
                'tenant' => tenant('id'),
                'message' => $exception->getMessage(),
            ]);
        }
    }

    /**
     * Remove offers that are not part of the stock feed
     *
     * @param Collection $offers
     * @return void
     */
    private function removeOldOffers(Collection $offers): void
    {
        VehicleOffer::query()
            ->where('stock_provider', $this->getProviderName())
            ->whereNotIn('uuid', $offers->pluck('id'))
            ->delete();
    }

    /**
     * Check if images should be synced for vehicles
     *
     * @param $model
     * @param array $data
     * @return bool
     */
    private function shouldSyncImages($model, array $data): bool
    {
        if (!Settings::get('stock-skoda-sync-images')) {
            return false;
        }

        return (!empty($data['images']))
            && $model->mediaUses()->exists() === false;
    }

    /**
     * Sync images for vehicle
     *
     * @param $model
     * @param array $data
     * @return void
     */
    private function syncImages($model, array $data)
    {
        $imageList = collect($data['images'])->map(fn($image) => rtrim($image, '?'));
        $this->dispatch(new ImportImagesFromUrlList($imageList, $model, false, 'skoda'));
    }

    /**
     * Get the dealership id from stock location
     *
     * @param $locationId
     * @return string|null
     */
    private function dealershipId($locationId): ?string
    {
        return $this->dealerships
            ->where('stock_location', $locationId)
            ->first()
            ?->id;
    }

    private function syncEquipment($model, $data): void
    {
        $equipment = [];
        if (!empty($data['equipmentGroups'])) {
            foreach ($data['equipmentGroups'] as $equipmentCodeGroup) {
                if (!empty($equipmentCodeGroup['items'])) {
                    foreach ($equipmentCodeGroup['items'] as $equipmentCode) {
                        $equipment[$equipmentCode['key']] = [
                        'vehicle_id' => $model->id,
                        'vehicle_type' => get_class($model) == Vehicle::class ? 'vehicle' : 'offer',
                        'code' => $equipmentCode['keytoolCode'],
                        'type' => 'Standard',
                        'category' => $equipmentCodeGroup['name'],
                        'description' => $equipmentCode['text'],
                        ];
                    }
                }
            }

            foreach ($equipment as $item) {
                $existingEquipment = $model->equipment()
                ->where([
                    'vehicle_id' => $item['vehicle_id'],
                    'vehicle_type' => $item['vehicle_type'],
                    'description' => $item['description'],
                ])->first();

                if ($existingEquipment) {
                    $existingEquipment->update($item);
                } else {
                    $model->equipment()->create($item);
                }
            }

            // delete any standard equipment items that are not present in the stock record
            $model->equipment()
            ->where('type', '=', 'Standard')
            ->whereNotIn(
                'code',
                array_column($equipment, 'code')
            )
            ->delete();
        }
    }

    /**
     * Remove vehicles that are not part of the stock feed
     *
     * @param Collection $vehicles
     * @return void
     */
    private function removeOldVehicles(Collection $vehicles): void
    {
        Vehicle::query()
            ->where('stock_provider', $this->getProviderName())
            ->whereNotIn('uuid', $vehicles->pluck('id'))
            ->delete();
    }

    /**
     * Sync vehicle record
     *
     * @param array $data
     * @return void
     */
    private function syncVehicle(array $data): void
    {
        try {
            $mileage = null;
            if (!empty($data['mileage']['value'])) {
                if ($data['mileage']['unit'] == 'km') {
                    $mileage = (new Vehicle())->kmToMiles(str_replace(',', '', $data['mileage']['value']));
                } else {
                    $mileage = str_replace(',', '', $data['mileage']['value']);
                }
            }

            /** @var Vehicle $vehicle */
            $vehicle = Vehicle::query()
                ->where('uuid', $data['id'])
                ->firstOrNew([
                    'stock_provider' => $this->getProviderName(),
                    'uuid' => $data['id'],
                ], [
                    'stock_provider' => $this->getProviderName(),
                    'uuid' => $data['id'],
                    'type' => VehicleType::CAR->value,
                    'title' => 'Skoda ' . ucfirst($data['model']) . ' ' . ucfirst($data['title']),
                    'is_reserved' => $data['isReserved'],
                    'vin' => $data['vin'],
                    'dealership_id' => $this->dealershipId($data['dealer']['id']),
                    'manufacture_year' => $data['modelYear'],
                    'colour' => $data['colors']['exterior']['text'],
                    'make_id' => $this->getMappedTaxonomy(TaxonomyMap::MAKE, 'Skoda', $data),
                    'model_id' => $this->getMappedTaxonomy(TaxonomyMap::MODEL, $data['model'], $data),
                    'derivative' => ucfirst($data['title']),
                    'transmission_id' => $this->getMappedTaxonomy(
                        TaxonomyMap::TRANSMISSION,
                        $data['technicalData']['gear'],
                        $data
                    ),
                    'fuel_type_id' => $this->getMappedTaxonomy(
                        TaxonomyMap::FUEL_TYPE,
                        $data['technicalData']['fuel'],
                        $data
                    ),
                    'body_style_id' => $this->getMappedTaxonomy(
                        TaxonomyMap::BODY_STYLE,
                        $data['modelBody'],
                        $data
                    ),
                    'drivetrain_id' => $this->getMappedTaxonomy(
                        TaxonomyMap::DRIVETRAIN,
                        $data['technicalData']['drive'],
                        $data
                    ),
                    'engine_size_cc' => !empty($data['technicalData']['capacity']['value'])
                        ? str_replace(',', '', $data['technicalData']['capacity']['value'])
                        : null,
                    'odometer_mi' => $mileage,
                ]);

            $vehicle->fill([
                'is_published' => $data['type'] == 'N',
                'is_new' => $data['type'] == 'N',
                'price' => $data['salePrice']['value'] + Settings::get('stock-skoda-delivery-price', 0),
                'title' => 'Skoda ' . ucfirst($data['model']) . ' ' . ucfirst($data['title']),
                'body_style_id' => $this->getMappedTaxonomy(TaxonomyMap::BODY_STYLE, $data['modelBody'], $data),
                'transmission_id' => $this->getMappedTaxonomy(
                    TaxonomyMap::TRANSMISSION,
                    $data['technicalData']['gear'],
                    $data
                ),
            ])->save();

            $this->syncEquipment($vehicle, $data);

            if ($this->shouldSyncImages($vehicle, $data)) {
                $this->syncImages($vehicle, $data);
            }

            $this->storeUnmappedTaxonomy($vehicle);

            if ($vehicle->wasRecentlyCreated) {
                Event::dispatch(new NewVehicleImported($vehicle, $data));
            }
        } catch (Exception $exception) {
            Log::warning('Skoda sync import issue', [
                'tenant' => tenant('id'),
                'message' => $exception->getMessage(),
            ]);
        }
    }
}
