<?php

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Cart;

use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Product\DTO\DegressiveAllocation;
use CpCreation\VitiCore\Product\DTO\DegressiveResult;
use CpCreation\VitiCore\Product\Model\ProductPriceGroup;

final class CartPricingFactory
{
    public function createFromCart(
        Cart $cart,
        ?DegressiveResult $degressiveResult = null,
        ?ProductPriceGroup $priceGroup = null,
    ): CartPricing {
        $cartTaxRate = max(0.0, null !== $cart->getTax() ? (float) $cart->getTax() : 0.0);
        $cartShipping = $cart->getCartShipping();
        $shippingTaxRate = max(
            0.0,
            null !== $cartShipping ? $cartShipping->taxRate : $cartTaxRate
        );
        $shippingTTC = max(0, (int) ($cartShipping?->amountTTC ?? 0));
        $shippingHT = $this->computeHTFromTTC($shippingTTC, $shippingTaxRate);

        $itemLines = $this->buildItemLines($cart, $cartTaxRate, $this->buildDegressiveMap($degressiveResult));
        $itemLines = array_merge($this->buildPromoGiftLines($cart), $itemLines);

        [
            'subtotalTTC' => $subtotalTTC,
            'subtotalHT' => $subtotalHT,
            'subtotalTax' => $subtotalTax,
            'catalogDiscountTTC' => $catalogDiscountTTC,
            'catalogDiscountHT' => $catalogDiscountHT,
            'degressiveTTC' => $degressiveTTC,
            'degressiveHT' => $degressiveHT,
        ] = $this->aggregateLines($itemLines);

        $catalogDiscountTax = $catalogDiscountTTC - $catalogDiscountHT;
        $degressiveTax = $degressiveTTC - $degressiveHT;

        ['itemsTTC' => $promoItemsTTC, 'shippingTTC' => $promoShippingTTC] = $this->aggregatePromos($cart);

        $itemsNetTTC = max(0, $subtotalTTC);
        $shippingNetTTC = $shippingTTC;

        $promoItemsTTC = min($promoItemsTTC, $itemsNetTTC);
        $promoShippingTTC = min($promoShippingTTC, $shippingNetTTC);

        $promoTTC = $promoItemsTTC + $promoShippingTTC;
        $promoItemsHT = $this->computeHTFromTTC($promoItemsTTC, $cartTaxRate);
        $promoShippingHT = $this->computeHTFromTTC($promoShippingTTC, $shippingTaxRate);
        $promoHT = $promoItemsHT + $promoShippingHT;
        $promoTax = $promoTTC - $promoHT;

        $discountTTC = $catalogDiscountTTC + $promoTTC;
        $discountHT = $catalogDiscountHT + $promoHT;
        $discountTax = $catalogDiscountTax + $promoTax;

        $totalTTC = max(0, $subtotalTTC + $shippingTTC - $promoTTC);
        $totalHT = max(0, $subtotalHT + $shippingHT - $promoHT);
        $totalTax = $totalTTC - $totalHT;

        return new CartPricing(
            lines: $itemLines,
            taxRate: $cartTaxRate,
            subtotalHT: $subtotalHT,
            subtotalTax: $subtotalTax,
            subtotalTTC: $subtotalTTC,
            discountHT: $discountHT,
            discountTax: $discountTax,
            discountTTC: $discountTTC,
            degressiveHT: $degressiveHT,
            degressiveTax: $degressiveTax,
            degressiveTTC: $degressiveTTC,
            degressiveIsBottleOffered: $degressiveResult?->isBottleOffered ?? false,
            totalHT: $totalHT,
            totalTax: $totalTax,
            totalTTC: $totalTTC,
            priceGroupId: $priceGroup?->getId()->toString(),
            priceGroupName: $priceGroup?->getName(),
        );
    }

    /**
     * @return array<string, array{totalTTC: int, unitTTC: ?int}>
     */
    private function buildDegressiveMap(?DegressiveResult $degressiveResult): array
    {
        $degressiveMap = [];

        if (null === $degressiveResult) {
            return $degressiveMap;
        }

        foreach ($degressiveResult->allocations as $allocation) {
            if (!$allocation instanceof DegressiveAllocation) {
                continue;
            }

            $degressiveMap[$allocation->item->getId()->toString()] = [
                'totalTTC' => max(0, $allocation->amountTTC),
                'unitTTC' => null !== $allocation->unitAmountTTC ? max(0, $allocation->unitAmountTTC) : null,
            ];
        }

        return $degressiveMap;
    }

