<?php

declare(strict_types=1);

namespace External\ZNZBundle\Util\Product;

use External\ZNZBundle\Util\ZNZConfiguration;
use External\ZNZBundle\Util\ZNZUtil;
use KupShop\KupShopBundle\Context\ContextManager;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\PricelistBundle\Context\PricelistContext;
use Query\Operator;
use Query\QueryBuilder;

class ZNZVisibilityUpdater
{
    public function __construct(
        private ZNZUtil $znzUtil,
        private ContextManager $contextManager,
        private ZNZConfiguration $configuration,
    ) {
    }

    public function updateVisibility(?array $productIds = null): void
    {
        $languageContext = Contexts::get(LanguageContext::class);

        // viditelnost potrebuji zaktualizovat pro kazdy jazyk zvlast
        foreach ($languageContext->getAll() as $lang) {
            $this->updateProductsVisibilityByHelios($lang->getId(), $productIds);
        }

        $this->hideProductsWithZeroPrice();
        $this->hideProductsWithoutTitle();
    }

    public function updateProductsVisibilityByHelios(string $language, ?array $productIds = null): void
    {
        $languageContext = Contexts::get(LanguageContext::class);

        $website = $this->znzUtil->getCurrentWebsite($language);

        if ($language === $languageContext->getDefaultId()) {
            $this->executeQueryBuilder(
                // skryju produkty, ktere nemaji byt videt ve vychozim jazyce
                sqlQueryBuilder()
                    ->update('products', 'p')
                    ->leftJoin('p', 'znz_products', 'zp', 'p.id = zp.id_product AND zp.id_variation IS NULL')
                    ->leftJoin('zp', 'znz_products_website', 'zpw', 'zpw.id_znz = zp.id_znz AND zpw.id_website = :website')
                    ->set('p.figure', ':figure')
                    // pokud je produkt videt a ma byt skryty, nebo neni viditelny pro aktualni website
                    ->andWhere('p.figure = "Y" AND (JSON_VALUE(zp.data, "$.znzFigure") = "N" OR zpw.id_znz IS NULL)')
                    ->addParameters(['figure' => 'N', 'website' => $website]),
                $productIds
            );

            $this->executeQueryBuilder(
                // zobrazim produktu, ktere maji byt videt ve vychozim jazyce
                sqlQueryBuilder()
                    ->update('products', 'p')
                    ->leftJoin('p', 'znz_products', 'zp', 'p.id = zp.id_product AND zp.id_variation IS NULL')
                    ->leftJoin('zp', 'znz_products_website', 'zpw', 'zpw.id_znz = zp.id_znz AND zpw.id_website = :website')
                    ->set('p.figure', ':figure')
                    // pokud je skryty a ma byt videt a je viditelny pro aktualni website
                    ->andWhere('p.figure = "N" AND JSON_VALUE(zp.data, "$.znzFigure") = "Y" AND zpw.id_znz IS NOT NULL')
                    // zobrazuju pouze produkty, ktere nemaji nulovou cenu
                    ->andWhere(static::withNonZeroPrice($language))
                    ->addParameters(['figure' => 'Y', 'website' => $website]),
                $productIds
            );
        } else {
            $this->executeQueryBuilder(
                // skryju produkty, ktere nemaji byt videt v prekladech
                sqlQueryBuilder()
                    ->update('products_translations', 'pt')
                    ->join('pt', 'products', 'p', 'p.id = pt.id_product')
                    ->leftJoin('pt', 'znz_products', 'zp', 'pt.id_product = zp.id_product AND zp.id_variation IS NULL')
                    ->leftJoin('zp', 'znz_products_website', 'zpw', 'zpw.id_znz = zp.id_znz AND zpw.id_website = :website')
                    ->set('pt.figure', ':figure')
                    // pokud je produkt videt a ma byt skryty, nebo neni viditelny pro aktualni website
                    ->andWhere('COALESCE(pt.figure, "Y") = "Y" AND (JSON_VALUE(zp.data, "$.znzFigure") = "N" OR zpw.id_znz IS NULL)')
                    ->andWhere(Operator::equals(['pt.id_language' => $language]))
                    ->addParameters(['figure' => 'N', 'website' => $website]),
                $productIds
            );

            $this->executeQueryBuilder(
                // zobrazim produkty, ktere maji byt videt v prekladech
                sqlQueryBuilder()
                    ->update('products_translations', 'pt')
                    ->join('pt', 'products', 'p', 'p.id = pt.id_product')
                    ->leftJoin('pt', 'znz_products', 'zp', 'pt.id_product = zp.id_product AND zp.id_variation IS NULL')
                    ->leftJoin('zp', 'znz_products_website', 'zpw', 'zpw.id_znz = zp.id_znz AND zpw.id_website = :website')
                    ->set('pt.figure', ':figure')
                    // pokud produkt neni videt a ma byt videt a je viditelny pro aktualni website
                    ->andWhere('COALESCE(pt.figure, "N") = "N" AND JSON_VALUE(zp.data, "$.znzFigure") = "Y" AND zpw.id_znz IS NOT NULL')
                    ->andWhere(Operator::equals(['pt.id_language' => $language]))
                    // zobrazuju pouze produkty, ktere nemaji nulovou cenu
                    ->andWhere(static::withNonZeroPrice($language))
                    // zobrazuju pouze produkty, ktere maji vyplneny title
                    ->andWhere(static::withTitle())
                    ->addParameters(['figure' => 'Y', 'website' => $website]),
                $productIds
            );
        }

        $this->updateVariationsVisibility($language, $productIds);
    }

