<?php

declare(strict_types=1);

namespace KupShop\LuigisBoxBundle\Search;

use Answear\LuigisBoxBundle\DTO\ConfigDTO;
use Answear\LuigisBoxBundle\Response\Search\Facet;
use Answear\LuigisBoxBundle\Response\Search\Hit;
use Answear\LuigisBoxBundle\Response\SearchResponse;
use Answear\LuigisBoxBundle\Service\ConfigProvider;
use Answear\LuigisBoxBundle\Service\SearchRequestInterface;
use Answear\LuigisBoxBundle\ValueObject\SearchUrlBuilder;
use KupShop\CatalogBundle\Search\FulltextElastic;
use KupShop\CatalogBundle\Search\FulltextInterface;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\Functional\Mapping;

class FulltextLuigisBox implements FulltextInterface
{
    public const INDEX_PRODUCTS = 'item';
    public const INDEX_SECTIONS = 'category';
    public const INDEX_PRODUCERS = 'brand';
    public const INDEX_ARTICLES = 'article';
    public const INDEX_PAGES = 'page';

    public const LB_TYPES = [
        FulltextElastic::INDEX_PRODUCTS => self::INDEX_PRODUCTS,
        FulltextElastic::INDEX_SECTIONS => self::INDEX_SECTIONS,
        FulltextElastic::INDEX_PRODUCERS => self::INDEX_PRODUCERS,
        FulltextElastic::INDEX_ARTICLES => self::INDEX_ARTICLES,
        FulltextElastic::INDEX_PAGES => self::INDEX_PAGES,
    ];

    protected int $curlTimeout = 60;

    protected int $totalProductsCount;

    protected array $filters = [];

    protected FulltextElastic $elastic;

    protected array $dynamicFilters = [];

    protected SearchRequestInterface $searchRequest;
    protected ConfigProvider $configProvider;

    public function setCurlTimeout($timeout): FulltextInterface
    {
        $this->curlTimeout = $timeout;

        return $this;
    }

    public function setDynamicFilters(array $filters): void
    {
        $this->dynamicFilters = $filters;
    }

    public function search(string $term, array $config, array $types = []): array
    {
        $types ??= $this->getIndexTypes();
        $mainConfig = $config[FulltextElastic::INDEX_PRODUCTS];

        $page = $mainConfig['offset'] / $mainConfig['count'] + 1;
        $urlBuilder = new SearchUrlBuilder($page);
        $urlBuilder
            ->setQuery($term)
            ->setSize(min($mainConfig['count'], 200))
            ->setQuicksearchTypes($this->getLBTypes(array_filter($types, fn ($x) => $x != FulltextElastic::INDEX_PRODUCTS)))
            ->setDynamicFacetsSize(3)
            ->setFacets(['price_amount', 'category', 'brand'])
            ->addFilter('type', 'item');

        $this->addDynamicFilters($urlBuilder);

        $searchResponse = $this->searchRequest->search($urlBuilder);

        $this->filters = $this->transformFacets($searchResponse->getFacets());

        return $this->processSearchResults($searchResponse);
    }

    protected function addDynamicFilters(SearchUrlBuilder $urlBuilder)
    {
        foreach ($this->formatDynamicFilters() as $name => $filter) {
            switch ($filter['type'] ?? '') {
                case 'float':
                    $urlBuilder->addFilter($name, ($filter['values']['min'] ?? '').'|'.($filter['values']['max'] ?? ''));
                    break;
                case 'text':
                    foreach ($filter['values'] ?? [] as $value) {
                        $urlBuilder->addFilter($name, $value);
                    }
                    break;
            }
        }
    }

    protected function formatDynamicFilters(): array
    {
        $result = [];

        foreach ($this->dynamicFilters['parameters'] ?? [] as $name => $filter) {
            switch ($filter['type'] ?? '') {
                case 'float':
                    $matches = [];
                    if (preg_match("/^\s?(\d*\.?\d*)(\*?)\ ?\-\ ?(\d*\.?\d*)(\*?)\s?$/", $filter['value'], $matches)) {
                        $result[$name] = [
                            'type' => $filter['type'],
                            'values' => [
                                'min' => $matches[1],
                                'max' => $matches[3],
                            ],
                        ];
                    }

                    break;
                default:
                case 'text':
                    $result[$name] = [
                        'type' => $filter['type'],
                        'values' => $filter['value'],
                    ];

                    break;
            }
        }

        return $result;
    }

    protected function getLBTypes($types): array
    {
        $types = array_filter($types, fn ($x) => array_key_exists($x, self::LB_TYPES));

        return array_map(fn ($x) => self::LB_TYPES[$x], $types);
    }

    protected function processSearchResults(SearchResponse $response)
    {
        $result = [];

        $this->totalProductsCount = $response->getTotalHits();

        $result[FulltextElastic::INDEX_PRODUCTS] = Mapping::mapKeys($response->getHits(), function ($index, Hit $x) {
            $hit = $this->processHit($x);

            return [$hit['id'], $hit];
        });

        $reverseTypes = array_flip(self::LB_TYPES);
        foreach ($response->getQuickSearchHits() as $hit) {
            $result[$reverseTypes[$hit->getType()]][] = $this->processHit($hit);
        }

        return $result;
    }

