<?php

namespace KupShop\CatalogBundle\Util;

use KupShop\CatalogBundle\Entity\Section;
use KupShop\CatalogBundle\Search\ElasticUpdateGroup;
use KupShop\CatalogBundle\Search\FulltextElastic;
use KupShop\CatalogElasticBundle\Util\ProductIndexExtension;
use KupShop\I18nBundle\Translations\ProductsTranslation;
use KupShop\KupShopBundle\Util\Functional\Mapping;
use KupShop\KupShopBundle\Util\StringUtil;
use Query\Operator;
use Query\QueryBuilder;
use Query\Translation;
use Symfony\Contracts\Service\Attribute\Required;

class ElasticDataFetchUtil
{
    use ElasticUpdateChangesTrait;

    protected ?ProductIndexExtension $productIndexExtension = null;

    #[Required]
    public FulltextElastic $fulltextElastic;

    /**
     * @param array $products array of products ids when $partialUpdate is false or mapping of products changed fields when $partialUpdate is true
     *                        mapping in format [idProduct => ['title', etc...], ...]
     *
     * @return array values for elastic
     */
    public function fetchProducts(array $products, bool $partialUpdate = false): array
    {
        $id_products = array_is_list($products) ? $products : array_keys($products);

        $this->setUpdateItemsChanges($partialUpdate ? $products : null);

        $productList = new \ProductList();
        $productList->andSpec(
            function (QueryBuilder $qb) use ($id_products) {
                $qb->addSelect('p.figure as figure, p.pieces_sold, p.position')
                    ->andWhere(Operator::inIntArray($id_products, 'p.id'))
                    ->joinProducersOnProducts()->addSelect('pr.name as producer_name')
                    ->andWhere($this->fulltextElastic->getProductsSpec())
                    ->andWhere(
                        Translation::coalesceTranslatedFields(
                            ProductsTranslation::class,
                            ['long_descr', 'parameters']
                        )
                    );

                if (!findModule(\Modules::COMPONENTS)) {
                    $qb->andWhere(Operator::equals(['p.show_in_search' => 'Y']));
                }
            }
        );

        if (findModule(\Modules::PRODUCTS_SECTIONS, \Modules::SUB_ELASTICSEARCH)) {
            $this->productIndexExtension->setUpdateItemsChanges($partialUpdate ? $products : null);
            $this->productIndexExtension->applyFulltextProductListModifiers($productList);
        }

        $products = [];
        $productList->fetchImages(null);
        $productList->fetchVariations(null);

        if ($this->anyUpdateItemChanges(ElasticUpdateGroup::Blocek)) {
            $productList->fetchDescriptionPlus();
        }

        if ($this->anyUpdateItemChanges(ElasticUpdateGroup::Parameters)) {
            $productList->fetchParameters();
        }

        if ($this->anyUpdateItemChanges(ElasticUpdateGroup::Producer)) {
            $productList->fetchProducers();
        }

        if ($this->anyUpdateItemChanges(ElasticUpdateGroup::Sections)) {
            $productList->fetchSections();
        }

        if ($partialUpdate && $this->anyUpdateItemChanges(ElasticUpdateGroup::DeliveryTime)) {
            $productList->fetchProductOfSuppliersInStore();
        }

        $delete = Mapping::mapKeys($id_products, fn ($k, $v) => [$v, ['id' => $v]]);

        /** @var \Product $product */
        foreach ($productList->getProducts() as $product) {
            $row = [];
            $row['id'] = $product->id;
            $row['title'] = $product->title;
            $row['annotation'] = $product->descr;

            if ($this->updateItemChanges($product->id, ['long_descr', 'parameters', ElasticUpdateGroup::Blocek])) {
                $row['description'] = StringUtil::htmlToCleanText($product->longDescr);
                $row['description2'] = StringUtil::htmlToCleanText($product->parameters);
                $row['description_plus'] = StringUtil::htmlToCleanText($product->description_plus ?? '');
            }

            $row['visible'] = $product->visible == 'Y' ? '1' : '0';
            $row['code'] = $product->code;
            $row['ean'] = formatEAN($product->ean);
            $row['id_photo'] = $product->photoId;

            if ($this->updateItemChanges($product->id, [ElasticUpdateGroup::Price, ElasticUpdateGroup::Discount])) {
                $row['price'] = $product->getProductPrice()->getPriceWithVat()->asFloat();
            }

            if ($this->updateItemChanges($product->id, ElasticUpdateGroup::Producer)) {
                $row['producer'] = $product->field_producerTitle();
            }

            // Sections

            if ($this->updateItemChanges($product->id, ElasticUpdateGroup::Sections)) {
                $sections = [];
                $parent_sections = [];
                foreach ($product->fetchSections() as $section) {
                    $sections[] = $section->getName();
                    $parents = $section->getParents();
                    // Without self
                    array_pop($parents);
                    $parent_sections[] = join(
                        '/',
                        array_map(
                            function (Section $x) {
                                return $x->getName();
                            },
                            $parents
                        )
                    );
                }

                $row['sections'] = join(', ', $sections);
                $row['parent_sections'] = join(', ', $parent_sections);
            }

            if ($this->updateItemChanges($product->id, ElasticUpdateGroup::Parameters)) {
                // Parameters
                $parameters = [];
                foreach ($product->fetchParameters() as $parameter) {
                    $parameter_values = array_map(
                        function ($x) {
                            return $x['value'];
                        },
                        $parameter['values']
                    );
                    $parameters[] = $parameter['name'].'='.join(',', $parameter_values);
                }
                $row['parameters'] = join(', ', $parameters);
            }

            if ($this->updateItemChanges($product->id, [ElasticUpdateGroup::Variations, ElasticUpdateGroup::Ean, ElasticUpdateGroup::Code])) {
                // Variations
                $variations = [];
                foreach ($product->fetchVariations() as $variation) {
                    $variations[] = $variation['title'];

                    if (!empty($variation['ean'])) {
                        $row['ean'] .= ' '.formatEAN($variation['ean']);
                    }

                    if (!empty($variation['code'])) {
                        $row['code'] .= ' '.$variation['code'];
                    }
                }
                $row['variations'] = join(', ', $variations);
            }

            $row['weight'] = 0.0;
            $row['delivery'] = 0;
            $row['sold'] = 0;
            $row['position'] = 0;

            if ($partialUpdate) {
                $product->prepareDeliveryText();
                $row = array_merge($row, $this->getProductsAttributesValues($product));
            }

            if (findModule(\Modules::PRODUCTS_SECTIONS, \Modules::SUB_ELASTICSEARCH)) {
                $row += $this->productIndexExtension->extractIndexFields($product);
            }

            $products[] = $row;

            unset($delete[$row['id']]);
        }

        return [$products, $delete];
    }

