<?php

namespace App;

use App\Facades\Feature;
use App\Facades\Settings;
use App\Http\Resources\VehicleList;
use App\Modules\PlaceholderImages\ImaginStudio;
use App\Services\MeilisearchService;
use App\Traits\CacheObject;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Pagination\LengthAwarePaginator as Paginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Log;
use Mtc\Filter\Contracts\FilterObject;
use Mtc\Filter\Contracts\FilterSeoContract;
use Mtc\Filter\Contracts\IsFilter;
use Mtc\MercuryDataModels\Filters\IndexedFilter;
use Mtc\MercuryDataModels\Services\LocatingService;
use Mtc\MercuryDataModels\Vehicle;
use Mtc\MercuryDataModels\VehicleFinance;

class MeilisearchFilter extends Filter
{
    use CacheObject;

    protected MeilisearchService $meilisearch;

    protected array $meilisearchFilters = [];

    protected ?array $cachedSearchResults = null;

    protected ?array $cachedFacets = null;

    protected ?string $geoFilter = null;

    /**
     * Cache for facet distribution per filter type (excludes own filter)
     */
    protected array $facetsByFilterType = [];

    /**
     * Mapping from filter type to Meilisearch facet attribute name
     */
    protected array $facetAttributeMapping = [
        'make' => 'make_slug',
        'model' => 'model_slug',
        'body_type' => 'body_style_slug',
        'fuel_type' => 'fuel_type_slug',
        'transmission' => 'transmission_slug',
        'colour' => 'colour_slug',
        'location' => 'dealership_slug',
        'franchise' => 'franchise_id',
        'vehicle_type' => 'type',
        'vehicle_category' => 'vehicle_category',
        'is_new' => 'is_new',
        'labels' => 'label_ids',
        'features' => 'feature_ids',
        'stock_status' => 'stock_status',
        'trim' => 'trim_slug',
        'age' => 'manufacture_year',
    ];

    public function __construct(Request $request, FilterObject $product_handler, FilterSeoContract $seo)
    {
        parent::__construct($request, $product_handler, $seo);
        $this->meilisearch = App::make(MeilisearchService::class);
        // Initialize query to avoid "must not be accessed before initialization" errors
        // when parent methods that use $this->query are called
        $this->query = $this->product_handler->createQuery();
    }

    public function handle(): array
    {
        $this->request = request();
        if ($this->request->has('slug')) {
            $this->filter_url_elements = explode('/', $this->request->input('slug'));
            $this->matchSortFromUrlElements();
            $this->matchSelectedFilters();
            $this->matchCustomSelections();
            $this->checkForSearchTerm();
        } else {
            $this->selections = $this->groupSelectionsByType($this->request->input('selections') ?? []);
            $this->matchSortFromAjaxRequest();
        }

        $this->buildMeilisearchFilters();
        $this->applySortBasedFilters();

        $results = $this->getResults();

        return [
            'results' => $results,
            'filters' => $this->getCachedFilterResults(),
            'sort_options' => $this->getSortOptions(),
            'sort_by' => $this->active_sort_option_name,
            'selections' => $this->getSelectionList(),
            'url' => $this->getPageUrl(),
            'seo' => $this->fillSeo(),
            'finance_example' => $this->getRepresentativeFinance($results),
            'imagin_studio_base_url' => Feature::isEnabled('imagin-studio-placeholders')
                ? (new ImaginStudio())->getBaseUrl()
                : null,
            'load_more' => [
                'enabled' => Settings::get('automotive-enable_load_more', false),
                'text' => Settings::get('automotive-load_more_text'),
                'previous_text' => Settings::get('automotive-load_more_previous_text'),
            ]
        ];
    }

    protected function buildMeilisearchFilters(): void
    {
        $this->meilisearchFilters = [
            'is_published' => true,
            'is_sold' => false,
        ];

        foreach ($this->selections as $type => $values) {
            if (empty($values)) {
                continue;
            }

            // Handle postcode filter separately as it uses geo search
            if ($type === 'postcode') {
                $this->buildGeoFilter($values);
                continue;
            }

            $filterMapping = $this->getMeilisearchFilterMapping($type, $values);
            if ($filterMapping !== null) {
                $this->meilisearchFilters = array_merge($this->meilisearchFilters, $filterMapping);
            }
        }
    }

    protected function buildGeoFilter(array $values): void
    {
        try {
            $postcode = $values[0] ?? null;
            if (!$postcode) {
                return;
            }

            $location = App::make(LocatingService::class)->locate($postcode);
            if ($location->lat() && $location->lng()) {
                $distance = $this->getDistanceSelection();
                // Convert miles to meters (1 mile = 1609.34 meters)
                $distanceMeters = $distance * 1609.34;
                $this->geoFilter = "_geoRadius({$location->lat()}, {$location->lng()}, {$distanceMeters})";
            }
        } catch (\Exception $e) {
            Log::debug('Meilisearch geo filter error: ' . $e->getMessage());
        }
    }

