<?php

namespace App;

use App\Facades\Settings;
use App\Http\Requests\AddVehicleRequest;
use App\Http\Requests\UpdateVehicleRequest;
use App\Modules\Lookup\Contracts\VehicleLookupData;
use App\Traits\EnsuresVehicleAttribute;
use App\Traits\MapsTaxonomies;
use App\VehicleSpec\Contracts\VehicleStandardEquipmentItem;
use App\VehicleSpec\Contracts\VehicleTechnicalDataItem;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Mtc\ContentManager\Facades\Media;
use Mtc\ContentManager\Traits\EnsuresSlug;
use Mtc\MercuryDataModels\Services\FinanceService;
use Mtc\MercuryDataModels\Services\FinanceServiceHelper;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleSpecType;
use Mtc\MercuryDataModels\VehicleStandardEquipment;
use Mtc\MercuryDataModels\VehicleTechnicalData;
use Mtc\VehicleLookup\VehicleLookupResponse;
use Mtc\VehicleLookup\VehicleLookupService;

class VehicleRepository
{
    use DispatchesJobs;
    use SavesSeoData;
    use SavesVehicleSpecTypeData;
    use EnsuresVehicleAttribute;
    use EnsuresSlug;
    use MapsTaxonomies;

    protected string $providerName = 'vehicle-repository';

    public function __construct(private readonly VehicleLookupService $lookupService)
    {
        //
    }

    public function create(AddVehicleRequest $request): Vehicle
    {
        if ($this->shouldFetchSpecs($request)) {
            return $this->createVehicleWithSpecs($request);
        }

        /** @var Vehicle $vehicle */
        $vehicle = Vehicle::query()->create([
            'title' => $request->input('title'),
            'make_id' => $request->input('make_id'),
            'model_id' => $request->input('model_id'),
            'is_new' => $request->input('is_new') ?? 0,
            'type' => VehicleType::CAR->value,
            'uuid' => 'web-' . strtolower(Str::random(24)),
        ]);

        $this->handleVariant($request, $vehicle);

        return $vehicle;
    }

    public function update(UpdateVehicleRequest $request, Vehicle $vehicle): bool
    {
        DB::beginTransaction();
        $vehicle->fill($request->input());
        $vehicle->slug = $request->input('seo.slug') ?? $vehicle->slug;
        if (empty($vehicle->slug)) {
            $vehicle->slug = $this->buildSlug($vehicle);
        }
        $this->syncLabels($vehicle, collect($request->input('labels')));
        $vehicle->save();
        Media::setUsesForModel($request->input('media', []), $vehicle);
        Media::setUseOrdering(array_flip($request->input('media', [])), $vehicle);
        if (!empty($request->primaryMedia())) {
            Media::setPrimaryUse($request->input('media', []), $request->primaryMedia(), $vehicle);
        }
        $this->saveSeo($vehicle, $request->input('seo', []));
        $this->syncFeatures($vehicle, collect($request->input('features', [])));
        $this->syncEquipment($vehicle, collect($request->input('equipment', [])));
        $this->syncSpecs($vehicle, collect($request->input('specs', [])));
        collect($request->input('extra', []))
            ->filter(fn($attribute) => isset($attribute['value']) && empty($attribute['readonly']))
            ->each(fn($attribute) => $this->saveAttributeValue($vehicle, $attribute));
        $this->checkFinanceSync($vehicle);
        $this->saveFinance($vehicle, $request->input('finance_examples', []));
        DB::commit();

        return true;
    }

    public function copy(Request $request, Vehicle $origin): Vehicle
    {
        /** @var Vehicle $vehicle */
        $vehicle = Vehicle::query()
            ->create($origin->toArray());

        $origin->specs()
            ->get()
            ->each(fn(VehicleStandardEquipment $spec) => $vehicle->specs()->create($spec->toArray()));

        return $vehicle;
    }

    public function buildSlug(Vehicle $vehicle)
    {
        return match (Settings::get('automotive-vehicles-url_building_format')) {
            'vrm-make-model' => Str::slug(
                $vehicle->registration_number . '-' . ($vehicle->make->name ?? '') . '-' . ($vehicle->model->name ?? '')
            ),
            'title' => Str::slug($vehicle->title),
            'uuid' => Str::slug($vehicle->uuid),
            default => Str::slug($vehicle->title),
        };
    }