    private function hideProductsWithoutTitle(): void
    {
        // skryju produkty v prekladech, pokud maji v prekladu nazvu prazdnej string
        sqlQueryBuilder()
            ->update('products_translations', 'pt')
            ->directValues(['figure' => 'N'])
            ->andWhere(Operator::not(Operator::equals(['pt.figure' => 'N'])))
            ->andWhere(Operator::not(static::withTitle()))
            ->execute();
    }

    private function hideProductsWithZeroPrice(): void
    {
        // v b2b modu maji defaultne vsechny produkty 0 cenu, tazke skryvani nedava smysl
        if ($this->configuration->isB2BMode() || $this->configuration->isB2BBasicMode()) {
            return;
        }

        // skryju varianty, ktere maji nulovou cenu
        sqlQueryBuilder()
            ->update('products_variations', 'pv')
            ->join('pv', 'products', 'p', 'p.id = pv.id_product')
            ->set('pv.figure', ':figure')
            ->andWhere(Operator::equals(['pv.figure' => 'Y']))
            ->andWhere('COALESCE(pv.price, p.price) <= 0')
            ->setParameter('figure', 'N')
            ->execute();

        // skryju produkty, ktere maji nulovou cenu
        sqlQueryBuilder()
            ->update('products', 'p')
            // joinuju pouze viditelne varianty, abych pri podmince COALESCE(pv.price, p.price) bral v potaz jen varianty, co jsou videt
            ->leftJoin('p', 'products_variations', 'pv', 'p.id = pv.id_product AND pv.figure = "Y"')
            ->set('p.figure', ':figure')
            ->andWhere(Operator::equals(['p.figure' => 'Y']))
            ->andWhere('COALESCE(pv.price, p.price) <= 0')
            ->setParameter('figure', 'N')
            ->execute();

        // pro kazdou mutaci jeste
        $languageContext = Contexts::get(LanguageContext::class);

        foreach ($languageContext->getAll() as $lang) {
            if ($lang->getId() === $languageContext->getDefaultId()) {
                continue;
            }

            $this->contextManager->activateContexts([LanguageContext::class => $lang->getId()], function () use ($lang) {
                $priceListContext = Contexts::get(PricelistContext::class);
                $priceListContext->clearCache();

                $productsQb = sqlQueryBuilder()
                    ->update('products_translations', 'pt')
                    ->join('pt', 'products', 'p', 'pt.id_product = p.id')
                    ->set('pt.figure', ':figure')
                    ->setParameter('figure', 'N')
                    ->andWhere(Operator::equals(['pt.id_language' => $lang->getId()]))
                    ->andWhere('pt.figure IS NULL OR pt.figure = "Y"')
                    ->andWhere(Operator::not(static::withNonZeroPrice($lang->getId())));

                $variationsQb = sqlQueryBuilder()
                    ->update('products_variations_translations', 'pvt')
                    ->join('pvt', 'products_variations', 'pv', 'pvt.id_products_variation = pv.id')
                    ->join('pv', 'products', 'p', 'p.id = pv.id_product')
                    ->set('pvt.figure', ':figure')
                    ->setParameter('figure', 'N')
                    ->andWhere(Operator::equals(['pvt.id_language' => $lang->getId()]))
                    ->andWhere('pvt.figure IS NULL OR pvt.figure = "Y"')
                    ->andWhere(Operator::not(static::withNonZeroPrice($lang->getId(), true)));

                $productsQb->execute();
                $variationsQb->execute();

                $priceListContext->forceEmpty();
            });
        }
    }