    protected function getDistanceSelection(): int
    {
        $distance = 200; // Default distance in miles

        if (isset($this->selections['distance']) && !empty($this->selections['distance'])) {
            $distance = (int) $this->selections['distance'][0];
        }

        return $distance;
    }

    protected function getMeilisearchFilterMapping(string $type, array $values): ?array
    {
        return match ($type) {
            'make' => ['make_slug' => $values],
            'model' => ['model_slug' => $values],
            'body_type' => ['body_style_slug' => $values],
            'fuel_type' => ['fuel_type_slug' => $values],
            'transmission' => ['transmission_slug' => $values],
            'colour' => ['colour_slug' => $values],
            'location' => ['dealership_slug' => $values],
            'franchise' => ['franchise_id' => array_map('intval', $values)],
            'vehicle_type' => ['type' => $values],
            'vehicle_category' => ['vehicle_category' => $values],
            'is_new' => ['is_new' => in_array('new', $values)],
            'is_vat_applicable' => ['is_vat_applicable' => in_array('true', $values) || in_array(true, $values)],
            'labels' => ['label_ids' => array_map('intval', $values)],
            'features' => ['feature_ids' => array_map('intval', $values)],
            'stock_status' => ['stock_status' => $values],
            'trim' => ['trim_slug' => $values],
            'price_min' => ['price' => ['min' => (int) $values[0]]],
            'price_max' => ['price' => ['max' => (int) $values[0]]],
            'price_monthly_min' => ['monthly_price' => ['min' => (int) $values[0]]],
            'price_monthly_max' => ['monthly_price' => ['max' => (int) $values[0]]],
            'mileage_min' => $this->getMileageFilter('min', $values),
            'mileage_max' => $this->getMileageFilter('max', $values),
            'manufacture_year_min' => ['manufacture_year' => ['min' => (int) $values[0]]],
            'manufacture_year_max' => ['manufacture_year' => ['max' => (int) $values[0]]],
            'engine_size_min' => ['engine_size_cc' => ['min' => (int) $values[0]]],
            'engine_size_max' => ['engine_size_cc' => ['max' => (int) $values[0]]],
            'doors_min' => ['door_count' => ['min' => (int) $values[0]]],
            'doors_max' => ['door_count' => ['max' => (int) $values[0]]],
            'seats_min' => ['seat_count' => ['min' => (int) $values[0]]],
            'seats_max' => ['seat_count' => ['max' => (int) $values[0]]],
            'co2' => ['co2' => ['max' => (int) $values[0]]],
            default => null,
        };
    }

    protected function getMileageFilter(string $bound, array $values): array
    {
        $field = Settings::get('automotive-distance_measurement') === 'mi'
            ? 'odometer_mi'
            : 'odometer_km';

        return [$field => [$bound => (int) $values[0]]];
    }

    protected function getMeilisearchSort(): array
    {
        return match ($this->active_sort_option_name) {
            'latest' => ['created_at:desc'],
            'oldest' => ['created_at:asc'],
            'price-desc' => ['price:desc'],
            'price-asc' => ['price:asc'],
            'monthly-price-desc' => ['monthly_price:desc'],
            'monthly-price-asc' => ['monthly_price:asc'],
            'mileage-desc' => Settings::get('automotive-distance_measurement') === 'mi'
                ? ['odometer_mi:desc']
                : ['odometer_km:desc'],
            'mileage-asc' => Settings::get('automotive-distance_measurement') === 'mi'
                ? ['odometer_mi:asc']
                : ['odometer_km:asc'],
            'age-desc' => ['manufacture_year:desc'],
            'age-asc' => ['manufacture_year:asc'],
            default => ['created_at:desc'],
        };
    }

    protected function applySortBasedFilters(): void
    {
        // When sorting by monthly price, exclude vehicles without monthly price
        if (in_array($this->active_sort_option_name, ['monthly-price-desc', 'monthly-price-asc'])) {
            $this->meilisearchFilters['monthly_price'] = ['min' => 1];
        }
    }

