<?php

namespace App;

use App\Contracts\AbleToSyncContentElements;
use App\Contracts\InteractsWithContentSync;
use App\Facades\Settings;
use App\Modules\Lookup\Contracts\VehicleLookupData;
use App\Traits\EnsuresVehicleAttribute;
use App\Traits\MapsTaxonomies;
use App\Traits\RetrievesFieldData;
use App\Traits\SavesContent;
use App\VehicleSpec\Contracts\VehicleOptionalEquipmentItem;
use App\VehicleSpec\Contracts\VehicleStandardEquipmentItem;
use App\VehicleSpec\Contracts\VehicleTechnicalDataItem;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Mtc\ContentManager\Facades\Media;
use Mtc\MercuryDataModels\Template;
use Mtc\MercuryDataModels\VehicleOffer;
use Mtc\MercuryDataModels\VehicleOfferFinance;
use Mtc\MercuryDataModels\VehicleSpecType;
use Mtc\MercuryDataModels\VehicleStandardEquipment;
use Mtc\MercuryDataModels\VehicleTechnicalData;
use Mtc\VehicleLookup\VehicleLookupService;

class OfferRepository implements InteractsWithContentSync, AbleToSyncContentElements
{
    use RetrievesFieldData;
    use SavesSeoData;
    use SavesVehicleSpecTypeData;
    use SavesContent;
    use MapsTaxonomies;
    use EnsuresVehicleAttribute;

    protected static array $dataFields = [
        'make_id',
        'model_id',
        'template_id',
        'type_id',
        'form_id',
        'derivative',
        'price',
        'deposit',
        'seo',
        'franchise_id',
        'engine_size_cc',
        'colour',
        'battery_range',
        'battery_capacity_kwh',
        'battery_usable_capacity_kwh',
        'battery_charge_time',
        'battery_quick_charge_time',
    ];

    /**
     * Save Page
     *
     * @param VehicleOffer $offer
     * @param Request $request
     * @param int $author
     * @return void
     */
    public function save(VehicleOffer $offer, Request $request, int $author)
    {
        DB::beginTransaction();

        $this->dropOrphanedContent($offer, $this->getFlatContentElementIds($request->input('content')));
        collect($request->input('content'))
            ->each(fn($element, $index) => $this->saveContentElement($offer, $element, $index));

        $offer->fill($request->input());
        $this->setFuelTypes($offer, $request->input('fuel_type_id'));
        $offer->slug = $request->input('seo.slug') ?? $offer->slug;
        $offer->key_features = $this->transformStringToArray($request->input('key_features'));
        $offer->standard_spec = $this->transformStringToArray($request->input('standard_spec'));
        $offer->technical_spec = $this->transformStringToArray($request->input('technical_spec'));
        $offer->new_car_type = strtolower((string) $request->input('new_car_type'));
        $offer->save();
        if ($request->filled('seo')) {
            $this->saveSeo($offer, $request->input('seo', []));
        }
        Media::setUsesForModel($request->input('media', []), $offer);
        Media::setUseOrdering(array_flip($request->input('media', [])), $offer);
        $primary_media = $request->input('primary_media');
        // Fall back to first use if none set explicitly
        if (empty($primary_media) && $request->filled('media')) {
             $primary_media = collect($request->input('media', []))->first();
        }
        Media::setPrimaryUse($request->input('media', []), $primary_media, $offer);

        $this->syncFeatures($offer, collect($request->input('features', [])));
        $this->syncEquipment($offer, collect($request->input('equipment', [])));
        $this->syncSpecs($offer, collect($request->input('specs', [])));
        $this->saveFinanceData($request->input('finance', []), $offer);

        collect($request->input('extra', []))
            ->filter(fn($attribute) => isset($attribute['value']) && empty($attribute['readonly']))
            ->each(fn($attribute) => $this->saveAttributeValue($offer, $attribute));

        if ($this->hasVersioning()) {
            $this->saveVersion($offer, $request->input('content', []), $author);
        }
        DB::commit();
    }

    public function copy(VehicleOffer $origin, Request $request): VehicleOffer
    {
        $origin->load([
            'finance',
            'content.subContent.subContent.subContent',
        ]);

        /** @var VehicleOffer $copy */
        $copy = VehicleOffer::query()
            ->create($origin->toArray());

        $origin->finance
            ->each(fn(VehicleOfferFinance $financeExample) => $copy->finance()->create($financeExample->toArray()));

        if ($request->input('with_content')) {
            $this->copyContent($copy, $origin->content);
        }

        return $copy;
    }