    /**
     * @param array<string, array{totalTTC: int, unitTTC: ?int}> $degressiveMap
     *
     * @return list<CartPricingLine>
     */
    private function buildItemLines(Cart $cart, float $cartTaxRate, array $degressiveMap): array
    {
        $lines = [];

        foreach ($cart->getItems() as $item) {
            $variant = $item->getVariant();
            $product = $variant?->getProduct();
            $type = $variant?->getType();
            $itemId = $item->getId()->toString();
            $optionTotalTTC = $item->getSelectedOptionTotalTTC();
            $optionTotalHT = $item->getSelectedOptionTotalHT();
            $baseTotalTTC = max(0, $item->getProductTotalTTC());
            $baseTotalDiscountTTC = max(0, $item->getProductTotalDiscountTTC());
            $totalTTC = max(0, $item->getTotalTTC());
            $totalDiscountTTC = $item->isDiscounted() ? max(0, $item->getTotalDiscountAmount()) : 0;
            $degressiveMeta = $degressiveMap[$itemId] ?? ['totalTTC' => 0, 'unitTTC' => null];
            $degressiveTTC = min($degressiveMeta['totalTTC'], max(0, $baseTotalDiscountTTC > 0 ? $baseTotalDiscountTTC : $baseTotalTTC));
            $unitDegressiveTTC = $degressiveMeta['unitTTC'];
            $netBaseTTC = max(0, ($baseTotalDiscountTTC > 0 ? $baseTotalDiscountTTC : $baseTotalTTC) - $degressiveTTC);
            $netTTC = $netBaseTTC + $optionTotalTTC;
            $netHT = $this->computeHTFromTTC($netTTC, $cartTaxRate);

            $lines[] = new CartPricingLine(
                itemId: $itemId,
                productId: $product?->getId()?->toString(),
                productName: $product?->getFullName(),
                variantId: $variant?->getId()?->toString(),
                variantFullName: $variant?->getFullName(),
                typeId: $type?->getId()?->toString(),
                typeName: $type?->getName(),
                unitLabel: $this->buildUnitLabel($type?->getVolume(), $type?->getUnity()),
                imagePath: $product?->getSecondImage()?->getPath() ?: $product?->getImage()?->getPath(),
                quantity: max(0, $item->getQuantity()),
                unitPriceHT: $this->computeHTFromTTC($item->getPriceTTC(), $cartTaxRate),
                unitPriceTTC: max(0, $item->getPriceTTC()),
                unitPriceDiscountHT: $item->isDiscounted() ? $this->computeHTFromTTC($item->getPriceDiscountTTC(), $cartTaxRate) : null,
                unitPriceDiscountTTC: $item->isDiscounted() ? max(0, $item->getPriceDiscountTTC()) : null,
                totalHT: $this->computeHTFromTTC($totalTTC, $cartTaxRate),
                totalTTC: $totalTTC,
                totalDiscountTTC: $totalDiscountTTC,
                totalDiscountHT: $this->computeHTFromTTC($totalDiscountTTC, $cartTaxRate),
                unitDegressiveTTC: $unitDegressiveTTC,
                unitDegressiveHT: null !== $unitDegressiveTTC ? $this->computeHTFromTTC($unitDegressiveTTC, $cartTaxRate) : null,
                degressiveTTC: $degressiveTTC,
                degressiveHT: $this->computeHTFromTTC($degressiveTTC, $cartTaxRate),
                netTTC: $netTTC,
                netHT: $netHT,
                tax: $netTTC - $netHT,
                optionName: $item->getSelectedOptionName(),
                optionFlatFee: $item->isSelectedOptionFlatFee(),
                optionPriceTTC: $item->getSelectedOptionPrice(),
                optionPriceHT: $this->computeHTFromTTC($item->getSelectedOptionPrice(), $cartTaxRate),
                optionTotalTTC: $optionTotalTTC,
                optionTotalHT: $optionTotalHT,
                option: $item->hasSelectedOption() ? [
                    'name' => $item->getSelectedOptionName(),
                    'flatFee' => $item->isSelectedOptionFlatFee(),
                    'priceTTC' => $item->getSelectedOptionPrice(),
                    'priceHT' => $this->computeHTFromTTC($item->getSelectedOptionPrice(), $cartTaxRate),
                    'totalTTC' => $optionTotalTTC,
                    'totalHT' => $optionTotalHT,
                ] : null,
            );
        }

        return $lines;
    }