    protected function getResults(): JsonResource
    {
        $pageLimit = Settings::get('filter-results-per-page', Config::get('filter.result_page_limit', 24));
        $perPage = request('perPage', $pageLimit);
        $page = (int) request('page', 1);
        $offset = ($page - 1) * $perPage;

        $searchTerm = $this->selections['search'][0] ?? null;

        $searchResults = $this->meilisearch->searchVehicles(
            $this->meilisearchFilters,
            $this->getMeilisearchSort(),
            $perPage,
            $offset,
            $searchTerm,
            $this->geoFilter
        );

        $this->cachedSearchResults = $searchResults;

        $vehicleIds = collect($searchResults['hits'])->pluck('id')->toArray();

        if (empty($vehicleIds)) {
            $paginator = new Paginator(collect([]), $searchResults['total'], $perPage, $page);
            return new VehicleList($paginator);
        }

        $vehicles = Vehicle::query()
            ->whereIn('id', $vehicleIds)
            ->withRelationshipsForCardView()
            ->when(Settings::get('automotive-vehicle-brand-on-filter-card'), fn($query) => $query->with('make'))
            ->get()
            ->sortBy(function ($vehicle) use ($vehicleIds) {
                return array_search($vehicle->id, $vehicleIds);
            })
            ->values();

        $paginator = new Paginator($vehicles, $searchResults['total'], $perPage, $page);

        return new VehicleList($paginator);
    }

    protected function getCachedFilterResults(): array
    {
        if (!empty($this->selections) && count($this->request->input('selections', [])) > 2) {
            return $this->getFilterResults();
        }

        $append = collect($this->request->input('selections', []))
            ->map(fn($selection) => $selection['type'] . '='
                . (is_array($selection['value']) ? implode('|', $selection['value']) : $selection['value']))
            ->implode('-');

        return $this->cache('meilisearch-filter-results' . $append, 15, fn() => $this->getFilterResults());
    }

    protected function getFilterResults(bool $prependAny = false): array
    {
        $input = $this->request->input();
        $filters = $this->getFilters()
            ->when(
                !empty($input['only_filters']),
                fn($all) => $all->filter(fn($filter, $name) => in_array($name, $input['only_filters'] ?? [], true))
            )
            ->map(fn(IsFilter $filter, $name) => $filter->format($this->retrieveSingleFilterResults($filter, $name)))
            ->filter()
            ->map(function (array $filterGroup, $type) use ($prependAny) {
                $filterGroup['results'] = collect($filterGroup['results'])
                    ->map(function ($entry) use ($type) {
                        if (is_array($entry)) {
                            $valueField = $type === 'price' ? 'value' : 'id';
                            $entry['selected'] = isset($this->selections[$type])
                                && in_array($entry[$valueField], $this->selections[$type]);
                        }
                        return $entry;
                    })
                    ->when($prependAny, fn($group) => $group->prepend([
                        'id' => 0,
                        'name' => __('labels.any'),
                    ]))
                    ->values();
                return $filterGroup;
            })
            ->toArray();

        return $this->groupRangeFilters($filters);
    }

    /**
     * Override to use Meilisearch facet counts instead of database queries.
     */
    protected function retrieveSingleFilterResults(IsFilter $filter, string $filter_name): Collection
    {
        $limit = in_array($filter_name, $this->request->input('expanded', []), true)
            ? 0
            : $this->config['filter_limit'];

        // Get the base results from the indexed filter or parent implementation
        if (Feature::isEnabled('indexed-search-page') && $filter instanceof IndexedFilter) {
            if (!empty($this->fullIndex[$filter->filterType()])) {
                $results = $this->fullIndex[$filter->filterType()];
            } else {
                $results = $filter->getIndexedResults(
                    $filter->filterType(),
                    $limit,
                    $this->selections ?? []
                )->map(function ($entry) {
                    $entry->id = $entry->filter_id;
                    return $entry;
                });
            }
        } else {
            // Fall back to parent implementation for non-indexed filters
            $results = $filter->getResults(
                function ($query) use ($filter_name) {
                    $this->applyForFilters($query, $filter_name);
                },
                $limit,
                $this->selections[$filter_name] ?? []
            );
        }

        // Merge Meilisearch facet counts into results
        return $this->mergeFacetCounts($results, $filter_name, $filter);
    }

    /**
     * Merge Meilisearch facet counts into filter results.
     * Excludes the current filter from the query to show accurate counts for each option.
     * Results are sorted by count descending to show most relevant options first.
     */
    protected function mergeFacetCounts(Collection $results, string $filterName, IsFilter $filter): Collection
    {
        $facetAttribute = $this->facetAttributeMapping[$filterName] ?? null;

        if (!$facetAttribute) {
            return $results;
        }

        // Get facets with current filter excluded so counts reflect "what if I select this option"
        $facets = $this->getFacetDistributionForFilter($filterName);
        $facetCounts = $facets[$facetAttribute] ?? [];

        if (empty($facetCounts)) {
            return $results;
        }

        // Get the ID attribute used by this filter (could be 'slug', 'id', 'colour', etc.)
        $idAttribute = $filter->getIdAttribute();

        return $results->map(function ($item) use ($facetCounts, $idAttribute, $facetAttribute, $filterName) {
            // Handle both array and object results
            $isArray = is_array($item);

            // Get the value to match against facet distribution
            $matchValue = $this->getFacetMatchValue($item, $idAttribute, $facetAttribute, $filterName);

            // Get count from Meilisearch facet distribution
            $count = $facetCounts[$matchValue] ?? 0;

            // Set result_count on object or array
            if ($isArray) {
                $item['result_count'] = $count;
                $item['count'] = $count;
            } else {
                $item->result_count = $count;
            }

            return $item;
        })->sortByDesc(function ($item) {
            return is_array($item) ? ($item['result_count'] ?? 0) : ($item->result_count ?? 0);
        })->values();
    }