    private function updateVariationsVisibility(string $language, ?array $productIds = null): void
    {
        $languageContext = Contexts::get(LanguageContext::class);
        $website = $this->znzUtil->getCurrentWebsite($language);

        if ($language === $languageContext->getDefaultId()) {
            $this->executeQueryBuilder(
                // skryju varianty, ktere maji byt skryte
                sqlQueryBuilder()
                    ->update('products_variations', 'pv')
                    ->join('pv', 'products', 'p', 'pv.id_product = p.id')
                    ->leftJoin('pv', 'znz_products', 'zp', 'pv.id_product = zp.id_product AND zp.id_variation = pv.id')
                    ->leftJoin('zp', 'znz_products_website', 'zpw', 'zpw.id_znz = zp.id_znz AND zpw.id_website = :website')
                    ->set('pv.figure', ':figure')
                    // pokud produkt neni videt a ma byt videt a je viditelny pro aktualni website
                    ->andWhere('COALESCE(pv.figure, "Y") = "Y" AND (JSON_VALUE(zp.data, "$.znzFigure") = "N" OR zpw.id_znz IS NULL)')
                    ->addParameters(['figure' => 'N', 'website' => $website]),
                $productIds
            );

            $this->executeQueryBuilder(
                // zobrazim varianty, ktere maji byt videt
                sqlQueryBuilder()
                    ->update('products_variations', 'pv')
                    ->join('pv', 'products', 'p', 'pv.id_product = p.id')
                    ->leftJoin('pv', 'znz_products', 'zp', 'pv.id_product = zp.id_product AND zp.id_variation = pv.id')
                    ->leftJoin('zp', 'znz_products_website', 'zpw', 'zpw.id_znz = zp.id_znz AND zpw.id_website = :website')
                    ->set('pv.figure', ':figure')
                    // pokud produkt neni videt a ma byt videt a je viditelny pro aktualni website
                    ->andWhere('COALESCE(pv.figure, "N") = "N" AND JSON_VALUE(zp.data, "$.znzFigure") = "Y" AND zpw.id_znz IS NOT NULL')
                    // zobrazuju pouze varianty, ktere nemaji nulovou cenu
                    ->andWhere(static::withNonZeroPrice($language, true))
                    ->addParameters(['figure' => 'Y', 'website' => $website]),
                $productIds
            );
        } else {
            $this->executeQueryBuilder(
                // skryju varianty, ktere maji byt skryte v prekladech
                sqlQueryBuilder()
                    ->update('products_variations_translations', 'pvt')
                    ->join('pvt', 'products_variations', 'pv', 'pv.id = pvt.id_products_variation')
                    ->join('pv', 'products', 'p', 'pv.id_product = p.id')
                    ->leftJoin('pv', 'znz_products', 'zp', 'pv.id_product = zp.id_product AND zp.id_variation = pv.id')
                    ->leftJoin('zp', 'znz_products_website', 'zpw', 'zpw.id_znz = zp.id_znz AND zpw.id_website = :website')
                    ->set('pvt.figure', ':figure')
                    // pokud produkt neni videt a ma byt videt a je viditelny pro aktualni website
                    ->andWhere('COALESCE(pvt.figure, "Y") = "Y" AND (JSON_VALUE(zp.data, "$.znzFigure") = "N" OR zpw.id_znz IS NULL)')
                    ->andWhere(Operator::equals(['pvt.id_language' => $language]))
                    ->addParameters(['figure' => 'N', 'website' => $website]),
                $productIds
            );

            $this->executeQueryBuilder(
                // zobrazim varianty, ktere maji byt videt v prekladech
                sqlQueryBuilder()
                    ->update('products_variations_translations', 'pvt')
                    ->join('pvt', 'products_variations', 'pv', 'pv.id = pvt.id_products_variation')
                    ->join('pv', 'products', 'p', 'pv.id_product = p.id')
                    ->leftJoin('pv', 'znz_products', 'zp', 'pv.id_product = zp.id_product AND zp.id_variation = pv.id')
                    ->leftJoin('zp', 'znz_products_website', 'zpw', 'zpw.id_znz = zp.id_znz AND zpw.id_website = :website')
                    ->set('pvt.figure', ':figure')
                    // pokud produkt neni videt a ma byt videt a je viditelny pro aktualni website
                    ->andWhere('COALESCE(pvt.figure, "N") = "N" AND JSON_VALUE(zp.data, "$.znzFigure") = "Y" AND zpw.id_znz IS NOT NULL')
                    ->andWhere(Operator::equals(['pvt.id_language' => $language]))
                    // zobrazuju pouze varianty, ktere nemaji nulovou cenu
                    ->andWhere(static::withNonZeroPrice($language, true))
                    ->addParameters(['figure' => 'Y', 'website' => $website]),
                $productIds
            );
        }
    }

