<?php

namespace App\Jobs;

use App\Filter;
use Carbon\CarbonInterface;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use Mtc\Filter\Contracts\CustomPatternFilter;
use Mtc\Filter\Contracts\IsFilter;
use Mtc\MercuryDataModels\KeyloopLeaseVehicleVariant;
use Mtc\MercuryDataModels\Traits\FilterIndexIds;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleMake;
use Mtc\MercuryDataModels\VehicleModel;
use Mtc\MercuryDataModels\VehicleTypeProfile;
use Throwable;

class RefreshFilterIndexJob implements ShouldQueue
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    public bool $failOnTimeout = true;

    public function __construct(private readonly ?CarbonInterface $since = null)
    {
        $this->onQueue('sync');
    }

    public function handle(): void
    {
        $vehicle_type_filters = [
            'filters' => Vehicle::class,
            'lease_vehicle_filters' => KeyloopLeaseVehicleVariant::class,
        ];

        $filter_index_class = config('filter.filter_index_model');

        foreach ($vehicle_type_filters as $filter_group => $target_model) {
            $filter_class = app(Filter::class);
            $filter_class->setFilterTargetModel($target_model);
            $filter_class->setConfigKey($filter_group);

            $stockTypes = $this->stockVehicleTypes($target_model);

            $filter_class->getFilters()
                ->reject(fn (IsFilter $filter) => $filter instanceof CustomPatternFilter || !$filter->reindexModel())
                ->each(fn (IsFilter $filter, $type) =>
                    $this->reindexFilter($filter, $type, $filter_index_class, $stockTypes));
        }
    }

    private function reindexFilter(
        IsFilter $filter,
        string $type,
        string $filter_index_class,
        array $stockTypes
    ): void {
        $query = App::make($filter->getModel())->newQuery();

        $isTaxonomy = $this->isTaxonomyFilter($filter);

        if ($isTaxonomy && $this->since !== null) {
            $query->where('updated_at', '>=', $this->since);
        }

        $this->applyStockTypeFilter($query, $filter->getModel(), $stockTypes);

        $query->chunk(250, function ($chunk) use ($type, $filter, $filter_index_class) {
            $chunk->each(fn ($model) => $filter_index_class::index($type, $model, $filter));
        });
    }

    private function isTaxonomyFilter(IsFilter $filter): bool
    {
        return in_array(FilterIndexIds::class, class_uses_recursive($filter), true);
    }

    /**
     * Distinct vehicle types currently held in stock for the given target model.
     * Returns null when the target model doesn't expose a `type` column we can scope on.
     */
    private function stockVehicleTypes(string $target_model): ?array
    {
        if (!in_array($target_model, [Vehicle::class, KeyloopLeaseVehicleVariant::class], true)) {
            return null;
        }

        return App::make($target_model)->newQuery()
            ->whereNotNull('type')
            ->where('type', '<>', '')
            ->distinct()
            ->pluck('type')
            ->all();
    }

    private function applyStockTypeFilter(Builder $query, string $model_class, ?array $stockTypes): void
    {
        if ($stockTypes === null) {
            return;
        }

        if (!in_array($model_class, [VehicleMake::class, VehicleModel::class], true)) {
            return;
        }

        if (empty($stockTypes)) {
            $query->whereRaw('1 = 0');
            return;
        }

        if ($model_class === VehicleMake::class) {
            // Resolve each stock type to its vehicle_makes boolean column via the
            // profile. Only recognised types contribute a flag; an unknown type
            // must not fall back to the car flag here, and a recognised type with
            // no make-flag column (e.g. campervan today) yields null and is skipped.
            $flags = collect($stockTypes)
                ->map(fn (string $stockType) => VehicleTypeProfile::isKnown($stockType)
                    ? VehicleTypeProfile::for($stockType)->filterFlag()
                    : null)
                ->filter()
                ->unique()
                ->values()
                ->all();

            if (empty($flags)) {
                $query->whereRaw('1 = 0');
                return;
            }

            $query->where(function (Builder $subQuery) use ($flags) {
                foreach ($flags as $flag) {
                    $subQuery->orWhere($flag, true);
                }
            });
            return;
        }

        $query->where(function (Builder $subQuery) use ($stockTypes) {
            foreach ($stockTypes as $stockType) {
                $subQuery->orWhere(fn (Builder $q) => $q->ofType($stockType));
            }
        });
    }

    public function failed(?Throwable $exception): void
    {
        Log::error('RefreshFilterIndexJob failed', [
            'exception_class' => $exception ? get_class($exception) : 'null (worker death or reservation race)',
            'message' => $exception?->getMessage(),
            'file' => $exception?->getFile(),
            'line' => $exception?->getLine(),
        ]);
    }
}