    public function mapTechnicalData(VehicleLookupData|VehicleLookupResponse $data): array
    {
        $make_id = !empty($data->make)
            ? $this->getMappedTaxonomy(
                TaxonomyMap::MAKE,
                $data->make,
                $data->toArray()
            ) : null;

        $model_id = !empty($data->model)
            ? $this->getMappedTaxonomy(
                TaxonomyMap::MODEL,
                $data->model,
                $data->toArray(),
                $make_id
            ) : null;

        return collect([
            'title' => trim(
                ($data->make ?? '') . ' ' .
                ($data->model ?? '') . ' ' .
                ($data->derivative ?? '')
            ),
            'registration_number' => $data->registration_number ?? null,
            'odometer_mi' => $data->mileage ?? null,
            'make_id' => $make_id,
            'model_id' => $model_id,
            'derivative' => $data->derivative ?? null,
            'engine_size_cc' => $data->engine_capacity_cc ?? null,
            'door_count' => $data->doors ?? null,
            'drivetrain' => $data->drivetrain ?? null,
            'vin' => $data->vin ?? null,
            'seats' => $data->seats ?? null,
            'previous_owner_count' => $data->previous_owner_count ?? null,
            'co2' => $data->co2 ?? null,
            'mpg' => $data->mpg ?? null,
            'first_registration_date' => $data->registration_date ?: null,
            'type' => $data->vehicle_type == 'lcv' ?  VehicleType::LCV : VehicleType::CAR,
            'manufacture_year' => $data->manufacture_year ?? $data->model_year ?? null,
            'colour' => $data->colour ?? null,
            'drivetrain_id' => !empty($data->drivetrain)
                ? $this->getMappedTaxonomy(
                    TaxonomyMap::DRIVETRAIN,
                    $data->drivetrain,
                    $data->toArray()
                ) : null,
            'body_style_id' => $this->getMappedTaxonomy(
                TaxonomyMap::BODY_STYLE,
                $data->body_type ?? $data->body_style,
                $data->toArray()
            ),
            'transmission_id' => !empty($data->transmission)
                ? $this->getMappedTaxonomy(
                    TaxonomyMap::TRANSMISSION,
                    $data->transmission,
                    $data->toArray()
                ) : null,
            'fuel_type_id' => !empty($data->fuel_type)
                ? $this->getMappedTaxonomy(
                    TaxonomyMap::FUEL_TYPE,
                    $data->fuel_type,
                    $data->toArray()
                ) : null,
            'features' => $data->features ?? null,
            'standard_equipment' => $data->standard_equipment ?? null,
            'technical_data' => $data->technical_data ?? null,
        ])->filter()->toArray();
    }

    public function getVehicleWarnings(): array
    {
        return collect([
            'total_vehicles',
            'no_price',
            'no_make',
            'no_model',
            'no_fuel_type',
            'no_body_style',
            'no_transmission',
            'no_images',
            'no_primary_image',
            'no_finance',
            'no_description',
        ])
            ->map(fn(string $type) => $this->getWarningValue($type))
            ->filter(fn($data_by_type) => !empty($data_by_type['values']))
            ->values()
            ->toArray();
    }

    public function getHasUnmappedTaxonomies(): bool
    {
        return TaxonomyMapable::query()
            ->where('tenant', tenant('id'))
            ->where('mappable_type', 'vehicle')
            ->whereHas('taxonomyMap', fn($query) => $query->whereNull('taxonomy_id'))
            ->exists();
    }

    private function syncLabels(Vehicle $vehicle, Collection $labels): void
    {
        $vehicle->labels()->sync($labels->pluck('id'));
    }

    private function saveFinance(Vehicle $vehicle, array $input): void
    {
        $vehicle->financeExamples()->whereNotIn('id', collect($input)->pluck('id'))->delete();
        if (FinanceServiceHelper::hasEnabledProvider() !== true) {
            return;
        }

        $data = collect($input)
            ->filter(fn($entry) => !is_numeric($entry['id']) || ($entry['provider'] ?? '') === 'manual')
            ->map(function (array $entry) {
                $entry['provider'] = 'manual';
                return $entry;
            });

        $data->each(fn($example) => $vehicle->financeExamples()->updateOrCreate([
            'id' => $example['id']
        ], $example));
    }