    public function restore(int $id): bool
    {
        return DB::transaction(function () use ($id) {
            $trashed = VehicleOffer::query()
                ->onlyTrashed()
                ->findOrFail($id);

            // Extract the original slug
            $originalSlug = preg_replace('/--trashed-[A-Za-z0-9]+$/', '', (string) $trashed->slug);

            // Check if a page with the original slug exists
            if (VehicleOffer::query()->where('slug', $originalSlug)->exists()) {
                throw new \Exception("An offer with the original slug '$originalSlug' already exists.");
            }

            $trashed->restore();
            $trashed->update(['slug' => $originalSlug]);
            return true;
        });
    }


    private function saveFinanceData(mixed $input, VehicleOffer $offer): void
    {
        $offer->finance()
            ->whereNotIn('id', collect($input)->pluck('id')->filter())
            ->delete();

        collect($input)
            ->each(fn($entry) => isset($entry['id'])
                ? $offer->finance()->updateOrCreate(['id' => $entry['id']], $entry)
                : $offer->finance()->create($entry));
    }

    public function hasVersioning(): bool
    {
        return Settings::get('offers-versioning-enabled') === true
            && TierHelper::isAllowed(tenant('tier'), Tier::STANDARD->value);
    }

    protected function elementParentColumnName(): string
    {
        return 'offer_id';
    }

    protected function contentOwnerType(): string
    {
        return 'offer-version';
    }

    protected function getQuery(): Builder
    {
        return VehicleOffer::query();
    }

    private function transformStringToArray($value, string $separator = ',')
    {
        return is_array($value)
            ? $value
            : collect(explode($separator, (string) $value))
                ->map(trim(...))
                ->filter(fn($item) => !empty($item))
                ->values()
                ->toArray();
    }

    public function mapTechnicalData(VehicleLookupData $data): array
    {
        return [
            'derivative' => $data->derivative,
            'engine_size_cc' => $data->engine_capacity_cc,
            'fuel_type_id' => $this->getMappedTaxonomy(
                TaxonomyMap::FUEL_TYPE,
                $data->fuel_type,
                $data->toArray()
            ),
            'drivetrain_id' => $this->getMappedTaxonomy(
                TaxonomyMap::DRIVETRAIN,
                $data->drivetrain,
                $data->toArray()
            ),
            'body_style_id' => $this->getMappedTaxonomy(
                TaxonomyMap::BODY_STYLE,
                $data->body_type ?? $data->body_style ?? null,
                $data->toArray()
            ),

            'door_count' => $data->doors,
            'seats' => $data->seats,
            'transmission_id' => $this->getMappedTaxonomy(
                TaxonomyMap::TRANSMISSION,
                $data->transmission,
                $data->toArray()
            ),
            'standard_equipment' => $data->standard_equipment ?? null,
            'optional_equipment' => $data->optional_equipment ?? null,
            'technical_data' => $data->technical_data ?? null,
        ];
    }

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

    public function syncLookupEquipment(
        VehicleOffer $offer,
        $equipment,
        $type = VehicleSpecType::STANDARD_EQUIPMENT
    ): void {
        $updatedEquipmentIds = collect($equipment)->map(function (
            VehicleStandardEquipmentItem|VehicleOptionalEquipmentItem $item
        ) use (
            $offer,
            $type
        ) {
            $equipment = VehicleStandardEquipment::query()->updateOrCreate(
                [
                    'vehicle_id' => $offer->id,
                    'vehicle_type' => 'offer',
                    'description' => $item->description,
                ],
                [
                    'type' => $type,
                    'code' => $item->code,
                    'category' => $item->category_description,
                    'price' => $item->price ?? 0,
                    'vat_amount' => $item->vat ?? 0,
                    'updated_at' => now()
                ]
            );
            return $equipment->id;
        })->toArray();

        VehicleStandardEquipment::query()
            ->where('vehicle_id', $offer->id)
            ->where('vehicle_type', 'offer')
            ->where('type', $type)
            ->whereNotIn('id', $updatedEquipmentIds)
            ->delete();
    }

    public function syncTechnicalData(VehicleOffer $offer, $technicalData): void
    {
        $updatedTechnicalDataIds = collect($technicalData)->map(function (VehicleTechnicalDataItem $item) use ($offer) {
            $technicalData = VehicleTechnicalData::query()->updateOrCreate([
                'vehicle_id' => $offer->id,
                'vehicle_type' => 'offer',
                'description' => $item->description,
                'category' => $item->category
            ], [
                'code' => $item->code,
                'value' => $item->value,
            ]);

            return $technicalData->id;
        })->toArray();

        VehicleTechnicalData::query()
            ->where('vehicle_id', $offer->id)
            ->where('vehicle_type', 'offer')
            ->whereNotIn('id', $updatedTechnicalDataIds)
            ->delete();
    }

