<?php

declare(strict_types=1);

namespace KupShop\CatalogBundle\Util\Product;

use KupShop\CatalogBundle\Entity\Product\ProductDiscountResult;
use KupShop\CatalogBundle\Entity\Product\ProductPricesResult;
use KupShop\CatalogBundle\Entity\Wrapper\ProductPricesResultWrapper;
use KupShop\CatalogBundle\ProductList\ProductCollection;
use KupShop\ContentBundle\Entity\ProductUnified;
use KupShop\KupShopBundle\Context\UserContext;
use KupShop\KupShopBundle\Util\Contexts;
use KupShop\KupShopBundle\Util\Price\Price;
use KupShop\KupShopBundle\Util\Price\PriceCalculator;
use KupShop\KupShopBundle\Util\Price\PriceLevelPrice;
use KupShop\PricelistBundle\Util\Price\PriceListPrice;

class ProductDiscountCalculator
{
    public function calculate(ProductUnified $productUnified, array $options = []): ProductDiscountResult|ProductPricesResultWrapper
    {
        if (findModule(\Modules::PRODUCTS, \Modules::SUB_MODERN_PRICES)) {
            $result = $this->calculateModern($productUnified);

            return ProductPricesResultWrapper::wrap($result);
        }

        $isB2B = $this->isB2B();

        $productDiscountResult = new ProductDiscountResult();
        $productDiscountResult->discount = \DecimalConstants::zero();
        $productDiscountResult->priceOriginal = null;
        $productDiscountResult->priceCommon = null;
        $productDiscountResult->isB2B = $isB2B;

        $productPrice = ($options['productPrice'] ?? false) ? $options['productPrice'] : $productUnified->getProductPrice();
        $priceCommon = ($options['priceCommon'] ?? false) ? $options['priceCommon'] : $productUnified->getPriceCommon();
        $priceForDiscount = ($options['priceForDiscount'] ?? false) ? $options['priceForDiscount'] : $productUnified->getPriceForDiscount();
        $originalPrice = ($options['originalPrice'] ?? false) ? $options['originalPrice'] : $productPrice->getOriginalPrice()->getPriceWithoutDiscount();

        if (!$productPrice->getValue()->isZero()) {
            // pokud nejsem B2B a mam aktivni cenik, tak nechci zobrazovat slevy mezi cenikem a vychozi cenou
            // ve chvili, kdy ale jsem B2B, tak uz dava smysl zobrazit slevu mezi cenikovou cenou a MOC cenou
            if ($this->showPriceWithoutDiscount($isB2B, $productPrice)) {
                $originalPrice = $productPrice->getPriceWithoutDiscount();
            }

            if ($priceCommon && !$isB2B/* && !($productPrice instanceof PriceListPrice) */) {
                if (PriceCalculator::firstLower($originalPrice, $priceCommon)) {
                    $originalPrice = $priceCommon;
                }
            }

            if (PriceCalculator::firstLower($productPrice, $originalPrice)) {
                $productDiscountResult->priceCommon = $originalPrice;
            }

            if ($priceForDiscount && $priceForDiscount->getValue()->isPositive() && !$isB2B) {
                // Zkonvertit ceny do stejne meny, pokud tomu tak neni - protoze muzu mit priceForDiscount z ceniku v jine mene
                $priceForDiscount = PriceCalculator::convert($priceForDiscount, $originalPrice->getCurrency());
                $productDiscountResult->priceForDiscount = $priceForDiscount;
                $originalPrice = $priceForDiscount;
            }

            if (findModule(\Modules::PRICE_LEVELS)) {
                $discountB2B = null;
                if ($productPrice instanceof PriceLevelPrice) {
                    $discountB2B = toDecimal($productPrice->getPricelevelDiscount()['discount'] ?? 0);
                }

                if ($discountB2B && $discountB2B->isPositive()) {
                    $originalPrice = PriceCalculator::firstGreater($originalPrice,
                        $productPrice->getOriginalPrice()->getPriceWithoutDiscount()) ? $originalPrice : $productPrice->getOriginalPrice()->getPriceWithoutDiscount();
                }
            }

            // Zkonvertit ceny do stejne meny, pokud tomu tak neni
            $originalPrice = PriceCalculator::convert($originalPrice, $productPrice->getCurrency());

            if (PriceCalculator::firstLower($productPrice, $originalPrice)) {
                $productDiscountResult->discount = \DecimalConstants::hundred()->sub($productPrice->getPriceWithVat()->mul(\DecimalConstants::hundred())->div($originalPrice->getPriceWithVat()));
                $productDiscountResult->priceOriginal = $originalPrice;
            }
        }

        if (!$productDiscountResult->discount->isZero() && !$isB2B) {
            $productDiscountResult->discount = $this->checkDiscountSettings($productDiscountResult->discount, $productUnified);
        }

        return $productDiscountResult;
    }

