<?php

namespace App;

use App\Facades\Settings;
use App\Filter\Sorting\VehicleAttribute as VehicleAttributeSorting;
use App\Filter\Sorting\VehicleAttributeNumeric as VehicleAttributeNumericSorting;
use Mtc\MercuryDataModels\CatalogOffer;
use Mtc\MercuryDataModels\CatalogOfferRule;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleOffer;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use Mtc\Filter\Contracts\FilterObject;
use App\Http\Resources\VehicleList;
use Mtc\MercuryDataModels\VehicleAttribute;

class VehicleFilter implements FilterObject
{
    private ?Builder $currentQuery = null;
    private array $selectedCatalogOffers = [];
    private bool $catalogOfferResolved = false;
    private array $selectedSrpFeaturedVehicles = [];
    private bool $srpFeaturedResolved = false;
    private array $selectedVehicleOffers = [];
    private bool $vehicleOfferResolved = false;

    /**
     * Set the current query context for catalog offer rule matching
     */
    public function setCurrentQuery(Builder $query): void
    {
        $this->currentQuery = $query;
    }

    /**
     * Instantiate a query for object
     *
     * @return Builder
     */
    public function createQuery(): Builder
    {
        return Vehicle::query()
            ->active()
            ->withRelationshipsForCardView()
            ->when(Settings::get('automotive-vehicle-brand-on-filter-card'), fn ($query) => $query->with('make'));
    }

    /**
     * Fetch results
     *
     * @param Builder $query
     * @return LengthAwarePaginator
     */
    public function getResults(Builder $query): LengthAwarePaginator
    {
        $this->currentQuery = clone $query;
        $pageLimit = Settings::get('filter-results-per-page', Config::get('filter.result_page_limit'));

        $offerCount = count($this->getCatalogOffers());
        if ($offerCount > 0 && $this->getResultCount($query, false) >= $pageLimit) {
            $pageLimit -= $offerCount;
        }

        $vehicleOfferCount = count($this->getVehicleOffers());
        if ($vehicleOfferCount > 0 && $this->getResultCount($query, false) >= $pageLimit) {
            $pageLimit -= $vehicleOfferCount;
        }

        $featuredVehicles = $this->getSrpFeaturedVehicles();
        $featuredCount = count($featuredVehicles);
        if ($featuredCount > 0) {
            $query->whereNotIn('id', array_map(fn ($v) => $v->id, $featuredVehicles));
            $pageLimit -= $featuredCount;
        }

        return $query->paginate(request('perPage', $pageLimit));
    }

    /**
     * Fetch results
     *
     * @param Builder $query
     * @return int
     */
    public function getResultCount(Builder $query, bool $include_extras = true): int
    {
        $this->currentQuery = clone $query;
        $count = $query->count();

        if ($include_extras) {
            $count += count($this->getCatalogOffers());
            $count += count($this->getVehicleOffers());
        }

        return $count;
    }

    /**
     * Format results to response data
     *
     * @param LengthAwarePaginator $results
     * @return JsonResource
     */
    public function format(LengthAwarePaginator $results): JsonResource
    {
        return new VehicleList(
            $results,
            $this->getCatalogOffers(),
            true,
            $this->getSrpFeaturedVehicles(),
            $this->getVehicleOffers(),
        );
    }

    /**
     * Apply filters to query (e.g. when getting values for filters not main object)
     *
     * @param Builder $query
     */
    public function applyFilter(Builder $query): void
    {
        $query->active();
    }

    /**
     * @return bool
     */
    public function catalogOffersEnabled(): bool
    {
        return Settings::get('filter-inject-offers-into-used-car-filter') === true;
    }

    /**
     * @return bool
     */
    private function hasCatalogOffers(): bool
    {
        if (!$this->catalogOffersEnabled()) {
            return false;
        }

        return count($this->getCatalogOffers()) > 0;
    }

    /**
     * @return CatalogOffer[]
     */
    public function getCatalogOffers(): array
    {
        if (!$this->catalogOffersEnabled()) {
            return [];
        }

        if ($this->catalogOfferResolved) {
            return $this->selectedCatalogOffers;
        }

        $this->catalogOfferResolved = true;

        $maxWeighted = (int) Settings::get('catalog-offer-max-weighted', 0);
        $isPageOne = (int) request('page', 1) === 1;

        if ($isPageOne && $maxWeighted > 0) {
            $weightedOffers = CatalogOffer::query()
                ->active()
                ->forVehicles()
                ->where('weight', '>', 0)
                ->with(['rules', 'mediaUses.media'])
                ->orderBy('weight', 'desc')
                ->get();

            foreach ($weightedOffers as $offer) {
                if (count($this->selectedCatalogOffers) >= $maxWeighted) {
                    break;
                }
                if ($this->catalogOfferMatchesQuery($offer)) {
                    $this->selectedCatalogOffers[] = $offer;
                }
            }

            if (!empty($this->selectedCatalogOffers)) {
                return $this->selectedCatalogOffers;
            }
        }

        // Fall back to random selection (page 2+ or when weighted feature is off)
        $offers = CatalogOffer::query()
            ->active()
            ->forVehicles()
            ->with(['rules', 'mediaUses.media'])
            ->inRandomOrder()
            ->get();

        foreach ($offers->shuffle() as $offer) {
            if ($this->catalogOfferMatchesQuery($offer)) {
                $this->selectedCatalogOffers = [$offer];
                return $this->selectedCatalogOffers;
            }
        }

        return [];
    }

