<?php

declare(strict_types=1);

namespace KupShop\ProductSetsBundle\EventListener;

use KupShop\KupShopBundle\Context\ContextManager;
use KupShop\KupShopBundle\Context\PriceLevelContext;
use KupShop\OrderingBundle\Event\OrderItemEvent;
use Query\Operator;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class OrderItemSubscriber implements EventSubscriberInterface
{
    public function __construct(private readonly ContextManager $contextManager)
    {
    }

    public static function getSubscribedEvents(): array
    {
        if (findModule(\Modules::PRODUCTS, \Modules::SUB_SETS) !== 'js_multisets' && !isFunctionalTests()) {
            return [];
        }

        return [
            OrderItemEvent::CALCULATE_PRICE => [
                ['calculateMultiSetPrice', 200],
            ],
        ];
    }

    public function calculateMultiSetPrice(OrderItemEvent $event): void
    {
        $dbcfg = \Settings::getDefault();

        // by default use multiset price
        $price = $event->getPrice();

        if ($calculationType = ($dbcfg->multisets['price_calc_type'] ?? null)) {
            $method = 'calculate'.ucfirst($calculationType);
            if (method_exists($this, $method)) {
                $price = $this->{$method}($event);
            }
        }

        $event->setPrice($price);
    }

    /**
     * Vypočítat cenu podle položek, ze kterých se set skládá + aplikovat slevu ze setového produktu.
     */
    private function calculateProductsPriceDiscounted(OrderItemEvent $event): \Decimal
    {
        $price = $event->getPrice();
        $data = $event->getData();

        if (isset($event->getProduct()->campaign_codes['MS']) && !empty($data['sets'])) {
            $finalProductPrice = \DecimalConstants::zero();

            // spocitam celkovou cenu multisetu podle cen jednotlivych polozek
            foreach ($data['sets'] as $setProduct) {
                if (empty($setProduct['id_product'])) {
                    continue;
                }

                $productPrice = $this->getSetProductPrice(
                    $event->getProduct(),
                    $setProduct
                );

                $finalProductPrice = $finalProductPrice->add($productPrice);
            }

            $price = $finalProductPrice;
        }

        return $price;
    }

    /**
     * Vypočítat cenu podle položek bez aplikované cenové hladiny, ze kterých se set skládá + aplikovat slevu ze setového produktu.
     */
    private function calculateProductsPriceDiscountedWithoutPriceLevel(OrderItemEvent $event): \Decimal
    {
        return $this->contextManager->activateContexts(
            [PriceLevelContext::class => ContextManager::FORCE_EMPTY],
            fn () => $this->calculateProductsPriceDiscounted($event)
        );
    }

    private function getSetProductPrice(\Product $masterProduct, array $setProduct): \Decimal
    {
        $product = \Variation::createProductOrVariation($setProduct['id_product'], $setProduct['id_variation'] ?: null);
        $product->createFromDB();

        $customSetProductPrice = null;

        if (!empty($setProduct['id_category'])) {
            $qb = sqlQueryBuilder()
                ->select('COALESCE(spv.price, sp.price) as price')
                ->from('set_products', 'sp')
                ->leftJoin('sp', 'set_products_variations', 'spv', 'spv.id_set_product = sp.id')
                ->andWhere(Operator::equals(
                    [
                        'sp.id_product_master' => $masterProduct->id,
                        'sp.id_product' => $setProduct['id_product'],
                        'sp.category' => $setProduct['id_category'],
                    ]
                ));

            if (!empty($setProduct['id_variation'])) {
                $qb->andWhere(Operator::equals(['spv.id_variation' => $setProduct['id_variation']]));
            }

            if ($price = $qb->execute()->fetchOne()) {
                $customSetProductPrice = toDecimal($price);
            }
        }

        // pokud je nastavena cena primo u setove polozky, tak chci pouzit tu, jinak pouzivam cenu od produktu
        $productPrice = $customSetProductPrice ?: $product->getProductPrice()->getPriceWithoutVat();

        // vynasobim jeste poctem kusu
        return $productPrice->mul(toDecimal($setProduct['pieces'] ?? 1));
    }
}
