<?php

namespace App\Modules\Stock;

use App\Contracts\StockProvider;
use App\Events\NewVehicleImported;
use App\Events\StockSyncFinished;
use App\Facades\Settings;
use App\Jobs\ImportImagesFromUrlList;
use App\TaxonomyMap;
use App\Traits\MapsTaxonomies;
use App\Traits\VehicleRegistrationDateFinderTrait;
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 Mtc\ContentManager\Models\MediaUse;
use Mtc\MercuryDataModels\Currency;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\Services\FinanceService;
use Mtc\MercuryDataModels\Services\FinanceServiceHelper;
use Mtc\MercuryDataModels\Vehicle;

class Skupenet implements StockProvider
{
    use DispatchesJobs;
    use MapsTaxonomies;
    use VehicleRegistrationDateFinderTrait;

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

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

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

    /**
     * Perform a scheduled import task
     *
     * @return void
     */
    public function runScheduledImport(bool $fullSync = true): void
    {
        Vehicle::query()
            ->where('stock_provider', $this->getProviderName())
            ->update([
                'was_recently_synced' => false,
            ]);
        $this->import($this->fetchVehicles());
    }

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

    /**
     * Get the vehicle data for a location from API
     *
     * @param string $locationId
     * @return Collection
     */
    public function fetchVehiclesFromLocation(string $locationId): Collection
    {
        $params = [
            'partnerid' => Settings::get('stock-skupenet-partner-id'),
            'password' => Settings::get('stock-skupenet-password'),
            'output' => 'JSON2',
            'sellerid' => $locationId,
            'resultsperpage' => 99999,
            'pagenum' => 1,
        ];
        $url = 'https://api.easierad.com/search?' . http_build_query($params);

        return $this->makeSkupenetRequest($url, 'results');
    }

    public function fetchVehicleDetails(int $id): Collection
    {
        $params = [
            'partnerid' => Settings::get('stock-skupenet-partner-id'),
            'password' => Settings::get('stock-skupenet-password'),
            'output' => 'JSON2',
            'id' => $id,
        ];
        $url = 'https://api.easierad.com/search/ad?' . http_build_query($params);

        return $this->makeSkupenetRequest($url);
    }

    protected function makeSkupenetRequest(string $url, ?string $keyToFetch = null): Collection
    {
        $response = Http::get($url);

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

            if (!empty($data)) {
                return collect($data);
            }
        }

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