    public function calculateForProduct(\Product $product, array $options = []): ProductDiscountResult|ProductPricesResultWrapper
    {
        return $this->calculate(
            new ProductUnified($product),
            $options
        );
    }

    protected function isB2B(): bool
    {
        return Contexts::get(UserContext::class)->isDealer();
    }

    public function showPriceWithoutDiscount(bool $isB2B, mixed $productPrice): bool
    {
        return !$isB2B && $productPrice instanceof PriceListPrice;
    }

    protected function calculateModern(ProductUnified $product): ProductPricesResult
    {
        $userContext = Contexts::get(UserContext::class);

        $productPrice = $product->getProductPrice();

        $retailType = $userContext->getRetailPriceType();
        $retailPrice = $retailType->getPrice($product);
        $retailPriceDiscount = $this->getDiscount(originalPrice: $retailPrice, finalPrice: $productPrice, keepDefaultPrice: true);

        $originalType = $userContext->getOriginalPriceType();
        $originalPrice = $originalType->getPrice($product);
        $discount = $this->getDiscount(originalPrice: $originalPrice, finalPrice: $productPrice);

        if (!$discount->isZero()) {
            $discount = $this->checkDiscountSettings($discount, $product);
        }

        return new ProductPricesResult($productPrice, $originalPrice, $retailPrice, $discount, $retailPriceDiscount);
    }

    public function getDiscount(?Price &$originalPrice, Price $finalPrice, $keepDefaultPrice = false): \Decimal
    {
        $discount = \DecimalConstants::zero();

        if ($originalPrice && PriceCalculator::firstLower($finalPrice, $originalPrice)) {
            $originalPrice = PriceCalculator::convert($originalPrice, $finalPrice->getCurrency());

            $discount = \DecimalConstants::hundred()->sub($finalPrice->getPriceWithVat()->mul(\DecimalConstants::hundred())->div($originalPrice->getPriceWithVat()));
        } else {
            $originalPrice = $keepDefaultPrice ? $originalPrice : null;
        }

        return $discount;
    }

    protected function checkDiscountSettings(\Decimal $discount, ProductUnified $product): \Decimal
    {
        $display_discount = \Settings::getDefault()['prod_display_discount'] ?? null;
        if (($display_discount == 'Y') && $product->getDiscount()->isZero()) {
            // v nastaveni je zapnute zobrazeni slevy pouze pokud je vyplnena sleva primo na produktu,
            // produkt nema nastavenou slevu => nezobrazime zadnou, i kdyz muze byt levnejsi nez priceForDiscount
            $discount = \DecimalConstants::zero();
        }

        $display_discount = \Settings::getDefault()['prod_display_discount_price_common'] ?? null;
        if (($display_discount == 'Y') && $product->getDiscount()->isZero()
            && (!$product->getPriceCommon() || $product->getPriceCommon()->getValue()->isZero())) {
            // v nastaveni je zapnute zobrazeni slevy pouze pokud je vyplnena skrtla cena (nebo sleva na produktu),
            // produkt nema nastavenou skrtlou cenu => nezobrazime zadnou slevu, i kdyz muze byt levnejsi nez priceForDiscount
            $discount = \DecimalConstants::zero();
        }

        if (!$discount->isZero() && ($min_display_discount = (\Settings::getDefault()['prod_min_display_discount'] ?: 1))) {
            $min_display_discount = toDecimal(intval($min_display_discount)); // Minimální zobrazená sleva
            if ($discount->lowerThan($min_display_discount)) {
                $discount = \DecimalConstants::zero();
            }
        }

        return $discount;
    }

    public function prefetchPriceTypes(ProductCollection $productCollection): void
    {
        $userContext = Contexts::get(UserContext::class);

        $retailType = $userContext->getRetailPriceType();
        $retailType->prefetchProductCollection($productCollection);

        $originalType = $userContext->getOriginalPriceType();
        $originalType->prefetchProductCollection($productCollection);
    }
}