    public function getProductsAttributesValues(\Product $product)
    {
        $deliveryTimeWeights = findModule(
            'fulltext_search',
            'delivery_weights',
            [
                '-2' => 1,
                '-1' => 2,
                '0' => 3,
            ]
        );

        $maxDeliveryTimeWeight = array_reduce(
            $deliveryTimeWeights,
            static function ($x, $y) {
                return max($x, $y);
            },
            0
        );

        $productPosition = $product->position !== null ? $product->position : POSITION_STANDARD;

        // Range values to <1, 10> scale
        $res = [
            'sold' => $product->pieces_sold !== null
                ? max(0, min(10,
                    intval(log10($product->pieces_sold) * 3)),
                )
                : 0,

            'position' => $product->position !== null
                ? max(0, min(10,
                    5 - ($productPosition - POSITION_STANDARD) / 1000),
                ) : 0,

            'delivery' => max(0, min(10,
                intval(getVal($product->deliveryTime, $deliveryTimeWeights, 1) * 10 / $maxDeliveryTimeWeight)),
            ),
        ];

        $res['weight'] = (1 + floatval($res['position']) / 100) * (1 + floatval($res['delivery']) / 100) * (1 + floatval($res['sold']) / 60);

        return $res;
    }

    #[Required]
    public function setProductIndexExtension(?ProductIndexExtension $productIndexExtension): void
    {
        $this->productIndexExtension = $productIndexExtension;
    }
}
