<?php

namespace App\Imports;

use App\Jobs\ImportImagesFromUrlList;
use App\Master\Models\VehicleMake;
use App\Master\Models\VehicleModel;
use App\TaxonomyMap;
use App\Traits\MapsTaxonomies;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Mtc\ContentManager\Facades\Media;
use Mtc\MercuryDataModels\BodyStyleType;
use Mtc\MercuryDataModels\Dealership;
use Mtc\MercuryDataModels\DrivetrainType;
use Mtc\MercuryDataModels\Form;
use Mtc\MercuryDataModels\Franchise;
use Mtc\MercuryDataModels\FuelType;
use Mtc\MercuryDataModels\OfferType;
use Mtc\MercuryDataModels\TransmissionType;
use Mtc\MercuryDataModels\VehicleOffer;

class OfferImport implements ToModel, WithHeadingRow
{
    use DispatchesJobs;
    use MapsTaxonomies;

    protected static array $offerFillable = [];

    public function __construct(
        private ?Collection $dealerships = null,
        private ?Collection $types = null,
        private ?Collection $makes = null,
        private ?Collection $models = null,
        private ?Collection $fuel_types = null,
        private ?Collection $body_styles = null,
        private ?Collection $drivetrains = null,
        private ?Collection $transmissions = null,
        private ?Collection $franchises = null,
    ) {
        $this->loadRelationships();
    }

    public function model(array $row): void
    {
        try {
            if (
                array_key_exists('to_be_deleted', $row)
                && !empty($row['to_be_deleted'])
                && array_key_exists('slug', $row)
                && !empty($row['slug'])
            ) {
                VehicleOffer::query()->where('slug', '=', (string)$row['slug'])->delete();
                return;
            }

            if (!array_key_exists('derivative', $row) || empty($row['derivative'])) {
                return;
            }

            $this->transformColumnToArray('key_features', $row);
            $this->transformColumnToArray('standard_spec', $row);
            $this->transformColumnToArray('technical_spec', $row);
            $row['new_car_type'] = strtolower($row['new_car_type'] ?? '');

            /** @var VehicleOffer $model */
            $model = VehicleOffer::query()
                ->updateOrCreate([
                    'slug' => $row['slug']
                ], $this->getFillableData($row));

            $this->syncOfferImages($model, $row['images'] ?? '');
            $this->syncFinance($model, $row);

            $this->storeUnmappedTaxonomy($model);
        } catch (\Exception $exception) {
            Log::warning('Failed to process entry for bulk offer import', [
                $exception->getMessage()
            ]);
        }
    }

    protected function getProviderName(): string
    {
        return 'offer-import';
    }

    /**
     * @param string $column
     * @param array $row
     * @return array
     */
    private function transformColumnToArray(string $column, array &$row, string $separator = ','): array
    {
        if (array_key_exists($column, $row)) {
            $row[$column] = collect(explode($separator, (string) $row[$column]))
                ->map(trim(...))
                ->filter(fn ($item) => !empty($item))
                ->values()
                ->toArray();
        }

        return $row;
    }

    private function syncFinance(VehicleOffer $offer, array $offerData): void
    {
        $finance = $offer->finance()->firstOrNew([
            'provider' => 'import'
        ]);

        $data = collect([
            'finance_type' => $offerData['finance_type'] ?? null,
            'first_payment' => $this->sanitiseNumber($offerData['first_payment'] ?? null),
            'monthly_price' => $this->sanitiseNumber($offerData['monthly_price'] ?? null),
            'final_payment' => $this->sanitiseNumber($offerData['final_payment'] ?? null),
            'full_price' => $this->sanitiseNumber($offerData['full_price'] ?? null),
            'deposit' => $this->sanitiseNumber($offerData['deposit'] ?? null),
            'total_amount' => $this->sanitiseNumber($offerData['total_amount'] ?? null),
            'total_credit_amount' => $this->sanitiseNumber($offerData['total_credit_amount'] ?? null),
            'apr' => $this->sanitiseNumber($offerData['apr'] ?? null),
            'interest_rate' => $this->sanitiseNumber($offerData['interest_rate'] ?? null),
            'number_of_payments' => $this->sanitiseNumber($offerData['number_of_payments'] ?? null),
            'term' => $this->sanitiseNumber($offerData['term'] ?? null),
            'annual_mileage' => $this->sanitiseNumber($offerData['annual_mileage'] ?? null),
            'customer_deposit' => $this->sanitiseNumber($offerData['customer_deposit'] ?? null),
            'dealer_deposit_contribution' => $this->sanitiseNumber($offerData['dealer_deposit_contribution'] ?? null),
            'option_to_purchase_fee' => $this->sanitiseNumber($offerData['option_to_purchase_fee'] ?? null),
            'excess_mileage_charge' => $this->sanitiseNumber($offerData['excess_mileage_charge'] ?? null),
        ])->filter()->toArray();

        // Only save if any details are set
        if (!empty($data)) {
            $finance->fill($data)->save();
        }
    }