    /**
     * @return list<CartPricingLine>
     */
    private function buildPromoGiftLines(Cart $cart): array
    {
        $lines = [];

        foreach ($cart->getPromos() as $promo) {
            if (null === $promo->giftVariantId || $promo->giftQuantity <= 0) {
                continue;
            }

            $giftOriginalUnitPriceTTC = max(0, (int) ($promo->giftOriginalUnitPriceTTC ?? 0));
            $giftTotalTTC = $giftOriginalUnitPriceTTC * $promo->giftQuantity;
            $giftTotalHT = $this->computeHTFromTTC($giftTotalTTC, null !== $cart->getTax() ? (float) $cart->getTax() : 0.0);
            $giftUnitHT = $promo->giftQuantity > 0 ? $this->computeHTFromTTC($giftOriginalUnitPriceTTC, null !== $cart->getTax() ? (float) $cart->getTax() : 0.0) : 0;

            $lines[] = new CartPricingLine(
                itemId: null,
                productId: null,
                productName: $promo->giftProductName,
                variantId: $promo->giftVariantId,
                variantFullName: $promo->giftProductName,
                typeId: null,
                typeName: $promo->giftTypeName,
                unitLabel: $promo->giftUnitLabel,
                imagePath: $promo->giftImagePath,
                quantity: $promo->giftQuantity,
                unitPriceHT: $giftUnitHT,
                unitPriceTTC: $giftOriginalUnitPriceTTC,
                unitPriceDiscountHT: null,
                unitPriceDiscountTTC: null,
                totalHT: $giftTotalHT,
                totalTTC: $giftTotalTTC,
                totalDiscountTTC: $giftTotalTTC,
                totalDiscountHT: $giftTotalHT,
                unitDegressiveTTC: null,
                unitDegressiveHT: null,
                degressiveTTC: 0,
                degressiveHT: 0,
                netTTC: 0,
                netHT: 0,
                tax: 0,
                isPromoGift: true,
            );
        }

        return $lines;
    }

    /**
     * @param list<CartPricingLine> $lines
     *
     * @return array{
     *     subtotalTTC: int,
     *     subtotalHT: int,
     *     subtotalTax: int,
     *     catalogDiscountTTC: int,
     *     catalogDiscountHT: int,
     *     degressiveTTC: int,
     *     degressiveHT: int
     * }
     */
    private function aggregateLines(array $lines): array
    {
        $subtotalTTC = 0;
        $subtotalHT = 0;
        $subtotalTax = 0;
        $catalogDiscountTTC = 0;
        $catalogDiscountHT = 0;
        $degressiveTTC = 0;
        $degressiveHT = 0;

        foreach ($lines as $line) {
            $subtotalTTC += $line->netTTC;
            $subtotalHT += $line->netHT;
            $subtotalTax += $line->tax;

            if ($line->isPromoGift) {
                continue;
            }

            $catalogDiscountTTC += $line->totalDiscountTTC;
            $catalogDiscountHT += $line->totalDiscountHT;
            $degressiveTTC += $line->degressiveTTC;
            $degressiveHT += $line->degressiveHT;
        }

        return [
            'subtotalTTC' => $subtotalTTC,
            'subtotalHT' => $subtotalHT,
            'subtotalTax' => $subtotalTax,
            'catalogDiscountTTC' => $catalogDiscountTTC,
            'catalogDiscountHT' => $catalogDiscountHT,
            'degressiveTTC' => $degressiveTTC,
            'degressiveHT' => $degressiveHT,
        ];
    }

    /**
     * @return array{itemsTTC: int, shippingTTC: int}
     */
    private function aggregatePromos(Cart $cart): array
    {
        $promoItemsTTC = 0;
        $promoShippingTTC = 0;

        foreach ($cart->getPromos() as $promo) {
            $promoItemsTTC += max(0, $promo->discountItemsTTC);
            $promoShippingTTC += max(0, $promo->discountShippingTTC);
        }

        return [
            'itemsTTC' => $promoItemsTTC,
            'shippingTTC' => $promoShippingTTC,
        ];
    }

    private function computeHTFromTTC(int $ttc, float $taxRate): int
    {
        if ($ttc <= 0) {
            return 0;
        }

        if ($taxRate <= 0.0) {
            return $ttc;
        }

        return $this->roundMoney($ttc / (1 + $taxRate));
    }

    private function buildUnitLabel(?float $volume, ?string $unity): ?string
    {
        if (null === $volume && (null === $unity || '' === mb_trim($unity))) {
            return null;
        }

        $parts = [];

        if (null !== $volume) {
            $parts[] = mb_rtrim(mb_rtrim(number_format($volume, 3, '.', ''), '0'), '.');
        }

        if (null !== $unity && '' !== mb_trim($unity)) {
            $parts[] = mb_trim($unity);
        }

        return '' !== implode(' ', $parts) ? implode(' ', $parts) : null;
    }

    private function roundMoney(float $value): int
    {
        return (int) round($value, 0, \PHP_ROUND_HALF_UP);
    }
}