    /**
     * Get the value to match against Meilisearch facet distribution.
     */
    protected function getFacetMatchValue($item, string $idAttribute, string $facetAttribute, string $filterName): mixed
    {
        $isArray = is_array($item);

        // Helper to get value from array or object
        $getValue = function (string $key) use ($item, $isArray) {
            if ($isArray) {
                return $item[$key] ?? null;
            }
            return $item->{$key} ?? null;
        };

        // For colour filter with colour mapping, the Colour model has 'colour' attribute
        // but Meilisearch uses slugified colour names in 'colour_slug'
        if ($filterName === 'colour') {
            $colourName = $getValue('colour') ?? $getValue($idAttribute);
            if ($colourName) {
                return \Illuminate\Support\Str::slug($colourName);
            }
        }

        // For slug-based facets, use the slug attribute
        if (str_ends_with($facetAttribute, '_slug')) {
            return $getValue('slug') ?? $getValue($idAttribute);
        }

        // For ID-based facets (like franchise_id, label_ids), use the ID
        if (str_ends_with($facetAttribute, '_id') || str_ends_with($facetAttribute, '_ids')) {
            return $getValue('id') ?? $getValue($idAttribute);
        }

        // For other facets (like 'type', 'manufacture_year'), use the ID attribute value
        return $getValue($idAttribute) ?? $getValue('id');
    }

    /**
     * Get facet distribution for a specific filter, excluding that filter's selection.
     * This allows showing accurate counts for each filter option.
     */
    protected function getFacetDistributionForFilter(string $filterName): array
    {
        if (!isset($this->facetsByFilterType[$filterName])) {
            $facetAttribute = $this->facetAttributeMapping[$filterName] ?? null;

            if (!$facetAttribute) {
                return [];
            }

            // Build filters excluding the current filter type
            $filtersWithoutCurrent = $this->buildMeilisearchFiltersExcluding($filterName);
            $searchTerm = $this->selections['search'][0] ?? null;

            $this->facetsByFilterType[$filterName] = $this->meilisearch->getFacets(
                $filtersWithoutCurrent,
                [$facetAttribute],
                $searchTerm,
                $this->geoFilter
            );
        }

        return $this->facetsByFilterType[$filterName];
    }

    /**
     * Build Meilisearch filters excluding a specific filter type.
     */
    protected function buildMeilisearchFiltersExcluding(string $excludeFilterType): array
    {
        $filters = [
            'is_published' => true,
            'is_sold' => false,
        ];

        foreach ($this->selections as $type => $values) {
            if (empty($values)) {
                continue;
            }

            // Skip the excluded filter type
            if ($type === $excludeFilterType) {
                continue;
            }

            // Handle postcode filter separately (uses geo search)
            if ($type === 'postcode') {
                continue;
            }

            $filterMapping = $this->getMeilisearchFilterMapping($type, $values);
            if ($filterMapping !== null) {
                $filters = array_merge($filters, $filterMapping);
            }
        }

        return $filters;
    }

    /**
     * Get facet distribution from Meilisearch, cached for the current request.
     */
    protected function getFacetDistribution(): array
    {
        if ($this->cachedFacets === null) {
            $facetAttributes = array_values($this->facetAttributeMapping);
            $searchTerm = $this->selections['search'][0] ?? null;

            $this->cachedFacets = $this->meilisearch->getFacets(
                $this->meilisearchFilters,
                $facetAttributes,
                $searchTerm,
                $this->geoFilter
            );
        }

        return $this->cachedFacets;
    }

    protected function getResultCount(bool $include_extras = true): int
    {
        if ($this->cachedSearchResults !== null) {
            return $this->cachedSearchResults['total'];
        }

        $searchResults = $this->meilisearch->getVehicleIds(
            $this->meilisearchFilters,
            [],
            0,
            0,
            $this->selections['search'][0] ?? null,
            $this->geoFilter
        );

        return $searchResults['total'];
    }

    public function getRepresentativeFinance(JsonResource $results): ?VehicleFinance
    {
        return parent::getRepresentativeFinance($results);
    }
}