        return collect();
    }

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

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

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

    /**
     * Import vehicles
     *
     * @param Collection $stock
     * @return void
     */
    private function import(Collection $stock)
    {
        if ($stock->count() === 0) {
            // No vehicles to sync, assume service failure and stop sync to avoid removing vehicles
            return;
        }
        $stock = $stock->filter(fn($vehicle) => $this->filterByPrice($vehicle));

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

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

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

        if (!is_numeric($vehicle['price'])) {
            return false;
        }

        return $vehicle['price'] > 0;
    }

    /**
     * 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['skupenet-location-id'] ?? null;
            });

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

    /**
     * Sync vehicle record
     *
     * @param array $data
     * @return void
     */
    private function syncVehicle(array $data): void
    {
        $type = match (true) {
            stripos($data['type'], 'lcv') => VehicleType::LCV->value,
            stripos($data['type'], 'bike'), stripos($data['type'], 'rad') => VehicleType::MOTORCYCLE->value,
            default => VehicleType::CAR->value,
        };
        if ($data['fields']['body'] === 'Commercial/Van') {
            $type = VehicleType::LCV->value;
        }

        $extraData = $this->fetchVehicleDetails($data['id']);
        $make_id = $this->getMappedTaxonomy(TaxonomyMap::MAKE, $data['fields']['make'] ?? '', $data);

        /** @var Vehicle $vehicle */
        $vehicle = Vehicle::query()
            ->where('uuid', $data['id'])
            ->firstOrNew([
                'stock_provider' => $this->getProviderName(),
                'uuid' => $data['id'],
            ]);
        $mileageType = strtolower($data['fields']['mileageunits'] ?? 'kms') === 'kms' ? 'km' : 'mi';

        if ($this->shouldSyncVideo($vehicle, $data)) {
            $vehicle['main_video_url'] = $this->getVideoUrl($data);
        }

        $vehicle->fill([
            'title' => ($data['fields']['make'] ?? '') . ' ' . ($data['fields']['model'] ?? ''),
            'registration_number' => $data['fields']['registration'],
            'first_registration_date' => !empty($data['fields']['registration'])
                ? Carbon::parse($this->getRegistrationDateFromRegistrationNumber($data['fields']['registration']))
                : null,
            'derivative' => $data['fields']['trim'] ?? '',
            'make_id' => $make_id,
            'model_id' => $this->getMappedTaxonomy(TaxonomyMap::MODEL, $data['fields']['model'] ?? '', $data, $make_id),
            'colour' => $data['fields']['colour'] ?? null,
            'fuel_type_id' => $this->getMappedTaxonomy(TaxonomyMap::FUEL_TYPE, $data['fields']['fuel'] ?? null, $data),
            'body_style_id' => $this->getMappedTaxonomy(
                TaxonomyMap::BODY_STYLE,
                $data['fields']['body'] ?? null,
                $data
            ),
            'type' => $type,
            'engine_size_cc' => isset($data['fields']['enginesize'])
                ? $this->getEngineSize($data['fields']['enginesize']) : null,
            'seats' => $data['fields']['seats'] ?? null,
            'previous_owner_count' => $extraData['fields']['numowners'] ?? null,
            'door_count' => $extraData['fields']['numdoors'] ?? null,
            'transmission_id' => $this->getMappedTaxonomy(
                TaxonomyMap::TRANSMISSION,
                $data['fields']['gearbox'] ?? null,
                $data
            ),
            'co2' => $data['fields']['CO2'] ?? null,
            'manufacture_year' => $data['fields']['year'],
            'was_recently_synced' => true,
            'is_published' => $data['price'] > 1,
            'featured' => $data['featured'],
            'is_new' => ($data['fields']['mileage'] ?? 1)
                <= Settings::get('stock-skupenet-new-vehicle-mileage-threshold'),
            'dealership_id' => $this->dealershipId($data['sellerid']),
            'odometer_mi' => $mileageType === 'km'
                ? $vehicle->kmToMiles($data['fields']['mileage'] ?? 0)
                : $data['fields']['mileage'] ?? 0,
            'odometer_km' => $mileageType === 'km'
                ? $data['fields']['mileage'] ?? 0
                : $vehicle->milesToKm($data['fields']['mileage'] ?? 0),
            'price' => $this->getPrice($data),
            'description' => trim(strip_tags($extraData['description'] ?? '')),
        ])->save();

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

        if ($this->shouldSyncOptions($vehicle, $extraData['options'] ?? [])) {
            $this->syncOptions($vehicle, $extraData['options'] ?? []);
        }

        $this->storeUnmappedTaxonomy($vehicle);

        if ($vehicle->wasRecentlyCreated) {
            Event::dispatch(new NewVehicleImported($vehicle, $data));
        }
    }

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

    /**
     * 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_from_other_provider = $vehicle->mediaUses()
            ->whereHas('media', fn($query) => $query->where('image_provider', '!=', 'skupenet'))
            ->count();
        return Settings::get('stock-skupenet-sync-images') && $images_from_other_provider == 0;
    }

    private function shouldSyncVideo(Vehicle $vehicle, array $data): bool
    {
        return Settings::get('stock-skupenet-sync-video');
    }

    /**
     * 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($image, '?'));

        $to_remove = $this->getImagesToRemove($vehicle, $imageList->toArray());
        MediaUse::query()->whereIn('id', $to_remove)->delete();
        $this->dispatch(new ImportImagesFromUrlList($imageList, $vehicle, false, 'skupenet'));
    }

    /**
     * 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;
    }

    /**
     * handle engine sizes cc which may contain text e.g. 'litre'
     * @param bool $value
     */
    private function getEngineSize($value = false)
    {
        $value = strtoupper($value);
        $value = str_replace('LITRES', '', $value);
        $value = str_replace('LITRE', '', $value);
        $value = str_replace(' ', '', $value);

        if (is_numeric($value)) {
            return $value * 1000;
        }

        return false;
    }

    /**
     * Get currency type from value
     *
     * @param string $value
     * @return string
     */
    private function getCurrencyType(string $value = ''): string
    {
        $type = 'EUR';

        if (stripos($value, 'gbp') || stripos($value, 'pound')) {
            $type = 'GBP';
        }

        return $type;
    }

    /**
     * Get price in default currency
     *
     * @param array $data
     * @return float|int|string
     */
    private function getPrice(array $data = []): float|int|string
    {
        $price = is_numeric($data['price']) ? $data['price'] : 0;
        $priceCurrency = $this->getCurrencyType($data['currency']);

        if ($priceCurrency == 'EUR') {
            $currency = Currency::query()
                ->where('CODE', Settings::get('app-details-currency'))
                ->first();

            return $currency->ratio * $price;
        }

        if ($priceCurrency != Settings::get('app-details-currency')) {
            $priceCurrencyRate = Currency::query()
                ->where('CODE', $priceCurrency)
                ->first();

            $priceInEuro = $priceCurrencyRate->ratio * $price;

            $appCurrency = Currency::query()
                ->where('CODE', Settings::get('app-details-currency'))
                ->first();

            return $appCurrency->ratio * $priceInEuro;
        }

        return $price;
    }

    private function shouldSyncOptions(Vehicle $vehicle, array $options): bool
    {
        return !empty($options)
            && $vehicle->features()->count() === 0;
    }

    private function syncOptions(Vehicle $vehicle, array $options)
    {
        collect($options)
            ->each(fn(array $optionData) => $vehicle->features()->updateOrCreate([
                'vehicle_type' => $vehicle->getMorphClass(),
                'name' => $optionData['value'],
            ]));
    }

    private function getVideoUrl(array $data): ?string
    {
        if (empty($data['numvideos'])) {
            return null;
        }

        try {
            return match ($data['videos'][0]['provider']) {
                'Youtube' => 'https://youtu.be/' . $data['videos'][0]['filename']
            };
        } catch (\Exception $exception) {
            return null;
        }
    }

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

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

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

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