<?php
/**
 * @author Colinet Julien
 */

namespace CpCreation\VitiCore\Cart\Model;

use CpCreation\VitiCore\Behavior\HasPayment;
use CpCreation\VitiCore\Behavior\HasTimestamp;
use CpCreation\VitiCore\Behavior\Impl\Timestamp;
use CpCreation\VitiCore\Product\Model\Product;
use CpCreation\VitiCore\Shop\Model\DeliveryMethod;
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 function PHPUnit\Framework\isEmpty;

/**
 * Class Cart
 *
 * @ORM\Entity(repositoryClass="CpCreation\VitiCore\Cart\Repository\CartRepository")
 * @ORM\Table(name="cart_cart", indexes={
 *     @ORM\Index(name="idx_cart_created_at", columns={"created_at"}),
 *     @ORM\Index(name="idx_cart_token", columns={"token"}, options={"lengths": {255}})
 * })
 * @ORM\HasLifecycleCallbacks()
 */
class Cart implements HasTimestamp, HasPayment
{
    use Timestamp;

    /**
     * @ORM\Id()
     * @ORM\Column(type="uuid")
     * @ORM\GeneratedValue(strategy="NONE")
     *
     * @var UuidInterface
     */
    private $id;

    /**
     * @ORM\Column(type="text", nullable=true)
     *
     * @var string
     */
    private $token;

    /**
     * @ORM\Column(type="integer", nullable=true)
     *
     * @var integer
     */
    private $shipping;

    /**
     * @ORM\Column(type="string", nullable=true)
     *
     * @var string | null
     */
    private $shippingName;

    /**
     * @ORM\Column(type="string", nullable=true)
     *
     * @var string
     */
    private $groupCode;

    /**
     * @ORM\Column(type="json", nullable=true)
     *
     * @var string
     */
    private $deliveryMethodJson;

    /**
     * @ORM\Column(type="string", nullable=true)
     *
     * @var string
     */
    private $promo;

    /**
     * @ORM\Column(type="uuid", nullable=true)
     *
     * @var UuidInterface
     */
    private $promoId;

    /**
     * @ORM\Column(type="integer", nullable=true)
     *
     * @var float | null
     */
    private $promoValueAmount;

    /**
     * @ORM\Column(type="decimal", precision=3, scale=2, nullable=true)
     *
     * @var float | null
     */
    private $promoValuePercent;

    /**
     * @ORM\Column(type="boolean", options={"default" : 0})
     *
     * @var boolean
     */
    private $promoWithShippingFees;
    /**
     * @ORM\Column(type="integer", nullable=true)
     *
     * @var integer
     */
    private $degressiveValue;

    /**
     * @ORM\Column(type="boolean", nullable=true)
     *
     * @var boolean
     */
    private $viewHT;

    /**
     * @ORM\Column(type="decimal", precision=3, scale=2, nullable=true)
     *
     * @var float
     */
    private $tax;

    /**
     * @ORM\Column(type="text", nullable=true)
     *
     * @var string
     */
    private $comment;

    /**
     * @ORM\ManyToOne(targetEntity="CpCreation\VitiCore\Shop\Model\DeliveryMethod")
     * @ORM\JoinColumn(referencedColumnName="id", nullable=true)
     *
     * @var DeliveryMethod
     *
     */
    private $deliveryMethod;

    /**
     * @ORM\OneToOne(targetEntity="CpCreation\VitiCore\Cart\Model\CartCheckout", mappedBy="cart", cascade={"persist"})
     * @Assert\Valid()
     *
     * @var CartCheckout
     */
    private $cartCheckout;

    /**
     * @var CartItem
     *
     * @ORM\OneToMany(targetEntity="CartItem", mappedBy="cart", cascade={"persist","remove"}, orphanRemoval=true)
     * @ORM\OrderBy({"createdAt" = "ASC"})
     * @Assert\Valid()
     */
    private $items;