    private function executeQueryBuilder(QueryBuilder $qb, ?array $productIds = null): void
    {
        if ($productIds) {
            $qb->andWhere(Operator::inIntArray($productIds, 'p.id'));
        }

        $qb->execute();
    }

    private static function withTitle(): callable
    {
        return fn () => "pt.title != ''";
    }

    private static function withNonZeroPrice(string $language, bool $variations = false): callable
    {
        $priceListId = ServiceContainer::getService(ContextManager::class)->activateContexts([LanguageContext::class => $language], function () {
            $priceListContext = Contexts::get(PricelistContext::class);

            $priceListContext->clearCache();
            $priceListId = $priceListContext->getActiveId();
            $priceListContext->forceEmpty();

            return $priceListId;
        });

        return function (QueryBuilder $qb) use ($priceListId, $variations) {
            if ($priceListId) {
                if ($variations) {
                    $qb->leftJoin('p', 'pricelists_products', 'prp', 'prp.id_product = p.id AND prp.id_variation IS NULL AND prp.id_pricelist = :priceListId')
                        ->leftJoin('pv', 'pricelists_products', 'prv', 'prv.id_variation = pv.id AND prv.id_pricelist = :priceListId')
                        ->setParameter('priceListId', $priceListId);

                    $priceField = 'COALESCE(prv.price, pv.price, prp.price, p.price)';
                } else {
                    $qb->leftJoin('p', 'pricelists_products', 'prp', 'prp.id_product = p.id AND prp.id_variation IS NULL AND prp.id_pricelist = :priceListId')
                        ->setParameter('priceListId', $priceListId);

                    $priceField = 'COALESCE(prp.price, p.price)';
                }
            } else {
                $priceField = $variations ? 'COALESCE(pv.price, p.price)' : 'p.price';
            }

            return "{$priceField} > 0";
        };
    }
}