    private function syncOfferImages(VehicleOffer $offer, string $images): void
    {
        $imageList = collect(explode('|', $images))
            ->filter()
            ->reject(fn (string $image) => $this->imageAlreadyHostedOnServer($image));

        if ($imageList->isNotEmpty()) {
            $this->dispatch(new ImportImagesFromUrlList($imageList, $offer));
        }
    }

    protected function getDetailsForTaxonomyMap(?array $record): array
    {
        return [
            'make' => $record['make'] ?? '',
            'model' => $record['model'] ?? '',
            'fuel_type' => $record['fuel_type'] ?? '',
        ];
    }


    private function imageAlreadyHostedOnServer($image)
    {
        return Media::assetExists($image);
    }

    private function getFillableData(array $vehicle)
    {
        if (empty(self::$offerFillable)) {
            self::$offerFillable = new VehicleOffer()->getFillable();
        }

        $data = collect($vehicle)
            ->only(self::$offerFillable)
            ->map(fn($value, $field) => $this->getMappedValue($field, $value))
            ->toArray();

        $data['make_id'] = $this->getMappedTaxonomy(TaxonomyMap::MAKE, $vehicle['make'], $vehicle);
        $data['model_id'] = $this->getMappedTaxonomy(TaxonomyMap::MODEL, $vehicle['model'], $vehicle, $data['make_id']);
        $data['fuel_type_id'] = $this->getMappedTaxonomy(TaxonomyMap::FUEL_TYPE, $vehicle['fuel_type'], $vehicle);
        $data['body_style_id'] = $this->getMappedTaxonomy(TaxonomyMap::BODY_STYLE, $vehicle['body_style'], $vehicle);
        $data['transmission_id'] = $this->getMappedTaxonomy(
            TaxonomyMap::TRANSMISSION,
            $vehicle['transmission'],
            $vehicle
        );
        $data['drivetrain_id'] = $this->getMappedTaxonomy(TaxonomyMap::DRIVETRAIN, $vehicle['drivetrain'], $vehicle);
        $data['franchise_id'] = $this->getFranchise($vehicle['franchise']);
        $data['dealership_id'] = $this->getDealership($vehicle['dealership']);
        $data['form_id'] = Form::query()
            ->where('name', '=', 'Offer Enquiry')
            ->where('is_active', '=', true)
            ->first()['id'] ?? null;

        $data['type_id'] = $this->getOfferType($vehicle['type'])['id'];
        $data['type'] = $this->getOfferType($vehicle['type'])['slug'];

        return $data;
    }

    private function getFranchise(?string $franchise_name): ?int
    {
        return $this->franchises[$franchise_name] ?? null;
    }
    private function getDealership(?string $dealership_name): ?int
    {
        return $this->dealerships[$dealership_name] ?? null;
    }

    private function getOfferType(?string $type): array
    {
        return $this->types->has(strtolower((string) $type))
            ? $this->types->get(strtolower((string) $type))->toArray()
            : [
                'id' => null,
                'slug' => null,
            ];
    }

    private function getMappedValue($field, $value)
    {
        return match ($field) {
            'featured' => (bool)$value,
            'published' => (bool)$value,
            'price',
            'deposit',
            'battery_range',
            'battery_capacity_kwh',
            'battery_usable_capacity_kwh',
            'battery_charge_time',
            'battery_quick_charge_time',
            'battery_quick_charge_level',
            'term',
            'annual_mileage',
            'customer_deposit',
            'dealer_deposit_contribution',
            'total_credit_amount',
            'option_to_purchase_fee',
            'final_payment',
            'full_price',
            'excess_mileage_charge',
            'monthly_payment' => $this->sanitiseNumber($value),
            'apr' => $this->sanitisePercentage($value),
            default => $value,
        };
    }

    private function sanitiseNumber($value): ?float
    {
        if (empty($value)) {
            return is_numeric($value) ? 0 : null;
        }

        return str_replace(['£', ',', '%'], '', $value);
    }

    private function sanitisePercentage($value): ?float
    {
        $value = str_replace([',', '%'], ['.', ''], $value);

        if (!is_numeric($value)) {
            return null;
        }

        return empty($value) ? 0 : $value;
    }

    private function loadRelationships(): void
    {
        $this->dealerships = Dealership::all()->pluck('id', 'name');
        $this->makes = VehicleMake::all()->pluck('id', 'name');
        $this->types = OfferType::all()->keyBy('slug');
        $this->models = VehicleModel::all()->pluck('id', 'name');
        $this->fuel_types = FuelType::all()->pluck('id', 'name');
        $this->body_styles = BodyStyleType::all()->pluck('id', 'name');
        $this->drivetrains = DrivetrainType::all()->pluck('id', 'name');
        $this->transmissions = TransmissionType::all()->pluck('id', 'name');
        $this->franchises = Franchise::all()->pluck('id', 'name');
    }
}
