<?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 Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Collection;
use Mtc\ContentManager\Facades\Media;
use Mtc\MercuryDataModels\Franchise;
use Mtc\MercuryDataModels\NewCar;
use Mtc\MercuryDataModels\VehicleModel;
use Mtc\MercuryDataModels\VehicleTrim;
use Ramsey\Uuid\Uuid;

class NewCarRepository implements InteractsWithContentSync, AbleToSyncContentElements
{
    use RetrievesFieldData;
    use SavesSeoData;
    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',
    ];

    /**
     * Bulk create new cars for the selected models of a franchise's make.
     *
     * Only models belonging to the franchise's make are created; any others are skipped.
     * Omitted template_id / form_id fall back to the NewCar creation defaults.
     *
     * @param Franchise $franchise
     * @param array<int, int> $modelIds
     * @param array{template_id?: int|null, form_id?: int|null, test_drive_form_id?: int|null, vehicle_type?: string|null} $options
     * @return Collection<int, NewCar>
     */
    public function bulkCreateForFranchise(Franchise $franchise, array $modelIds, array $options = []): Collection
    {
        $models = VehicleModel::query()
            ->where('make_id', $franchise->make_id)
            ->whereIn('id', $modelIds)
            ->get();

        return DB::transaction(fn () => $models->map(fn (VehicleModel $model) => NewCar::query()->create(array_filter([
            'name' => trim(($franchise->make?->name ? $franchise->make->name . ' ' : '') . $model->name),
            'make_id' => $franchise->make_id,
            'model_id' => $model->id,
            'franchise_id' => $franchise->id,
            'vehicle_type' => $options['vehicle_type'] ?? ($model->type ? explode('-', $model->type)[0] : null),
            'template_id' => $options['template_id'] ?? null,
            'form_id' => $options['form_id'] ?? null,
            'test_drive_form_id' => $options['test_drive_form_id'] ?? null,
        ], fn ($value) => !is_null($value)))));
    }

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

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

        $car->fill($request->input());
        $car->slug = $request->input('seo.slug') ?? $car->slug;
        $car->save();

        $this->saveFinanceData($request->input('finance', []), $car);

        if ($request->filled('seo')) {
            $this->saveSeo($car, $request->input('seo', []));
        }

        $this->setImages($request, $car);

        $allMedia = array_unique(array_merge(
            $request->input('listing_image', []),
            $request->input('hero_image', []),
            $request->input('mobile_hero_image', [])
        ));
        Media::removeMediaUsesNotInList($car, $allMedia);

        $car->fuelTypes()->sync($request->input('fuel_types'));

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

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

    /**
     * Create a version snapshot for a bulk field change, mirroring the
     * inactive-snapshot the single-edit save writes. Call while the model
     * holds the new (dirty) field values so the version's `data` captures
     * them. Content is not re-snapshotted (bulk update changes fields, not
     * content). When $makeActive is true the snapshot becomes the active
     * marker and any previous active version is cleared.
     */
    public function saveBulkVersion(NewCar $car, int $author, bool $makeActive = false): void
    {
        if (!$this->hasVersioning()) {
            return;
        }

        if ($makeActive) {
            $car->versions()->update(['is_active' => false]);
        }

        $car->versions()->create([
            'uuid' => Uuid::uuid4(),
            'author_id' => $author,
            'is_active' => $makeActive,
            'data' => $car->only(self::$dataFields),
        ]);
    }

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

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

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

        return $copy;
    }


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

    public function mapTechnicalData(VehicleLookupData $data): array
    {
        return [
            'derivative' => $data->derivative,
            'engine_size_cc' => $data->engine_capacity_cc,
            'drivetrain_id' => $this->getMappedTaxonomy(
                TaxonomyMap::DRIVETRAIN,
                $data->drivetrain,
                $data->toArray()
            ),
            'body_style_id' => $this->getMappedTaxonomy(
                TaxonomyMap::BODY_STYLE,
                $data->body_type,
                $data->toArray()
            ),
            'transmission_id' => $this->getMappedTaxonomy(
                TaxonomyMap::TRANSMISSION,
                $data->transmission,
                $data->toArray()
            ),
            'fuel_type' => $this->getMappedTaxonomy(
                TaxonomyMap::FUEL_TYPE,
                $data->fuel_type,
                $data->toArray()
            )
        ];
    }

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

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

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

    public function importRecord(array $entry): bool
    {
        $newCar = $this->getQuery()
            ->create(Arr::only($entry, new NewCar()->getFillable()));

        if (!empty($entry['hero_image'])) {
            $media = Media::importImageFromUrl($entry['hero_image']);
            Media::setUsesForModel([$media->id], $newCar, ['flags' => ['listing' => true]], false);
        }

        if (!empty($entry['listing_image'])) {
            $media = Media::importImageFromUrl($entry['listing_image']);
            Media::setUsesForModel([$media->id], $newCar, ['flags' => ['hero' => true]], false);
        }

        if (!empty($entry['fuel_types'])) {
            $newCar->fuelTypes()->sync($entry['fuel_types']);
        }

        return $newCar->exists;
    }

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

    public function restore(int $id): bool
    {
        return DB::transaction(function () use ($id) {
            $trashed = NewCar::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 (NewCar::query()->where('slug', $originalSlug)->exists()) {
                throw new \Exception("A vehicle with the original slug '$originalSlug' already exists.");
            }

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

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

        collect($input)
            ->each(fn ($entry) => $car->finance()->updateOrCreate(['id' => $entry['id']], $entry));
    }


    public function exportToRemote(array $selections): array
    {
        return $this->getQuery()
            ->with('mediaUses.media', 'fuelTypes:id')
            ->whereIn('id', $selections)
            ->get()
            ->map(function ($newCar) {
                $newCarData = $newCar->toArray();

                $newCarData['hero_image'] = $newCar->mediaUses
                    ->filter(fn ($mediaUse) => $mediaUse->flags['hero'] ?? null)
                    ->first()
                    ?->media
                    ->getOriginalUrlAttribute();

                $newCarData['listing_image'] = $newCar->mediaUses
                    ->filter(fn ($mediaUse) => $mediaUse->flags['listing'] ?? null)
                    ->first()
                    ?->media
                    ->getOriginalUrlAttribute();

                unset($newCarData['media_uses']);

                $newCarData['fuel_types'] = $newCar->fuelTypes->pluck('id')->toArray();

                return $newCarData;
            })
            ->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,
        ];
    }

    /**
     * @param NewCar $car
     * @param array $data
     * @return void
     * @throws ValidationException
     */
    public function saveTrims(NewCar $car, array $data): void
    {
        $trimRepository = app(VehicleTrimRepository::class);
        $car->trims()->whereNotIn('id', collect($data)->pluck('id'))->delete();

        collect($data)->each(function ($trim, $order) use ($car, $trimRepository) {
            if ($this->isDuplicateTrimName($car, $trim)) {
                // we are trying to add a new trim but the name is a duplicate of an existing trim
                throw ValidationException::withMessages(['Duplicate trim name: ' . $trim['name']]);
            }

            // Find existing trim by ID if numeric
            if (is_numeric($trim['id'] ?? null)) {
                $trim_model = VehicleTrim::find($trim['id']);
            } elseif ($car->id) {
                // Lookup by new_car_id and name if available
                $trim_model = VehicleTrim::query()
                    ->where('new_car_id', $car->id)
                    ->where('name', $trim['name'] ?? null)
                    ->first();
            }
            if (empty($trim_model)) {
                // Fall back to lookup by make_id and model_id
                $trim_model = VehicleTrim::query()
                    ->where('make_id', $car->make_id)
                    ->where('model_id', $car->model_id)
                    ->where('name', $trim['name'] ?? null)
                    ->first();
            }

            // Create new trim if not found
            if (!$trim_model) {
                $trim_model = new VehicleTrim();
            }
            $trim_model->new_car_id = $car->id;

            $trim_model->fill([
                'order' => $order,
                'data' => $trim['data'] ?? [],
                'name' => $trim['name'] ?? null,
                'make_id' => $car->make_id,
                'model_id' => $car->model_id,
                'fuel_types' => $trim['fuel_types'] ?? null,
                'description' => $trim['description'] ?? null,
                'colours' => $trim['colours'] ?? null,
                'transmission_types' => $trim['transmission_types'] ?? null,
                'mpg' => $trim['mpg'] ?? null,
                'emissions' => $trim['emissions'] ?? null,
                'engine_size' => $trim['engine_size'] ?? null,
                'battery_size' => $trim['battery_size'] ?? null,
                'range_miles' => $trim['range_miles'] ?? null,
                'bhp' => $trim['bhp'] ?? null,
                'ps' => $trim['ps'] ?? null,
                'active' => $trim['active'] ?? null,
                'zero_to_sixty' => $trim['zero_to_sixty'] ?? null,
                'zero_to_sixty_two' => $trim['zero_to_sixty_two'] ?? null,
                'doors' => $trim['doors'] ?? null,
                'seats' => $trim['seats'] ?? null,
                'battery_charge_time' => $trim['battery_charge_time'] ?? null,
                'battery_quick_charge_time' => $trim['battery_quick_charge_time'] ?? null,
                'wheelbase' => $trim['wheelbase'] ?? null,
                'cap_id' => $trim['cap_id'] ?? null,
                'full_price' => $trim['full_price'] ?? null,
            ])->save();

            if (empty($trim['image'])) {
                $trim_model->mediaUses()->where('primary', true)->delete();
            } else {
                $trim_model->mediaUses()
                    ->where('primary', true)
                    ->whereNotIn('media_id', $trim['image'])
                    ->delete();

                Media::setUsesForModel(
                    $trim['image'],
                    $trim_model,
                    ['primary' => true],
                    false
                );
            }

            if (empty($trim['listing_image'])) {
                $trim_model->mediaUses()->where('primary', false)->delete();
            } else {
                $trim_model->mediaUses()
                    ->where('primary', false)
                    ->whereNotIn('media_id', $trim['listing_image'])
                    ->delete();

                Media::setUsesForModel(
                    $trim['listing_image'],
                    $trim_model,
                    ['flags' => ['listing' => true]],
                    false
                );
            }

            if (!empty($trim['content'])) {
                $trimRepository->saveContent($trim_model, $trim['content']);
            }

            collect($trim['extra'] ?? [])
                ->filter(fn ($attribute) => isset($attribute['value']) && empty($attribute['readonly']))
                ->each(fn ($attribute) => $this->saveAttributeValue($trim_model, $attribute));
        });
    }

    private function isDuplicateTrimName(NewCar $car, array $trim): bool
    {
        return VehicleTrim::query()
            ->where('new_car_id', $car->id)
            ->where('name', '=', $trim['name'])
            ->when(is_numeric($trim['id'] ?? null), fn ($query) => $query->where('id', '!=', $trim['id']))
            ->exists();
    }

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

    private function setImages(Request $request, NewCar $car): void
    {
        $imageFlagsMap = [];

        $inputs = [
            'listing_image' => 'listing',
            'hero_image' => 'hero',
            'mobile_hero_image' => 'mobile_hero',
        ];

        // Collect flags for each unique image
        foreach ($inputs as $inputKey => $flagName) {
            $imageIds = $request->input($inputKey, []);

            foreach ($imageIds as $imageId) {
                if (!isset($imageFlagsMap[$imageId])) {
                    $imageFlagsMap[$imageId] = [];
                }
                $imageFlagsMap[$imageId][$flagName] = true;
            }
        }

        // Now apply each image with its combined flags
        foreach ($imageFlagsMap as $imageId => $flags) {
            Media::setUsesForModel([$imageId], $car, ['flags' => $flags], false);
        }
    }

    public function updateOrder(array $orderedIds): bool
    {
        return DB::transaction(function () use ($orderedIds) {
            collect($orderedIds)
                ->each(fn ($id, $index) => $this->getQuery()
                    ->where('id', $id)
                    ->update(['order' => $index]));

            return true;
        });
    }
}