    /**
     * @var InvoiceAddress
     *
     * @ORM\OneToOne(targetEntity="InvoiceAddress", cascade={"persist"})
     * @Assert\NotBlank()
     * @Assert\Valid()
     */
    private $invoiceAddress;

    /**
     * @var DeliveryAddress
     *
     * @ORM\OneToOne(targetEntity="DeliveryAddress", cascade={"persist"})
     * @Assert\NotBlank()
     * @Assert\Valid()
     */
    private $deliveryAddress;

    public function __construct()
    {
        $this->id                    = Uuid::uuid4();
        $this->items                 = new ArrayCollection();
        $this->token                 = strtoupper(bin2hex(random_bytes(3)));
        $this->promoWithShippingFees = false;
    }

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

    /**
     * @return string
     */
    public function getToken(): ?string
    {
        return $this->token;
    }

    /**
     * @throws \Exception
     */
    public function setToken(): void
    {
        $this->token = strtoupper(bin2hex(random_bytes(3)));
    }

    /**
     * @return int
     */
    public function getShipping(): ?int
    {
        return $this->shipping;
    }

    /**
     * @param int $shipping
     */
    public function setShipping(int $shipping = null): void
    {
        $this->shipping = $shipping;
    }

    /**
     * @return string
     */
    public function getShippingName(): ?string
    {
        return $this->shippingName;
    }

    /**
     * @param string $shippingName
     */
    public function setShippingName(?string $shippingName): void
    {
        $this->shippingName = $shippingName;
    }

    /**
     * @return string
     */
    public function getGroupCode(): ?string
    {
        return $this->groupCode;
    }

    /**
     * @param string $groupCode
     */
    public function setGroupCode(?string $groupCode): void
    {
        $this->groupCode = $groupCode;
    }

    /**
     * @return string
     */
    public function getDeliveryMethodJson(): ?string
    {
        return $this->deliveryMethodJson;
    }

    /**
     * @param string $deliveryMethodJson
     */
    public function setDeliveryMethodJson(string $deliveryMethodJson = null)
    {
        $this->deliveryMethodJson = $deliveryMethodJson;
    }


    /**
     * @return string
     */
    public function getPromo(): ?string
    {
        return $this->promo;
    }

    /**
     * @param string $promo
     */
    public function setPromo(string $promo = null): void
    {
        $this->promo = strtoupper($promo);
    }

    /**
     * @return UuidInterface
     */
    public function getPromoId(): ?UuidInterface
    {
        return $this->promoId;
    }

    /**
     * @param UuidInterface $promoId
     */
    public function setPromoId(?UuidInterface $promoId): void
    {
        $this->promoId = $promoId;
    }

    /**
     * @return float|null
     */
    public function getPromoValueAmount(): ?float
    {
        return $this->promoValueAmount;
    }

    /**
     * @param float|null $promoValueAmount
     */
    public function setPromoValueAmount(?float $promoValueAmount): void
    {
        $this->promoValueAmount = $promoValueAmount;
    }

    /**
     * @return float|null
     */
    public function getPromoValuePercent(): ?float
    {
        return $this->promoValuePercent;
    }

    /**
     * @param float|null $promoValuePercent
     */
    public function setPromoValuePercent(?float $promoValuePercent): void
    {
        $this->promoValuePercent = $promoValuePercent;
    }

    /**
     * @return bool
     */
    public function isPromoWithShippingFees(): bool
    {
        return $this->promoWithShippingFees;
    }

    /**
     * @param bool $promoWithShippingFees
     */
    public function setPromoWithShippingFees(bool $promoWithShippingFees): void
    {
        $this->promoWithShippingFees = $promoWithShippingFees;
    }

    /**
     * @return int
     */
    public function getDegressiveValue(): ?int
    {
        return $this->degressiveValue;
    }

    /**
     * @param int $degressiveValue
     */
    public function setDegressiveValue(int $degressiveValue = null)
    {
        $this->degressiveValue = $degressiveValue;
    }

