<?php

namespace Daylight\Core\Modules\Search;

use Daylight\Core\Modules\Search\Entities\SearchResult;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Meilisearch\Client;
use Meilisearch\Contracts\SearchQuery;

class SearchQueryBuilder
{
    protected ?Collection $facets;

    protected ?Collection $sortingOptions;

    protected ?Collection $activeFilters;

    protected ?Builder $baseQuery;

    protected int $perPage = 24;

    protected ?int $currentPage = 1;

    protected ?string $sortBy = null;

    protected ?string $searchQuery = null;

    protected bool $onlyFacets = false;

    private function __construct()
    {
        $this->facets = collect();

        return $this;
    }

    public function setBaseQuery(Builder $builder): self
    {
        $this->baseQuery = $builder;

        return $this;
    }

    public function setSearchQuery(?string $searchQuery): self
    {
        $this->searchQuery = $searchQuery;

        return $this;
    }

    public function setAvailableFacets(Collection $facets): self
    {
        $this->facets = $facets;

        return $this;
    }

    public function setAvailableSortingOptions(Collection $sortingOptions): self
    {
        $this->sortingOptions = $sortingOptions;

        return $this;
    }

    public function setActiveFilters(array|Collection $activeFilters): self
    {
        if (is_array($activeFilters)) {
            $activeFilters = collect($activeFilters);
        }

        $availableFilters = config('scout.meilisearch.index-settings.products.filterableAttributes');

        $filterableAttributes = Cache::remember('filterable_attribute_keys_from_db', now()->addHours(2), function () {
            return daylightModel('attribute')::query()
                ->whereFilterable(1)
                ->pluck('key')
                ->toArray();
        });

        $availableFilters = array_merge($availableFilters, $filterableAttributes);

        if (Cache::get('filterable_attribute_keys')) {
            $cachedAvailableFilter = Cache::get('filterable_attribute_keys');
            $availableFilters = array_merge($availableFilters, array_keys($cachedAvailableFilter));
        }

        $activeFilters = $activeFilters->filter(function ($filter, $key) use ($availableFilters) {
            return in_array($key, $availableFilters);
        });

        $this->activeFilters = $activeFilters;

        return $this;
    }

    public function setPerPage(int $amount): self
    {
        $this->perPage = $amount;

        return $this;
    }

    public function setCurrentPage(int $currentPage): self
    {
        $this->currentPage = $currentPage;

        return $this;
    }

    public function setSortBy(?string $sortBy): self
    {
        if (is_null($sortBy)) {
            return $this;
        }

        $options = [
            'relevance',
            ...collect(config('scout.meilisearch.index-settings.products.sortableAttributes'))->map(function ($attribute) {
                return [
                    $attribute . ':asc',
                    $attribute . ':desc',
                ];
            })->flatten(),
        ];

        if (! in_array($sortBy, $options)) {
            return $this;
        }

        $this->sortBy = $sortBy;

        return $this;
    }

    public function getBaseQuery(): Builder
    {
        return $this->baseQuery;
    }

    public function getAvailableFacets(): Collection
    {
        return $this->facets;
    }

    public function getActiveFilters(): Collection
    {
        return $this->activeFilters;
    }

    public function getSortingOptions(): Collection
    {
        return $this->sortingOptions;
    }

    public function getPerPage(): int
    {
        return $this->perPage;
    }

    private function getCurrentPage(): int
    {
        return Paginator::resolveCurrentPage();
    }

    private function getOffset(): int
    {
        return $this->getCurrentPage() * $this->perPage - $this->perPage;
    }

    public function getSortBy(): array
    {
        if (is_null($this->sortBy) || $this->sortBy === 'relevance') {
            return [];
        }

        return [$this->sortBy];
    }

    public function onlyFacets(): self
    {
        $this->onlyFacets = true;

        return $this;
    }

    private function getSearchQuery(): ?string
    {
        return $this->searchQuery;
    }

    private function getIndex(): string
    {
        return config('scout.prefix') . 'products';
    }

    private function getFilterQuery(): Collection
    {
        return $this->activeFilters->filter()->map(function ($filter, $key) {
            if ($key === 'price') {
                return 'price ' . $filter['min'] . ' TO ' . $filter['max'];
            }

            return collect($filter)->map(function ($value) use ($key) {
                return $key . '="' . $value . '"';
            });
        });
    }

    private function getQuery(array $facets, array $filter, ?string $searchQuery = null): SearchQuery
    {
        return (new SearchQuery)
            ->setAttributesToHighlight(['*'])
            ->setFacets($facets)
            ->setFilter($filter)
            ->setAttributesToRetrieve($this->onlyFacets() ? ['id'] : ['*'])
            ->setHighlightPostTag('__/ais-highlight__')
            ->setHighlightPreTag('__ais-highlight__')
            ->setIndexUid($this->getIndex())
            ->setLimit($this->getPerPage())
            ->setOffset($this->getOffset())
            ->setSort($this->getSortBy())
            ->setQuery($searchQuery ?? '');
    }

    public function getQueries(): array
    {
        return collect([
            $this->getQuery(
                $this->facets->keys()->toArray(),
                $this->getFilterQuery()->values()->toArray(),
                $this->getSearchQuery()
            ),
            //            ...$this->activeFilters->filter(fn($filter, $key) => $key !== 'price')->map(function ($filter, $key) {
            ...$this->activeFilters->filter()->map(function ($filter, $key) {
                return $this->getQuery(
                    [$key],
                    $this->getFilterQuery()->except($key)->values()->toArray(),
                    $this->getSearchQuery()
                );
            }),
        ])->values()->toArray();
    }

    public function get(): SearchResult|bool
    {
        $response = $this->getClient()->multiSearch($this->getQueries());

        if (isset($response['results']) && is_array($response['results'])) {
            return new SearchResult($this, $this->getBaseQuery(), collect($response['results']));
        }

        return false;
    }

    private function getClient(): Client
    {
        return app(Client::class);
    }

    public static function create(): self
    {
        return new self;
    }
}