    public function saveLookupData(
        VehicleOffer $offer,
        Request $request,
        VehicleLookupService $lookupService,
    ): void {
        if (!$request->filled('variant')) {
            return;
        }

        $column = match ($request->input('variant_type')) {
            'cap' => 'cap_id',
            'auto-trader' => 'auto_trader_id',
            default => null,
        };
        if (empty($column)) {
            return;
        }

        $variant = $request->input('variant');
        if (!$lookupService->hasActiveDriver()) {
            return;
        }

        $offerData = $this->mapTechnicalData($lookupService->getTechnicalData($variant));
        $offerData[$column] = $variant;
        $offerData['make_id'] = $request->input('make_id');
        $offerData['model_id'] = $request->input('model_id');
        $offer->fill($offerData);
        $offer->save();

        if (!empty($offerData['standard_equipment'])) {
            $this->syncLookupEquipment($offer, $offerData['standard_equipment']);
        }
        if (!empty($offerData['optional_equipment'])) {
            $this->syncLookupEquipment(
                $offer,
                $offerData['optional_equipment'],
                VehicleSpecType::OPTIONAL_EQUIPMENT
            );
        }
        if (!empty($offerData['technical_data'])) {
            $this->syncTechnicalData($offer, $offerData['technical_data']);
        }

        $this->storeUnmappedTaxonomy($offer);
    }

    private function setFuelTypes(VehicleOffer $offer, int|array|null $fuel_types): void
    {
        if (Settings::get('offers-multiple-fuel-types')) {
            $offer->fuel_type_id = null;
            $offer->fuelTypes()->sync($fuel_types);
        }
    }

    public function importRecord(array $entry): bool
    {
        $offer = $this->getQuery()
            ->updateOrCreate(
                ['slug' => $entry['slug']],
                Arr::only($entry, new VehicleOffer()->getFillable())
            );

        if (!empty($entry['media_uses'])) {
            $imagesIds = [];
            foreach ($entry['media_uses'] as $image) {
                $imagesIds[] = Media::importImageFromUrl($image)->id;
            }
            Media::setUsesForModel($imagesIds, $offer);
        }

        if (!empty($entry['template'])) {
            $offer->template_id = Template::query()
                ->where('slug', $entry['template'])
                ->first()?->id;
        }

        $offer->save();

        if (!empty($offer->template_id)) {
            $this->syncContentWithTemplate($offer->id);
        }

        $this->syncFeatures($offer, collect($entry['features']));
        $this->syncEquipment($offer, collect($entry['equipment']));
        $this->syncSpecs($offer, collect($entry['specs']));

        return $offer->exists;
    }

    public function canBeImported(array $entry): bool
    {
        return !$this->getQuery()
            ->where('slug', $entry['slug'])
            ->exists();
    }

    public function exportToRemote(array $selections): array
    {
        return $this->getQuery()
            ->with([
                'mediaUses.media',
                'equipment',
                'specs',
                'features',
                'template',
            ])
            ->whereIn('id', $selections)
            ->get()
            ->map(function ($offer) {
                $offerData = $offer->toArray();

                unset($offerData['template_id']);
                $offerData['template'] = $offer->template?->slug;

                $offerData['media_uses'] = $offer->mediaUses
                    ->map(fn($mediaUse) => $mediaUse->media?->getOriginalUrlAttribute())
                    ->filter()
                    ->toArray();

                $offerData['features'] = [];
                if (!empty($offer->features)) {
                    foreach ($offer->features as $feature) {
                        $offerData['features'][] = [
                            'name' => $feature->name,
                            'id' => bin2hex(random_bytes(16))
                        ];
                    }
                }

                $offerData['specs'] = [];
                if (!empty($offer->specs)) {
                    foreach ($offer->specs as $standardSpec) {
                        $offerData['specs'][] = [
                            'category' => $standardSpec->category,
                            'code' => $standardSpec->code,
                            'description' => $standardSpec->description,
                            'value' => $standardSpec->value,
                            'price' => $standardSpec->price,
                            'id' => bin2hex(random_bytes(16))
                        ];
                    }
                }

                $offerData['equipment'] = [];
                if (!empty($offer->equipment)) {
                    foreach ($offer->equipment as $equipment) {
                        $offerData['equipment'][] = [
                            'type' => $equipment->type,
                            'code' => $equipment->code,
                            'category' => $equipment->category,
                            'description' => $equipment->description,
                            'value' => $equipment->value,
                            'price' => $equipment->price,
                            'id' => bin2hex(random_bytes(16))
                        ];
                    }
                }

                return $offerData;
            })
            ->toArray();
    }

    public function checkImportEntryValidity(array $dataEntry, array $allEntries): array
    {
        $errors = [];

        if (empty($dataEntry['slug'])) {
            $errors[] = __('validation.import_slug_missing', ['slug' => $dataEntry['slug']]);
        } elseif ($this->getQuery()->where('slug', $dataEntry['slug'])->exists()) {
            $errors[] = __('validation.import_slug_taken', ['slug' => $dataEntry['slug']]);
        }

        return [
            'data' => $dataEntry,
            'errors' => $errors,
        ];
    }
}