    /**
     * @return bool
     */
    public function isViewHT(): ?bool
    {
        return $this->viewHT;
    }

    /**
     * @param bool $viewHT
     */
    public function setViewHT(bool $viewHT = null): void
    {
        $this->viewHT = $viewHT;
    }

    /**
     * @return float
     */
    public function getTax(): ?float
    {
        return $this->tax;
    }

    /**
     * @param float $tax
     */
    public function setTax(float $tax = null): void
    {
        $this->tax = $tax;
    }

    /**
     * @return string
     */
    public function getComment(): ?string
    {
        return $this->comment;
    }

    /**
     * @param string $comment
     */
    public function setComment(string $comment = null): void
    {
        $this->comment = $comment;
    }

    /**
     * @return DeliveryMethod
     */
    public function getDeliveryMethod(): ?DeliveryMethod
    {
        return $this->deliveryMethod;
    }

    /**
     * @param DeliveryMethod $deliveryMethod
     */
    public function setDeliveryMethod(DeliveryMethod $deliveryMethod = null)
    {
        $this->deliveryMethod = $deliveryMethod;
    }


    /**
     * @return CartCheckout
     */
    public function getCartCheckout(): ?CartCheckout
    {
        return $this->cartCheckout;
    }

    /**
     * @param CartCheckout|null $cartCheckout
     */
    public function setCartCheckout(CartCheckout $cartCheckout = null)
    {
        $cartCheckout->setCart($this);
        $this->cartCheckout = $cartCheckout;
    }

    /**
     * @return int
     */
    public function getItemsTotal(): int
    {
        $total = 0;
        /** @var CartItem $item */
        foreach ($this->getItems() as $item) {
            $total = $total + $item->getTotal();
        }

        return $total;
    }

    public function getTotal(): ?int
    {
        $total = $this->getItemsTotal() + $this->getShipping();
        if ($this->getPromoValuePercent()) {
            $total = $total - ceil($this->getItemsTotal() * $this->getPromoValuePercent());
        }
        if ($this->getPromoValueAmount()) {
            $total = $total - $this->getPromoValueAmount();
        }
        if ($this->getDegressiveValue()) {
            $total = $total - $this->getDegressiveValue();
        }
        if ($this->isViewHT()) {
            return round($total * (1 + $this->getTax()), 2);
        }

        return $total;
    }

    public function getTotalWithoutTax()
    {
        $total = $this->getItemsTotal() + $this->getShipping();
        if ($this->getPromoValuePercent()) {
            if ($this->isPromoWithShippingFees()) {
                return $total - ceil($total * $this->getPromoValuePercent());
            } else {
                return $total - ceil($this->getItemsTotal() * $this->getPromoValuePercent());
            }
        }
        if ($this->getPromoValueAmount()) {
            $total = $total - $this->getPromoValueAmount();
        }
        if ($this->getDegressiveValue()) {
            $total = $total - $this->getDegressiveValue();
        }

        return $total;
    }

    public function getTotalTax()
    {
        $total = $this->getItemsTotal() + $this->getShipping();
        if ($this->getPromoValuePercent()) {
            if ($this->isPromoWithShippingFees()) {
                $total = $total - ceil($total * $this->getPromoValuePercent());
            } else {
                $total = $total - ceil($this->getItemsTotal() * $this->getPromoValuePercent());
            }

        }
        if ($this->getPromoValueAmount()) {
            $total = $total - $this->getPromoValueAmount();
        }
        if ($this->getDegressiveValue()) {
            $total = $total - $this->getDegressiveValue();
        }

        return round($total * $this->getTax(), 2);
    }

    public function getQuantityAllAbsolute()
    {
        $quantityAllAbsolu = 0;
        /** @var CartItem $item */
        foreach ($this->getItems() as $item) {
            $quantityAllAbsolu = $quantityAllAbsolu + ($item->getQuantity() * $item->getVariant()->getType()->getEquivalency());
        }

        return $quantityAllAbsolu;
    }