    /**
     * @return VehicleOffer[]
     */
    public function getVehicleOffers(): array
    {
        if ($this->vehicleOfferResolved) {
            return $this->selectedVehicleOffers;
        }

        $this->vehicleOfferResolved = true;

        if (!Settings::get('automotive-srp-vehicle-offers-enabled')) {
            return [];
        }

        if (!$this->stockTypeMatchesCurrentSrp()) {
            return [];
        }

        $maxPerPage = min((int) Settings::get('automotive-srp-vehicle-offers-max-per-page', 0), 5);
        if ($maxPerPage <= 0) {
            return [];
        }

        $isPageOne = (int) request('page', 1) === 1;
        $maxWeighted = (int) Settings::get('vehicle-offer-max-weighted', 0);

        $baseQuery = VehicleOffer::query()
            ->active()
            ->withRelationshipsForCardView();

        if ($isPageOne && $maxWeighted > 0) {
            $weightedCandidates = (clone $baseQuery)
                ->where('weight', '>', 0)
                ->orderBy('weight', 'desc')
                ->get();

            foreach ($weightedCandidates as $offer) {
                if (count($this->selectedVehicleOffers) >= min($maxWeighted, $maxPerPage)) {
                    break;
                }
                if ($this->vehicleOfferMatchesQuery($offer)) {
                    $this->selectedVehicleOffers[] = $offer;
                }
            }

            if (!empty($this->selectedVehicleOffers)) {
                return $this->selectedVehicleOffers;
            }
        }

        // Page 1 with no weighted matches, or page 2+: random selection
        $limit = $isPageOne ? $maxPerPage : 1;
        $randomCandidates = (clone $baseQuery)->inRandomOrder()->get();

        foreach ($randomCandidates as $offer) {
            if (count($this->selectedVehicleOffers) >= $limit) {
                break;
            }
            if ($this->vehicleOfferMatchesQuery($offer)) {
                $this->selectedVehicleOffers[] = $offer;
            }
        }

        return $this->selectedVehicleOffers;
    }

    /**
     * Check if a vehicle offer is relevant to the customer's currently-filtered vehicle query.
     *
     * Clones the current vehicle query (which already has all customer filters applied -
     * make via URL slug, multi-make via selections, fuel type, body style, etc.) and adds
     * the offer's own foreign-key constraints. If any vehicles match both, the offer is relevant.
     */
    private function vehicleOfferMatchesQuery(VehicleOffer $offer): bool
    {
        if ($this->currentQuery === null) {
            return true;
        }

        $query = clone $this->currentQuery;

        if ($offer->make_id !== null) {
            $query->where('make_id', $offer->make_id);
        }
        if ($offer->model_id !== null) {
            $query->where('model_id', $offer->model_id);
        }
        if ($offer->fuel_type_id !== null) {
            $query->where('fuel_type_id', $offer->fuel_type_id);
        }
        if ($offer->body_style_id !== null) {
            $query->where('body_style_id', $offer->body_style_id);
        }
        if ($offer->transmission_id !== null) {
            $query->where('transmission_id', $offer->transmission_id);
        }
        if ($offer->drivetrain_id !== null) {
            $query->where('drivetrain_id', $offer->drivetrain_id);
        }

        try {
            return $query->exists();
        } catch (\Illuminate\Database\QueryException $e) {
            Log::error('Vehicle offer relevance check failed', [
                'offer_id' => $offer->id,
                'message' => $e->getMessage(),
            ]);
            return false;
        }
    }

    /**
     * Check if the configured stock type setting matches the current SRP context.
     * - 'used'  = active when the SRP filter is 'used' / 'ex_demo'
     * - 'new'   = active when the SRP filter is 'new' / 'pre_reg'
     * - 'both'  = active on any SRP context
     */
    private function stockTypeMatchesCurrentSrp(): bool
    {
        $stockType = Settings::get('automotive-srp-vehicle-offers-stock-type');

        if ($stockType === 'both') {
            return true;
        }

        if ($stockType !== 'used' && $stockType !== 'new') {
            return false;
        }

        $isNewFilter = $this->detectIsNewFilter();

        return match ($stockType) {
            'used' => $isNewFilter === false,
            'new' => $isNewFilter === true,
            default => false,
        };
    }