    private function checkFinanceSync(Vehicle $vehicle): void
    {
        if ($this->shouldTriggerFinanceCheck($vehicle)) {
            (new FinanceService())->request($vehicle);
        }
    }

    /**
     * Check whether we have enough info to trigger vehicle finance fetch
     *
     * @param Vehicle $vehicle
     * @return bool
     */
    private function shouldTriggerFinanceCheck(Vehicle $vehicle): bool
    {
        return FinanceServiceHelper::hasEnabledProvider()
            && app()->runningUnitTests() === false
            && !empty($vehicle->getAttribute('cap_id'))
            && $vehicle->financeExamples()->exists() === false;
    }

    protected function getProviderName(): string
    {
        return $this->providerName;
    }

    protected function setProviderName(string $providerName): void
    {
        $this->providerName = $providerName;
    }

    protected function handleVariant($request, Vehicle $vehicle): void
    {
        if (!$request->filled('variant')) {
            return;
        }

        $variantTypeColumn = $this->getVariantColumn($request->input('variant_type'));
        if ($variantTypeColumn && $this->lookupService->hasActiveDriver()) {
            $variant = $request->input('variant');
            $vehicleData = $this->mapTechnicalData($this->lookupService->getTechnicalData($variant));
            $vehicleData[$variantTypeColumn] = $variant;

            $vehicle->fill($vehicleData)->save();

            $this->syncStandardEquipment($vehicle, $vehicleData['standard_equipment'] ?? []);
            $this->syncTechnicalData($vehicle, $vehicleData['technical_data'] ?? []);

            if (isset($vehicleData['features']) && $vehicleData['features']->count()) {
                foreach ($vehicleData['features'] as $feature) {
                    $vehicle->features()->create([
                        'name' => $feature
                    ]);
                }
            }
        }
    }

    protected function getVariantColumn($variantType): ?string
    {
        return match ($variantType) {
            'cap' => 'cap_id',
            'auto-trader' => 'auto_trader_id',
            'motor-check' => 'motor_check_id',
            default => null,
        };
    }

    protected function shouldFetchSpecs($request): bool
    {
        // TODO: make sure this is not specific to motor-check but all supported lookup providers
        return $request->filled('registration_number')
            && $request->input('variant_type') === 'motor-check'
            && $this->lookupService->hasActiveDriver();
    }

    protected function createVehicleWithSpecs($request): Vehicle
    {
        try {
            $specService = $this->lookupService->findByVRM($request->input('registration_number'), 0);
            if ($lookupActiveDriver = $this->lookupService->findActiveDriver()) {
                $this->setProviderName($lookupActiveDriver);
            }
            $vehicleData = $this->mapTechnicalData($specService);
        } catch (\Throwable $throwable) {
            Log::warning($throwable->getMessage(), [$throwable->getTrace()]);
            throw ValidationException::withMessages([
                'Failed to retrieve vehicle ' . $request->input('registration_number')
                .  ' it may not exist in the providers database, please fill out vehicle details manually'
            ]);
        }
        $vehicleData['is_new'] = $request->input('is_new');
        $vehicleData['uuid'] = 'web-' . strtolower(Str::random(24));
        /** @var Vehicle $vehicle */
        $vehicle = Vehicle::query()->create($vehicleData);

        $this->syncStandardEquipment($vehicle, $vehicleData['standard_equipment'] ?? []);
        $this->syncTechnicalData($vehicle, $vehicleData['technical_data'] ?? []);
        $this->storeUnmappedTaxonomy($vehicle);
        return $vehicle;
    }

    protected function syncStandardEquipment(Vehicle $vehicle, $equipment): void
    {
        collect($equipment)->each(function (VehicleStandardEquipmentItem $item) use ($vehicle) {
            VehicleStandardEquipment::query()->updateOrCreate(
                [
                    'vehicle_id' => $vehicle->id,
                    'vehicle_type' => 'vehicle',
                    'description' => $item->description,
                ],
                [
                    'type' => VehicleSpecType::STANDARD_EQUIPMENT,
                    'code' => $item->code,
                    'category' => $item->category_description,
                    'updated_at' => now()
                ]
            );
        });
    }

