<?php

namespace KupShop\FeedsBundle\Feed;

use KupShop\CatalogBundle\Util\ProductsFilterSpecs;
use KupShop\FeedGeneratorBundle\Feed\IConfigurableFeed;
use KupShop\FeedsBundle\FeedProductList;
use KupShop\FeedsBundle\Query\FeedProductQuery;
use KupShop\KupShopBundle\Context\CurrencyContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\RestrictionsBundle\Utils\Restrictions;
use Query\Operator;
use Query\QueryBuilder;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Contracts\Service\Attribute\Required;

abstract class BaseFeed implements IFeed
{
    /** @var string */
    public static $type;

    /** @var string */
    public static $alias;

    /** @var string */
    public static $objectType = 'product';

    /** @var string */
    protected static $template;

    /** @var int */
    protected static $batchSize = 1000;

    /** @var CurrencyContext */
    protected $currencyContext;

    /** @var ProductsFilterSpecs */
    protected $productsFilterSpecs;

    /** @var array of additional template variables */
    protected $templateVariables = [];

    /** @var float|int ttl for cache */
    protected $ttl = 60 * 60 * 4;

    /** @var FeedProductList */
    protected $productList;

    protected RequestStack $requestStack;

    #[Required]
    public function setCurrencyContext(CurrencyContext $currencyContext)
    {
        $this->currencyContext = $currencyContext;
    }

    #[Required]
    public function setProductsFilterSpecs(ProductsFilterSpecs $productsFilterSpecs)
    {
        $this->productsFilterSpecs = $productsFilterSpecs;
    }

    #[Required]
    public function setRequestStack(RequestStack $requestStack): void
    {
        $this->requestStack = $requestStack;
    }

    public static function getType(): string
    {
        return static::$type;
    }

    public static function getAlias(): string
    {
        return static::$alias;
    }

    public static function isAllowed(): bool
    {
        return static::$objectType === 'product' ? ((bool) findModule(\Modules::PRODUCTS)) : true;
    }

    public function getObjectType(): string
    {
        return static::$objectType;
    }

    protected static function getTemplateFile(): string
    {
        return static::$template ?? 'feeds/'.static::getType().'.tpl';
    }

    public function getProductList(int $feedID): FeedProductList
    {
        $productList = ServiceContainer::getService(FeedProductList::class);
        $productList->andSpec(FeedProductQuery::withFeedProducts($feedID));
        if (!$this instanceof IConfigurableFeed) {
            $productList->fetchParameters()
                ->fetchSections()
                ->fetchVariationLabels();
        }
        $productList->fetchProducers()
            ->orderBy('p.id, pv.id');
        $this->fetchImages($productList);

        if (findModule(\Modules::PRODUCT_SETS, \Modules::SUB_PRODUCT_SETS_CALCULATE_PRICE)) {
            $productList->fetchSets();
        }

        $date_from = $this->requestStack->getMainRequest()?->get('date_from') ?? $filter['date_added_range']['from'] ?? null;
        $date_to = $this->requestStack->getMainRequest()?->get('date_to') ?? $filter['date_added_range']['to'] ?? null;

        $filterSpecs = null;
        $feedData = sqlQueryBuilder()->select('id, settings, only_feed_products')->from('feeds')->where(Operator::equals(['id' => $feedID]))->execute()->fetch();
        $settings_json = ($feedData['settings'] ?? null);
        if ($settings_json) {
            $settings = json_decode_strict($settings_json, true);
            $filter = $settings['filter'] ?? null;
            // remove from filter, we handle it separately
            unset($filter['date_added_range']);
            if ($filter) {
                $filterSpecs = $this->productsFilterSpecs->getSpecs($filter);
            }
        }
        if ($filterSpecs) {
            $productList->andSpec($filterSpecs);
            // pokud se pouziji Filter specky, ktere jsou nastavene u feedu, tak se
            // neaplikuji restrikce, ktere se aplikuji pouze tehdy, kdyz se vola $productList->applyDefaultFilterParams()
            // proto se tady musi restrikce nastavit zvlast
            if (findModule(\Modules::RESTRICTIONS)) {
                $restrictions = ServiceContainer::getService(Restrictions::class);
                $productList->andSpec($restrictions->getRestrictionSpec());
            }
        } else {
            $fp = $productList->applyDefaultFilterParams();
            $fp->setInStore(\Filter::IN_STORE_SUPPLIER);
        }

        if ($feedData['only_feed_products'] == 'Y') {
            $productList->andSpec(
                function (QueryBuilder $qb) use ($feedData) {
                    $qb->andWhere('p.id IN (
                        SELECT id_product FROM feed_products WHERE id_feed = :id_feed
                    )')->addParameters(['id_feed' => $feedData['id']]);
                }
            );
        }

        if ($date_from || $date_to) {
            $added_field = $productList->getVariationsAsResult() ? 'COALESCE(pv.date_added, p.date_added)' : 'p.date_added';
            $updated_field = $productList->getVariationsAsResult() ? 'COALESCE(pv.updated, p.updated)' : 'p.updated';
            $productList->andSpec(function (QueryBuilder $qb) use ($date_from, $date_to, $added_field, $updated_field) {
                $qb->andWhere(Operator::orX(
                    Operator::between($added_field, new \Range($date_from, $date_to)),
                    Operator::between($updated_field, new \Range($date_from, $date_to)),
                ));
            });
        }

        $this->productList = $productList;

        return $productList;
    }

    protected function fetchImages(\ProductList $productList): void
    {
        $productList->fetchImages('default_feed', null, true)
            ->fetchOtherImages('default_feed');
    }

    /**
     * Prepare single row data - overrides title, adds cpc...
     * Intended use is for example to add heureka sections.
     */
    protected function prepareData(\Product $product)
    {
        $urlParams = [
            'URL' => 'launch.php',
            's' => 'product',
            'IDproduct' => $product['id'],
            'TITLE' => $product['title'],
            'ESCAPE' => 'NO',
            'absolute' => true,
        ];
        if (isset($this->currencyContext) && $this->currencyContext->getActiveId() !== $this->currencyContext->getDefaultId()) {
            $urlParams['currency'] = $this->currencyContext->getActiveId();
        }

        $product['URL_Abs'] = createScriptURL($urlParams);

        $product['title_origin'] = $product['title'];
        $product['title'] = $product['fp_title'] ?? $product['title'];
        $product['cpc'] = $product['fp_cpc'] ?? $product['max_cpc'] ?? 0;

        return $product;
    }

    public function getData(array $feedRow, ?int $limit = null): \Generator
    {
        $productList = $this->productList ?? $this->getProductList($feedRow['id']);
        // use batching only if limit is NOT set
        $iterationLimit = isset($limit) ? 1 : ($productList->getProductsCount() / (float) static::$batchSize);
        for ($i = 0; $i < $iterationLimit; $i++) {
            $productList
                ->limit($limit ?? static::$batchSize, $i * static::$batchSize);
            foreach ($productList->getProducts() as $product) {
                yield $this->prepareData($product);
            }
        }
    }

    public function render(array $feedRow, ?int $limit = null): void
    {
        $data = $this->getData($feedRow, $limit);

        $smarty = createSmarty(false, true);
        $smarty->assign('dataGenerator', $data);
        $smarty->assign('feed', $this);
        $smarty->assign('feedData', $feedRow['data'] ?? []);
        foreach ($this->templateVariables as $key => $value) {
            $smarty->assign($key, $value);
        }
        $smarty->display(static::getTemplateFile());
        unset($smarty);
    }

    public function getTTL(): int
    {
        return $this->ttl;
    }
}