    protected function processHit(Hit $hit): array
    {
        $result = $hit->getAttributes();

        if (!isset($result['id']) && isset($result['identity'])) {
            $result['id'] = $result['identity'];
        }

        if (!isset($result['id']) && isset($result['original_url'])) {
            $result['id'] = $result['original_url'];
        }

        if (is_array($result['id'])) {
            $result['id'] = reset($result['id']);
        }

        switch ($hit->getType()) {
            case self::INDEX_PRODUCTS:
                $result['id'] = explode('_', $result['id'])[0];
                break;

            case self::INDEX_PAGES:
            case self::INDEX_PRODUCERS:
                $result['name'] = $result['title'];
                break;

            case self::INDEX_SECTIONS:
                $result['name'] = $result['title'];
                $result['photo'] = getImage($result['id'], null, null, 'section', 1);
                $result['photo_src'] = $result['image_link'] ?? '';

                // no break
            default:
        }

        return $result;
    }

    public function searchProducts($term, $count, $offset, $order = null, $filter = '')
    {
        return $this->search($term, [])[FulltextElastic::INDEX_PRODUCTS];
    }

    public function searchProductsExact($term, $count, $offset, $order = null, $filter = '')
    {
        // TODO: Implement searchProductsExact() method.
    }

    public function getRowsCount()
    {
        return $this->totalProductsCount;
    }

    public function suggestTerm($term)
    {
        return null;
    }

    public function updateProduct($id_product)
    {
        return $this->elastic->updateProduct($id_product);
    }

    public function loadSynonyms(): array
    {
        return [];
    }

    public function loadSynonymsFromIndex(): ?array
    {
        return null;
    }

    public function updateSynonyms(array $synonyms, bool $merge = false): void
    {
        // TODO: Implement updateSynonyms() method.
    }

    public function saveSynonyms(array $synonyms): void
    {
        // TODO: Implement saveSynonyms() method.
    }

    public function updateIndex(string $type = 'all', $clean = true, array $exceptTypes = []): void
    {
        $this->elastic->updateIndex($type);
    }

    /**
     * @required
     */
    public function setElastic(FulltextElastic $elastic): void
    {
        $this->elastic = $elastic;
    }

    public function getIndexTypes(): array
    {
        return $this->elastic->getIndexTypes();
    }

    /** @required */
    public function setSearchRequest(SearchRequestInterface $searchRequest): void
    {
        $this->searchRequest = $searchRequest;
    }

    /** @required */
    public function setConfigProvider(ConfigProvider $configProvider): void
    {
        $dbcfg = \Settings::getDefault();

        if (($dbcfg->analytics['luigis_box']['id'] ?? false) && ($dbcfg->analytics['luigis_box']['key'] ?? false)) {
            $configProvider->addConfig('kupshop', new ConfigDTO($dbcfg->analytics['luigis_box']['id'], $dbcfg->analytics['luigis_box']['key']));
            $configProvider->setConfig('kupshop');
        }

        $this->configProvider = $configProvider;
    }

    public function getFilters(): array
    {
        return $this->filters;
    }

    /**
     * @param Facet[] $facets
     *
     * @return array
     */
    protected function transformFacets(array $facets)
    {
        $filters = [];

        $dynamic = $this->formatDynamicFilters();

        foreach ($facets as $facet) {
            if (in_array($facet->getName(), ['pcs_store', 'identity', 'Index ziskovosti', 'labels', 'Značka'])) {
                continue;
            }

            switch ($facet->getType()) {
                case 'float':
                    $values = $facet->getValues();
                    $getRange = fn ($x) => $x ? explode('|', $x->getValue()) : [0, 0];
                    $filters[] = [
                        'id' => $facet->getName(),
                        'type' => 'float',
                        'value_type' => 'float',
                        'name' => $this->getFilterName($facet->getName()),
                        'values' => [
                            'min' => $getRange($values[0] ?? 0)[0],
                            'max' => $getRange($values[count($values) - 1] ?? 0)[1],
                        ],
                        'active_filter' => $dynamic[$facet->getName()] ?? [],
                    ];
                    break;

                case 'text':
                    $values = [];
                    foreach ($facet->getValues() as $val) {
                        $values[$val->getValue()] = [
                            'id' => $val->getValue(),
                            'name' => $val->getValue(),
                            'count' => $val->getHitsCount(),
                        ];
                    }
                    $filters[] = [
                        'id' => $facet->getName(),
                        'type' => 'text',
                        'value_type' => 'text',
                        'name' => $this->getFilterName($facet->getName()),
                        'values' => $values,
                        'active_filter' => $dynamic[$facet->getName()] ?? [],
                    ];
                    break;
            }
        }

        return $filters;
    }

    protected function getFilterName($name): string
    {
        return match ($name) {
            'price_amount' => translate('price', 'lbx_filter_name'),
            'category' => translate('category', 'lbx_filter_name'),
            'brand' => translate('producer', 'lbx_filter_name'),
            default => $name,
        };
    }

    public function getFulltextLanguages(): array
    {
        $languages = [];
        foreach (Contexts::get(LanguageContext::class)->getAll() as $language) {
            if (!$language->isActive()) {
                continue;
            }

            $languages[$language->getId()] = $language;
        }

        return $languages;
    }

    public function supportsFilters(): bool
    {
        return true;
    }
}