    /**
     * Read the is_new filter from request selections.
     *
     * The frontend sends `{type: 'is_new', value: 'used'|'new'|'ex_demo'|'pre_reg'}`
     * via the IsNewFilter mechanism. We read the same selection that scopes the vehicle query.
     *
     * Returns false for used contexts ('used', 'ex_demo'), true for new ('new', 'pre_reg'),
     * or null when no is_new selection is present.
     */
    private function detectIsNewFilter(): ?bool
    {
        $value = collect(request('selections', []))
            ->firstWhere('type', 'is_new')['value'] ?? null;

        return match ($value) {
            'used', 'ex_demo' => false,
            'new', 'pre_reg' => true,
            default => null,
        };
    }

    /**
     * Check if catalog offer rules match vehicles in the current query
     */
    private function catalogOfferMatchesQuery(CatalogOffer $offer): bool
    {
        if ($offer->rules->isEmpty()) {
            return true;
        }

        if ($this->currentQuery === null) {
            return true;
        }

        $query = clone $this->currentQuery;

        foreach ($offer->rules as $rule) {
            $this->applyRuleToQuery($query, $rule);
        }

        try {
            return $query->exists();
        } catch (\Illuminate\Database\QueryException $e) {
            Log::error('Catalog offer rule query failed', [
                'offer_id' => $offer->id,
                'message' => $e->getMessage(),
            ]);
            return false;
        }
    }

    /**
     * Apply a single rule to the query
     */
    private function applyRuleToQuery(Builder $query, CatalogOfferRule $rule): void
    {
        $value = $this->parseRuleValue($rule->value);

        if ($rule->field === 'label') {
            $this->applyLabelRuleToQuery($query, $rule->condition, $value);
            return;
        }

        $field = $this->resolveFieldName($rule->field);

        match ($rule->condition) {
            '=' => $query->where($field, '=', $value),
            '!=' => $query->where($field, '!=', $value),
            '>' => $query->where($field, '>', $value),
            '>=' => $query->where($field, '>=', $value),
            '<' => $query->where($field, '<', $value),
            '<=' => $query->where($field, '<=', $value),
            'is_null' => $query->whereNull($field),
            'exists' => $query->whereNotNull($field),
            'in' => $query->whereIn($field, (array) $value),
            'not_in' => $query->whereNotIn($field, (array) $value),
            default => null,
        };
    }

    private function applyLabelRuleToQuery(Builder $query, string $condition, mixed $value): void
    {
        $ids = (array) $value;

        match ($condition) {
            '=', 'in' => $query->whereHas('labels', fn ($q) => $q->whereIn('labels.id', $ids)),
            '!=', 'not_in' => $query->whereDoesntHave('labels', fn ($q) => $q->whereIn('labels.id', $ids)),
            default => null,
        };
    }

    /**
     * Resolve virtual field names to actual database columns.
     */
    private function resolveFieldName(string $field): string
    {
        return match ($field) {
            'odometer' => Settings::get('automotive-distance_measurement', 'mi') === 'mi'
                ? 'odometer_mi'
                : 'odometer_km',
            'condition' => 'is_new',
            default => $field,
        };
    }

    /**
     * Parse rule value - decode JSON arrays if present
     */
    private function parseRuleValue(mixed $value): mixed
    {
        if (is_string($value) && str_starts_with($value, '[')) {
            $decoded = json_decode($value, true);
            if (json_last_error() === JSON_ERROR_NONE) {
                return $decoded;
            }
        }

        return $value;
    }

    public function getSrpFeaturedVehicles(): array
    {
        if ($this->srpFeaturedResolved) {
            return $this->selectedSrpFeaturedVehicles;
        }

        $this->srpFeaturedResolved = true;

        if (!Settings::get('automotive-srp-featured-vehicles')) {
            return [];
        }

        $isPageOne = (int) request('page', 1) === 1;
        if (!$isPageOne) {
            return [];
        }

        $max = (int) Settings::get('automotive-srp-featured-max', 0);
        if ($max === 0) {
            return [];
        }

        if ($this->currentQuery === null) {
            return [];
        }

        $this->selectedSrpFeaturedVehicles = (clone $this->currentQuery)
            ->where('srp_featured', true)
            ->where('is_reserved', false)
            ->where('is_sold', false)
            ->limit($max)
            ->get()
            ->all();

        return $this->selectedSrpFeaturedVehicles;
    }

    public function sortOptionName(): string
    {
        return 'vehicles';
    }

    public function additionalSortOptions($activeSortOptionName = '')
    {
        $sortOptions = [];
        $sortableAttributes = VehicleAttribute::query()
            ->where('sortable', 1)
            ->get();

        foreach ($sortableAttributes as $sortableAttribute) {
            if ($sortableAttribute->type === 'boolean') {
                $sortOptions[$sortableAttribute->slug] = VehicleAttributeSorting::class;
            } elseif (in_array($sortableAttribute->type, ['integer', 'number'])) {
                $sortOptions[$sortableAttribute->slug . '-asc'] = VehicleAttributeNumericSorting::class;
                $sortOptions[$sortableAttribute->slug . '-desc'] = VehicleAttributeNumericSorting::class;
            }
        }

        return $sortOptions;
    }
}
