<?php

declare(strict_types=1);

namespace KupShop\CatalogBundle\Util\Sets;

use KupShop\KupShopBundle\Util\Price\Price;
use KupShop\KupShopBundle\Util\Price\PriceCalculator;
use KupShop\KupShopBundle\Util\Price\PriceLevelPrice;
use KupShop\KupShopBundle\Util\Price\ProductPrice;
use KupShop\KupShopBundle\Wrapper\PriceWrapper;

class SetsPriceCalculator
{
    /**
     * Calculates set price and updates `$product->productPrice` to calculated price.
     */
    public function calculatePrice(\Product $product): void
    {
        if (!$product->sets) {
            return;
        }

        if (!$product->getProductPrice()) {
            return;
        }

        if (!findModule(\Modules::PRODUCT_SETS, \Modules::SUB_PRODUCT_SETS_CALCULATE_PRICE)) {
            return;
        }

        [$price, $discount, $priceForDiscount] = $this->getSetInitialPrices($product);

        $productPrice = new Price($price, $product->getProductPrice()->getCurrency(), $product->getProductPrice()->getVat());
        foreach ($product->sets as $setItem) {
            // add set price to product price
            if (!empty($setItem->getProductPrice())) {
                $productPrice = PriceCalculator::add(
                    $productPrice,
                    $this->getSetItemPrice($setItem)
                );
            }

            // calculate set price for discount to be correct according to set_pieces value
            if (!empty($setItem->priceForDiscount)) {
                $setPriceForDiscount = PriceCalculator::mul($setItem->priceForDiscount, toDecimal($setItem['set_pieces']));
                $priceForDiscount = $priceForDiscount ? PriceCalculator::add($priceForDiscount, $setPriceForDiscount) : $setPriceForDiscount;
            }
        }

        $newProductPrice = new ProductPrice(
            $productPrice->getPriceWithoutVat(),
            $productPrice->getCurrency(),
            $productPrice->getVat(),
            $discount
        );

        // keep it as price level price so price level discount is applied
        if ($product->getProductPrice() instanceof PriceLevelPrice) {
            $newProductPrice = $this->convertProductPriceToPriceLevelPrice($newProductPrice, $product->getProductPrice());
        }

        $this->updateSetPrices(
            $product,
            $newProductPrice,
            $priceForDiscount
        );
    }

    public function convertProductPriceToPriceLevelPrice(ProductPrice $productPrice, PriceLevelPrice $priceLevelPrice, bool $removeDiscount = true): PriceLevelPrice
    {
        $priceLevelDiscount = $priceLevelPrice->getPricelevelDiscount();
        $newProductPriceValue = $productPrice->getValue();
        if ($removeDiscount && !empty($priceLevelDiscount['discount']) && $priceLevelDiscount['unit'] === 'perc'
            && ($priceLevelDiscount['add'] || $productPrice->getDiscount()->isZero())) {
            $newProductPriceValue = $newProductPriceValue->removeDiscount($priceLevelDiscount['discount']);
        }

        $productPrice = new PriceLevelPrice(
            new ProductPrice(
                $newProductPriceValue,
                $productPrice->getCurrency(),
                $productPrice->getVat(),
                $productPrice->getDiscount(),
            )
        );
        $productPrice->setPricelevelDiscount($priceLevelPrice->getPricelevelDiscount());

        return $productPrice;
    }

    /**
     * @return array{0: \Decimal, 1: \Decimal, 3: \Decimal|null}
     *
     * 0: Price
     * 1: Discount
     * 2: Price for discount
     */
    protected function getSetInitialPrices(\Product $product): array
    {
        // TODO: pradlo-elegant hnus - asi by se to ted diky tyhle servise mohlo dat do pryc a prenest jen na shop
        // TODO: jeste by bylo potreba pak presunout ten hnus z KupShop\OrderingBundle\EventListener\OrderItemListener::calculateSetsPrice
        if (findModule(\Modules::PRODUCT_SETS, \Modules::SUB_PRODUCT_SETS_CALCULATE_PRICE) === 'pradlo-elegant') {
            $price = $product->getProductPrice()->getPriceWithoutVat();
            $discount = \DecimalConstants::zero(); // sleva je uz aplikovana u hlavniho produktu i setu, takze 0
            $priceForDiscount = null;
            if ($product->priceForDiscount) {
                $priceForDiscount = PriceCalculator::toPrice($product->priceForDiscount);
            }

            return [$price, $discount, $priceForDiscount];
        }

        $priceForDiscount = null;
        $price = \DecimalConstants::zero();
        $discount = $product->getProductPrice()->getDiscount();
        if ($product->getProductPrice() instanceof PriceLevelPrice) {
            $discount = $product->getProductPrice()->getOriginalPrice()->getDiscount();
        }

        return [$price, $discount, $priceForDiscount];
    }

    /**
     * Returns price of single set item.
     */
    protected function getSetItemPrice(\Product $product): Price
    {
        return new Price(
            $product->getProductPrice()->getPriceWithoutVat()->mul(toDecimal($product['set_pieces'])),
            $product->getProductPrice()->getCurrency(),
            $product->getProductPrice()->getVat()
        );
    }

    /**
     * Updates set prices by given prices.
     */
    protected function updateSetPrices(\Product $product, ProductPrice $setPrice, ?Price $priceForDiscount): void
    {
        // update main product productPrice
        $product->setProductPrice($setPrice);

        // update main product priceForDiscount
        if ($priceForDiscount) {
            $product->priceForDiscount = new ProductPrice(
                $priceForDiscount->getPriceWithoutVat(),
                $priceForDiscount->getCurrency(),
                $priceForDiscount->getVat()
            );
        }

        // set price to price_array for backward compatibility
        $product->price_array = PriceWrapper::wrap($product->getProductPrice());
        // update product priceOriginal
        // TODO: pradlo-elegant hnus - asi by se to ted diky tyhle servise mohlo dat do pryc a prenest jen na shop
        if (findModule(\Modules::PRODUCT_SETS, \Modules::SUB_PRODUCT_SETS_CALCULATE_PRICE) !== 'pradlo-elegant') {
            $product->priceOriginal = PriceWrapper::wrap($setPrice->getPriceWithoutDiscount());
        }
    }
}
