<?php

declare(strict_types=1);

namespace KupShop\GraphQLBundle\ApiPublic\Controller;

use KupShop\CatalogBundle\ProductList\DynamicFilterAttributes;
use KupShop\CatalogBundle\Util\FilterUtil;
use KupShop\CatalogElasticBundle\Contracts\ElasticSectionEventDispatcher;
use KupShop\CatalogElasticBundle\SectionRequest;
use KupShop\CatalogElasticBundle\Util\ElasticSpecs;
use KupShop\ComponentsBundle\Twig\DataProvider\ProductDataProvider;
use KupShop\GraphQLBundle\ApiAdmin\Annotation\Module;
use KupShop\GraphQLBundle\ApiPublic\Types\Category\BaseFilter;
use KupShop\GraphQLBundle\ApiPublic\Types\Category\CategoryNextPage;
use KupShop\GraphQLBundle\ApiPublic\Types\Category\FilteredCategory;
use KupShop\GraphQLBundle\ApiPublic\Types\Category\SectionFilter;
use KupShop\GraphQLBundle\ApiPublic\Util\SectionUtil;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use TheCodingMachine\GraphQLite\Annotations\Query;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
use Twig\TemplateWrapper;

class CategoryController
{
    protected string $templateName = 'view/section.ajax.html.twig';
    private TemplateWrapper $template;

    public function __construct(
        private readonly SectionUtil $util,
        private readonly Environment $twig,
        private readonly ?ProductDataProvider $productDataProvider,
        private readonly FilterUtil $filterUtil,
    ) {
    }

    #[Query]
    #[Module(\Modules::PRODUCTS_SECTIONS, \Modules::SUB_ELASTICSEARCH)]
    public function categoryFilter(
        SectionFilter $filter,
    ): FilteredCategory {
        $result = $this->util->getFilteredSection($filter, $baseFilterParams);

        $response = new FilteredCategory(
            $filter->baseFilter,
            $baseFilterParams,
            $result,
            $this->util,
            $this->productDataProvider,
            $this->filterUtil,
        );
        $response->setTemplate($this->getTemplate($filter->baseFilter->categoryId));

        return $response;
    }

    #[Query]
    #[Module(\Modules::PRODUCTS_SECTIONS, \Modules::SUB_ELASTICSEARCH)]
    public function categoryLoadMore(SectionFilter $filter): CategoryNextPage
    {
        $result = $this->util->getFilteredSection($filter);

        $response = new CategoryNextPage($result, $this->productDataProvider);
        $response->setTemplate($this->getTemplate($filter->baseFilter->categoryId));

        return $response;
    }

    #[Query]
    #[Module(\Modules::PRODUCTS_SECTIONS, \Modules::SUB_ELASTICSEARCH)]
    public function loadAllFilterValues(BaseFilter $baseFilter, string $filterData, string $filterId): string
    {
        $result = $this->util->getFilteredSection(new SectionFilter(
            baseFilter: $baseFilter,
            filterData: $filterData,
        ));
        assert($result instanceof ElasticSectionEventDispatcher);

        [$filterType, $filterId] = $this->parseFilterId($filterId);

        $maxBucketSizeBefore = ElasticSpecs::$topSellingBucketsSize;
        $result->onBeforeCompile(function (SectionRequest $request) use ($filterType, $filterId) {
            ElasticSpecs::$topSellingBucketsSize = 10_000;

            // === Only create the aggregations for the requested filter
            //  -> disable other filters in the DynamicFilterAttributes object
            if (!property_exists($request->dynamicFilterAttributes, $filterType)) {
                throw $this->invalidFilterId($filterType);
            }

            $myAttrs = DynamicFilterAttributes::empty();
            $myAttrs->{$filterType} = $request->dynamicFilterAttributes->{$filterType};

            if (null !== $filterId) {
                if (!is_array($myAttrs->{$filterType})) {
                    throw $this->invalidFilterId($filterType.'.'.$filterId);
                }

                $myAttrs->{$filterType} = [$filterId => $myAttrs->{$filterType}[$filterId]];
            }

            $request->dynamicFilterAttributes = $myAttrs;

            // No need to fetch products (micro-optimization)
            $request->setNoProducts();

            // === Fetch all possible filter values for the products in the given section.
            //  -> This setting fetches the values *without* filtering the section (only id_section = xyz),
            //     and then fetches the product counts from the section *with* filters applied (stuff from dynamic_filter[xyz]=asd).
            $request->fetchAllFilters = true;
        });

        // This line actually executes the ES search...
        //  -> return the original value for $maxBucketSize after this line.
        $config = $this->filterUtil->getSectionFiltersConfig($result->getDynamicFilter(), null);

        ElasticSpecs::$topSellingBucketsSize = $maxBucketSizeBefore;

        return json_encode(array_filter(
            $config,
            fn ($value) => $value && is_array($value) && (!isset($value['values']) || !empty($value['values'])),
        ));
    }

    /**
     * @throws SyntaxError
     * @throws RuntimeError
     * @throws LoaderError
     */
    protected function getTemplate(int $idSection): TemplateWrapper
    {
        $section = $this->util->getSection($idSection);
        if ($customTemplate = $section->getTemplate()) {
            $customTemplate = 'view/'.str_replace('.tpl', '.ajax.html.twig', $customTemplate);
            if ($this->twig->getLoader()->exists($customTemplate)) {
                return $this->template = $this->twig->load($customTemplate);
            }
        }

        return $this->template ??= $this->twig->load($this->templateName);
    }

    /**
     * @return array{string, string|null}
     */
    private function parseFilterId(string $filterId): array
    {
        $exploded = explode('.', $filterId);
        if (count($exploded) > 2) {
            throw $this->invalidFilterId($filterId);
        }

        if (count($exploded) === 2) {
            return $exploded;
        }

        return [reset($exploded), null];
    }

    private function invalidFilterId(string $filterId): BadRequestHttpException
    {
        return new BadRequestHttpException(sprintf('invalid filterId (%s)', $filterId));
    }
}
