<?php

declare(strict_types=1);

namespace KupShop\CatalogElasticBundle\DataContext;

use Elastica\Multi\ResultSet as MultiResultSet;
use Elastica\Query\AbstractQuery;
use KupShop\CatalogBundle\ProductList\DynamicFilterAttributes;
use KupShop\CatalogElasticBundle\Enum\FilterValuesOrder;
use KupShop\CatalogElasticBundle\Exception\AggregationUnfoldingException;
use KupShop\CatalogElasticBundle\Util\ElasticFilterValues;
use KupShop\CatalogElasticBundle\Util\ElasticQueryClient;
use KupShop\CatalogElasticBundle\Util\ElasticSpecs;
use KupShop\CatalogElasticBundle\Wrapper\TargetAggregation;

class ElasticFilterValuesContext
{
    /**
     * @var array<string, array<string, TargetAggregation>>
     */
    private array $cachedTargetAggregations;

    public function __construct(
        private readonly ElasticFilterValues $elasticFilterValues,
        public readonly MultiResultSet $multiResultSet,
        public readonly DynamicFilterAttributes $filterAttributes,

        public array &$filterValues = [],
        public readonly string $aggregationName = '',
    ) {
    }

    public function with(string $name): self
    {
        $me = new self(
            $this->elasticFilterValues,
            $this->multiResultSet,
            $this->filterAttributes,
            $this->filterValues,
            $name,
        );

        if (isset($this->cachedTargetAggregations)) {
            $me->cachedTargetAggregations = $this->cachedTargetAggregations;
        }

        return $me;
    }

    public function set(string $key, mixed $value): static
    {
        $this->filterValues[$key] = $value;

        return $this;
    }

    public function get(): array
    {
        return $this->filterValues;
    }

    public function getFilter(string $esFieldName): ?AbstractQuery
    {
        $filterQuery = $this->multiResultSet[ElasticQueryClient::$mainQueryName]
            ->getQuery()
            ->getQuery();

        $filters = self::getQueryFilterSafe($filterQuery);
        if (!$filters) {
            return null;
        }

        foreach ($filters as $filter) {
            foreach ($filter->getParams() as $filterName => $_) {
                if ($filterName === $esFieldName) {
                    return $filter;
                }
            }
        }

        return null;
    }

    public function isFilterEmpty(array $ignoring = ['filter_section']): bool
    {
        $filterQuery = $this->multiResultSet[ElasticQueryClient::$mainQueryName]
            ->getQuery()
            ->getQuery();

        $filters = self::getQueryFilterSafe($filterQuery);
        if (!$filters) {
            return true;
        }

        $filterNames = [];

        /** @var AbstractQuery $filter */
        foreach ($filters as $filter) {
            foreach ($filter->getParams() as $filterName => $_) {
                $filterNames[] = $filterName;
            }
        }

        return empty(array_diff($filterNames, $ignoring));
    }

    /**
     * @return array<string, TargetAggregation>
     *
     * @throws AggregationUnfoldingException
     */
    public function getTargetAggregations(): array
    {
        if (!isset($this->cachedTargetAggregations)) {
            $this->cachedTargetAggregations = $this->elasticFilterValues->getTargetAggregations($this->multiResultSet);
        }

        return $this->cachedTargetAggregations[$this->aggregationName] ?? [];
    }

    /**
     * @return FilterValuesOrder what should be used to order the filter values
     *
     * @throws AggregationUnfoldingException
     */
    public function getOrderBy(string|int|null $aggregationId = null): FilterValuesOrder
    {
        $allAggregations = $this->getTargetAggregations();
        if ($aggregationId !== null) {
            $aggregation = $allAggregations[$aggregationId] ?? null;
        } else {
            $aggregation = reset($allAggregations);
        }

        if (!$aggregation
            || null === ($bucketCount = $aggregation->getTotalBucketCount())
            || $bucketCount <= ElasticSpecs::$topSellingBucketsSize
        ) {
            return FilterValuesOrder::Admin;
        }

        return FilterValuesOrder::ProductsSold;
    }

    private static function getQueryFilterSafe(AbstractQuery $query): ?array
    {
        try {
            if (!$query->hasParam('filter')) {
                return null;
            }

            return $query->getParam('filter');
        } catch (\Error $e) {
            // TODO: remove after fix or other workaround
            // The function calls throw an Error if the query had no params set.
            // The toArray() method of BoolQuery sets the _params member to an empty stdClass.
            //  see: https://github.com/ruflin/Elastica/issues/1450
            if (str_contains($e->getMessage(), 'stdClass')) {
                return null;
            }

            throw $e;
        }
    }
}