    /**
     * @param Product[] $products
     */
    public function getQuantityByProducts(Collection $products)
    {
        if ($products->count() === 0) {
            return $this->getQuantityAllAbsolute();
        }

        $quantity = 0;
        /** @var CartItem $item */
        foreach ($this->getItems() as $item) {
            /** @var CartItem $selectedItem */
            foreach ($products as $selectedProduct) {
                if ($item->getVariant()->getProduct() === $selectedProduct) {
                    $quantity = $quantity + ($item->getQuantity() * $item->getVariant()->getType()->getEquivalency());
                }
            }

        }

        return $quantity;
    }

    /**
     * @return Collection<CartItem>
     */
    public function getItems(): Collection
    {
        return $this->items;
    }

    /**
     * @param CartItem $item
     */
    public function addItem(CartItem $item)
    {
        $item->setCart($this);
        $this->items[] = $item;
    }

    /**
     * @param CartItem $item
     */
    public function removeItem(CartItem $item)
    {
        $this->items->removeElement($item);
    }

    /**
     * @return DeliveryAddress
     */
    public function getDeliveryAddress(): ?DeliveryAddress
    {
        return $this->deliveryAddress;
    }

    /**
     * @param DeliveryAddress $deliveryAddress
     */
    public function setDeliveryAddress(DeliveryAddress $deliveryAddress)
    {
        $this->deliveryAddress = $deliveryAddress;
    }

    /**
     * @return InvoiceAddress
     */
    public function getInvoiceAddress(): ?InvoiceAddress
    {
        return $this->invoiceAddress;
    }

    /**
     * @param InvoiceAddress $invoiceAddress
     */
    public function setInvoiceAddress(InvoiceAddress $invoiceAddress): void
    {
        $this->invoiceAddress = $invoiceAddress;
    }

    public function getEmail(): ?string
    {
        if ($this->getInvoiceAddress()) {
            return $this->getInvoiceAddress()->getEmail();
        }

        return null;
    }

    public function getPaymentMethod(): ?PaymentMethod
    {
        if ($this->getCartCheckout()) {
            return $this->getCartCheckout()->getPaymentMethod();
        }

        return null;
    }

    public function getFirstname(): ?string
    {
        if ($this->getInvoiceAddress()) {
            return $this->getInvoiceAddress()->getFirstname();
        }

        return null;
    }

    public function getLastname(): ?string
    {
        if ($this->getInvoiceAddress()) {
            return $this->getInvoiceAddress()->getLastname();
        }

        return null;
    }

    public function getPhone(): ?string
    {
        if ($this->getInvoiceAddress()) {
            return $this->getInvoiceAddress()->getPhone();
        }

        return null;
    }

    public function getCompany(): ?string
    {
        if ($this->getInvoiceAddress()) {
            return $this->getInvoiceAddress()->getCompany();
        }

        return null;
    }

    public function getStreet(): ?string
    {
        if ($this->getInvoiceAddress()) {
            return $this->getInvoiceAddress()->getStreet();
        }

        return null;
    }

    public function getPostalCode(): ?string
    {
        if ($this->getInvoiceAddress()) {
            return $this->getInvoiceAddress()->getPostalCode();
        }

        return null;
    }

    public function getCity(): ?string
    {
        if ($this->getInvoiceAddress()) {
            return $this->getInvoiceAddress()->getCity();
        }

        return null;
    }

    public function getCountry(): ?string
    {
        if ($this->getInvoiceAddress()) {
            return $this->getInvoiceAddress()->getCountry();
        }

        return null;
    }

    public function getQuantity(): int
    {
        $quantity = 0;
        foreach ($this->getItems() as $item) {
            $quantity = $quantity + $item->getQuantity();
        }

        return $quantity;
    }

    public function containsGifts(): bool
    {
        /** @var CartItem $item */
        foreach ($this->items as $item){
            if($item->getVariant()->getType()->isGift()){
                return true;
            }
        }
        return false;
    }
}