    protected function syncTechnicalData(Vehicle $vehicle, $technicalData): void
    {
        collect($technicalData)->each(function (VehicleTechnicalDataItem $item) use ($vehicle) {
            VehicleTechnicalData::query()->updateOrCreate([
                'vehicle_id' => $vehicle->id,
                'vehicle_type' => 'vehicle',
                'description' => $item->description,
                'category' => $item->category
            ], [
                'code' => $item->code,
                'value' => $item->value,
            ]);
        });
    }

    private function getWarningValue(string $type)
    {
        return match ($type) {
            'total_vehicles' => [
                'severity' => 'info',
                'label' => __('labels.total_count'),
                'values' => collect([
                    [
                        'label' => __('labels.published'),
                        'value' => Vehicle::query()->active()->count(),
                        'slug' => 'active',
                        'selections' => [ 'status' => 'is_published']
                    ],
                    [
                        'label' => __('labels.total'),
                        'value' => Vehicle::query()->count(),
                    ],
                ])->filter(fn($row) => !empty($row['value']))->toArray(),
            ],
            'no_price' => [
                'severity' => 'warning',
                'label' => __('labels.vehicle_warnings.missing_price'),
                'values' => collect([
                    [
                        'label' => __('labels.published'),
                        'value' => Vehicle::query()->active()->where('price', '<=', 1)->count(),
                        'slug' => 'no_price_active',
                        'selections' => [ 'price_max' => 0, 'status' => 'is_published']
                    ],
                    [
                        'label' => __('labels.total'),
                        'value' => Vehicle::query()->where('price', '<=', 1)->count(),
                        'slug' => 'no_price',
                        'selections' => [ 'price_max' => 0]
                    ],
                ])->filter(fn($row) => !empty($row['value']))->toArray(),
            ],
            'no_make' => [
                'severity' => 'warning',
                'label' => __('labels.vehicle_warnings.missing_make'),
                'values' => collect([
                    [
                        'label' => __('labels.published'),
                        'value' => Vehicle::query()->active()->whereDoesntHave('make')->count(),
                        'slug' => 'no_make_active',
                        'selections' => [ 'make_id' => 'null', 'status' => 'is_published']
                    ],
                    [
                        'label' => __('labels.total'),
                        'value' => Vehicle::query()->whereDoesntHave('make')->count(),
                        'slug' => 'no_make',
                        'selections' => [ 'make_id' => 'null', 'status' => 'is_published']
                    ],
                ])->filter(fn($row) => !empty($row['value']))->toArray(),
            ],
            'no_model' => [
                'severity' => 'warning',
                'label' => __('labels.vehicle_warnings.missing_model'),
                'values' => collect([
                    [
                        'label' => __('labels.published'),
                        'value' => Vehicle::query()->active()->whereDoesntHave('model')->count(),
                        'slug' => 'no_model_active',
                        'selections' => [ 'model_id' => 'null', 'status' => 'is_published']
                    ],
                    [
                        'label' => __('labels.total'),
                        'value' => Vehicle::query()->whereDoesntHave('model')->count(),
                        'slug' => 'no_model',
                        'selections' => [ 'model_id' => 'null']
                    ],
                ])->filter(fn($row) => !empty($row['value']))->toArray(),
            ],
            'no_fuel_type' => [
                'severity' => 'warning',
                'label' => __('labels.vehicle_warnings.missing_fuel_type'),
                'values' => collect([
                    [
                        'label' => __('labels.published'),
                        'value' => Vehicle::query()->active()->whereDoesntHave('fuelType')->count(),
                        'slug' => 'no_fuel_type_active',
                        'selections' => [ 'fuel_type_id' => 'null', 'status' => 'is_published']
                    ],
                    [
                        'label' => __('labels.total'),
                        'value' => Vehicle::query()->whereDoesntHave('fuelType')->count(),
                        'slug' => 'no_fuel_type',
                        'selections' => [ 'fuel_type_id' => 'null']
                    ],
                ])->filter(fn($row) => !empty($row['value']))->toArray(),
            ],
            'no_body_style' => [
                'severity' => 'warning',
                'label' => __('labels.vehicle_warnings.missing_body_style'),
                'values' => collect([
                    [
                        'label' => __('labels.published'),
                        'value' => Vehicle::query()->active()->whereDoesntHave('bodyStyle')->count(),
                        'slug' => 'no_body_style_active',
                        'selections' => [ 'body_style_id' => 'null', 'status' => 'is_published']
                    ],
                    [
                        'label' => __('labels.total'),
                        'value' => Vehicle::query()->whereDoesntHave('bodyStyle')->count(),
                        'slug' => 'no_body_style',
                        'selections' => [ 'body_style_id' => 'null']
                    ],
                ])->filter(fn($row) => !empty($row['value']))->toArray(),
            ],
            'no_transmission' => [
                'severity' => 'warning',
                'label' => __('labels.vehicle_warnings.missing_transmission'),
                'values' => collect([
                    [
                        'label' => __('labels.published'),
                        'value' => Vehicle::query()->active()->whereDoesntHave('transmission')->count(),
                        'slug' => 'no_transmission_active',
                        'selections' => [ 'transmission_id' => 'null', 'status' => 'is_published']
                    ],
                    [
                        'label' => __('labels.total'),
                        'value' => Vehicle::query()->whereDoesntHave('transmission')->count(),
                        'slug' => 'no_transmission',
                        'selections' => [ 'transmission_id' => 'null']
                    ],
                ])->filter(fn($row) => !empty($row['value']))->toArray(),
            ],
            'no_primary_image' => [
                'severity' => 'warning',
                'label' => __('labels.vehicle_warnings.missing_primary_image'),
                'values' => collect([
                    [
                        'label' => __('labels.published'),
                        'value' => Vehicle::query()->active()->whereDoesntHave('primaryMediaUse')->count(),
                        'slug' => 'no_primary_image_active',
                    ],
                    [
                        'label' => __('labels.total'),
                        'value' => Vehicle::query()->whereDoesntHave('primaryMediaUse')->count(),
                        'slug' => 'no_primary_image',
                    ],
                ])->filter(fn($row) => !empty($row['value']))->toArray(),
            ],
            'no_images' => [
                'severity' => 'warning',
                'label' => __('labels.vehicle_warnings.missing_images'),
                'values' => collect([
                    [
                        'label' => __('labels.published'),
                        'value' => Vehicle::query()->active()->whereDoesntHave('mediaUses')->count(),
                        'slug' => 'no_images_active',
                        'selections' => [ 'image_count_max' => 0, 'status' => 'is_published']
                    ],
                    [
                        'label' => __('labels.total'),
                        'value' => Vehicle::query()->whereDoesntHave('mediaUses')->count(),
                        'slug' => 'no_images',
                        'selections' => [ 'image_count_max' => 0]
                    ],
                ])->filter(fn($row) => !empty($row['value']))->toArray(),
            ],
            'no_finance' => [
                'severity' => 'warning',
                'label' => __('labels.vehicle_warnings.missing_finance'),
                'values' => collect([
                    [
                        'label' => __('labels.published'),
                        'value' => Vehicle::query()->active()->whereDoesntHave('financeExamples')->count(),
                        'slug' => 'no_finance_active',
                    ],
                    [
                        'label' => __('labels.total'),
                        'value' => Vehicle::query()->whereDoesntHave('financeExamples')->count(),
                        'slug' => 'no_finance',
                    ],
                ])->filter(fn($row) => !empty($row['value']))->toArray(),
            ],
            'no_description' => [
                'severity' => 'warning',
                'label' => __('labels.vehicle_warnings.missing_description'),
                'values' => collect([
                    [
                        'label' => __('labels.published'),
                        'value' => Vehicle::query()->active()->whereNull('description')->count(),
                        'slug' => 'no_description_active',
                    ],
                    [
                        'label' => __('labels.total'),
                        'value' => Vehicle::query()->whereNull('description')->count(),
                        'slug' => 'no_description',
                    ],
                ])->filter(fn($row) => !empty($row['value']))->toArray(),
            ],
            default => [],
        };
    }
}
