<?php

declare(strict_types=1);

/*
 * 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\Model;

use CpCreation\VitiCore\Behavior\HasTimestamp;
use CpCreation\VitiCore\Behavior\Impl\Timestamp;
use CpCreation\VitiCore\Product\Model\Product;
use CpCreation\VitiCore\Product\Model\ProductGift;
use CpCreation\VitiCore\Product\Model\ProductVariant;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;

#[ORM\Entity(repositoryClass: 'CpCreation\VitiCore\Cart\Repository\CartItemRepository')]
#[ORM\Table(name: 'cart_cart_item')]
#[ORM\HasLifecycleCallbacks]
class CartItem implements HasTimestamp
{
    use Timestamp;

    #[ORM\Id]
    #[ORM\Column(type: 'uuid')]
    #[ORM\GeneratedValue(strategy: 'NONE')]
    private UuidInterface $id;

    #[ORM\Column(type: 'integer')]
    #[Assert\NotBlank(groups: ['Default', 'add_to_cart'])]
    #[Assert\Type(type: 'integer', groups: ['Default', 'add_to_cart'])]
    /*#[Assert\GreaterThanOrEqual(
        value: 1,
        message: 'error.quantity_min', groups: ['Default', 'add_to_cart']
    )]*/
    private ?int $quantity = 1;

    #[ORM\ManyToOne(targetEntity: Cart::class, inversedBy: 'items')]
    #[ORM\JoinColumn(name: 'cart_cart_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
    private ?Cart $cart = null;

    #[ORM\ManyToOne(targetEntity: ProductVariant::class)]
    #[ORM\JoinColumn(name: 'product_variant_id', referencedColumnName: 'id')]
    #[Assert\NotBlank(message: 'error.variant_required')]
    private ?ProductVariant $variant = null;

    /**
     * @var Collection<int, ProductGift>
     */
    #[ORM\OneToMany(targetEntity: ProductGift::class, mappedBy: 'cartItem', cascade: ['all'])]
    private Collection $gifts;

    public function __construct()
    {
        $this->id = Uuid::uuid4();
        $this->gifts = new ArrayCollection();
    }

    public static function create(?Product $product = null): self
    {
        $item = new self();
        if ($product) {
            $item->setVariant($product->getVariantByDefault());
        }

        return $item;
    }

    public function getId(): UuidInterface
    {
        return $this->id;
    }

    public function getQuantity(): int
    {
        return max(0, $this->quantity ?? 0);
    }

    public function setQuantity(?int $quantity): void
    {
        $this->quantity = $quantity;
    }

    public function getVariant(): ?ProductVariant
    {
        return $this->variant;
    }

    public function setVariant(?ProductVariant $variant): void
    {
        $this->variant = $variant;
    }

    public function getCart(): ?Cart
    {
        return $this->cart;
    }

    public function setCart(?Cart $cart): void
    {
        $this->cart = $cart;
    }

    public function getTax(): ?float
    {
        return null !== $this->getCart()?->getTax() ? (float) $this->getCart()?->getTax() : null;
    }

    public function getTaxRate(): float
    {
        return max(0.0, $this->getTax() ?? 0.0);
    }

    public function getPriceTTC(): int
    {

        return max(0, $this->getVariant()?->getVariantPriceByGroupAvailable()->getPrice());
    }

    public function getPriceDiscountTTC(): int
    {
        return max(0, $this->getVariant()?->getVariantPriceByGroupAvailable()->getPriceDiscount());
    }

    /**
     * Prix unitaire HT en centimes, déduit depuis le TTC.
     */
    public function getUnitPriceHT(): int
    {
        return $this->computeHTFromTTC($this->getPriceTTC(), $this->getTaxRate());
    }

    /**
     * Prix unitaire remisé HT en centimes, déduit depuis le TTC remisé.
     */
    public function getUnitPriceDiscountHT(): int
    {
        return $this->computeHTFromTTC($this->getPriceDiscountTTC(), $this->getTaxRate());
    }

    /**
     * TVA unitaire en centimes, déduite depuis le TTC.
     */
    public function getUnitTaxAmount(): int
    {
        return $this->getPriceTTC() - $this->getUnitPriceHT();
    }

    /**
     * TVA unitaire remisée en centimes.
     */
    public function getUnitDiscountTaxAmount(): int
    {
        return $this->getPriceDiscountTTC() - $this->getUnitPriceDiscountHT();
    }

    /**
     * Total TTC ligne en centimes.
     */
    public function getTotalTTC(): int
    {
        return $this->getQuantity() * $this->getPriceTTC();
    }

    /**
     * Total TTC remisé ligne en centimes.
     */
    public function getTotalDiscountTTC(): int
    {
        return $this->getQuantity() * $this->getPriceDiscountTTC();
    }

    /**
     * Total HT ligne en centimes, déduit depuis le TTC total de ligne.
     */
    public function getTotalHT(): int
    {
        return $this->computeHTFromTTC($this->getTotalTTC(), $this->getTaxRate());
    }

    /**
     * Total HT remisé ligne en centimes.
     */
    public function getTotalDiscountHT(): int
    {
        return $this->computeHTFromTTC($this->getTotalDiscountTTC(), $this->getTaxRate());
    }

    /**
     * Total TVA ligne en centimes, déduit comme résiduel depuis le TTC.
     */
    public function getTotalTax(): int
    {
        return $this->getTotalTTC() - $this->getTotalHT();
    }

    /**
     * Total TVA remisé ligne en centimes.
     */
    public function getTotalDiscountTax(): int
    {
        return $this->getTotalDiscountTTC() - $this->getTotalDiscountHT();
    }

    /**
     * Montant total de la remise en centimes.
     */
    public function getTotalDiscountAmount(): int
    {
        return max(0, $this->getTotalTTC() - $this->getTotalDiscountTTC());
    }

    /**
     * @return Collection<int, ProductGift>
     */
    public function getGifts(): Collection
    {
        return $this->gifts;
    }

    public function addGift(ProductGift $gift): void
    {
        if ($this->gifts->contains($gift)) {
            return;
        }

        $gift->setCartItem($this);
        $this->gifts->add($gift);
    }

    public function removeGift(ProductGift $gift): void
    {
        if (!$this->gifts->contains($gift)) {
            return;
        }

        $this->gifts->removeElement($gift);

        if ($gift->getCartItem() === $this) {
            $gift->setCartItem(null);
        }
    }

    public function removeGifts(): void
    {
        foreach ($this->getGifts() as $gift) {
            $this->removeGift($gift);
        }
    }

    public function getFinalPriceTTC(): int
    {
        if ($this->getPriceDiscountTTC() > 0) {
            return $this->getPriceDiscountTTC();
        }

        return $this->getPriceTTC();
    }

    public function getTotalFinalTTC(): int
    {
        return $this->quantity * $this->getFinalPriceTTC();
    }

    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 roundMoney(float $value): int
    {
        return (int) round($value, 0, \PHP_ROUND_HALF_UP);
    }

    #[Assert\Callback(groups: ['Default', 'add_to_cart'])]
    public function validate(ExecutionContextInterface $context): void
    {
        $variant = $this->getVariant();
        if (!$variant) {
            return;
        }

        if (!$variant->isAvailable()) {
            $context->buildViolation('shop.error.unavailable')
                ->setParameter('%name%', (string) $variant->getLabel())
                ->atPath('variant')
                ->addViolation();
        }

        $currentQuantityForVariantInCart = 0;
        $currentQuantityForTypeInCart = 0;
        $variantId = $variant->getId()->toString();
        $typeId = $variant->getType()->getId()->toString();

        foreach ($this->getCart()?->getItems() ?? [] as $cartItem) {
            if ($cartItem === $this) {
                continue;
            }

            $cartItemVariant = $cartItem->getVariant();

            if (null === $cartItemVariant) {
                continue;
            }

            if ($cartItemVariant->getId()->toString() === $variantId) {
                $currentQuantityForVariantInCart += $cartItem->getQuantity();
            }

        if ($cartItemVariant->getType()->getId()->toString() === $typeId) {
            $currentQuantityForTypeInCart += $cartItem->getQuantity();
        }
    }

        $lotQuantity = max(1, $variant->getLot() ?? 1);
        $totalQuantityForVariant = $currentQuantityForVariantInCart + $this->getQuantity();

        if ($lotQuantity > 1 && $totalQuantityForVariant > 0 && 0 !== ($totalQuantityForVariant % $lotQuantity)) {
            $context->buildViolation('shop.error.quantity_variant_lot')
                ->setParameter('%name%', (string) $variant->getFullName())
                ->setParameter('%quantity%', (string) $lotQuantity)
                ->setTranslationDomain('validators')
                ->atPath('quantity')
                ->addViolation();

            return;
        }

        $variantStock = $variant->getStock();

        if ($variantStock && $currentQuantityForVariantInCart + $this->getQuantity() > $variantStock) {
            $context->buildViolation('shop.error.stock_limit')
                ->setParameter('%name%', (string) $variant->getLabel())
                ->setParameter('%quantity%', (string) $this->getQuantity())
                ->setParameter('%stock%', (string) $variantStock)
                ->setTranslationDomain('validators')
                ->atPath('quantity')
                ->addViolation();

            return;
        }

        $maximumQuantityCart = $variant->getType()?->getMaximumQuantityCart();

        if (null !== $maximumQuantityCart && $currentQuantityForTypeInCart + $this->getQuantity() > $maximumQuantityCart) {
            $context->buildViolation('shop.error.maximum_quantity_cart')
                ->setParameter('%name%', (string) $variant->getLabel())
                ->setParameter('%quantity%', (string) $this->getQuantity())
                ->setParameter('%max%', (string) $maximumQuantityCart)
                ->setTranslationDomain('validators')
                ->atPath('quantity')
                ->addViolation();
        }
    }

    public function isDiscounted(): bool
    {
        return null !== $this->getPriceDiscountTTC()
            && $this->getPriceDiscountTTC() > 0
            && $this->getPriceDiscountTTC() < $this->getPriceTTC();
    }
}
