<?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\Product\Model;

use CpCreation\VitiCore\Behavior\HasTimestamp;
use CpCreation\VitiCore\Behavior\Impl\Timestamp;
use CpCreation\VitiCore\Product\Repository\ProductCollectionRepository;
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\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;

#[ORM\Entity(repositoryClass: ProductCollectionRepository::class)]
#[ORM\Table(name: 'product_collection')]
#[ORM\UniqueConstraint(name: 'uniq_product_collection_code', columns: ['code'])]
#[UniqueEntity(fields: ['code'], message: 'Ce code est déjà utilisé par une autre collection.')]
#[ORM\HasLifecycleCallbacks]
class ProductCollection implements HasTimestamp
{
    use Timestamp;

    public const MODE_MANUAL = 'manual';
    public const MODE_AUTOMATIC = 'automatic';

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

    #[ORM\Column(type: 'string')]
    #[Assert\NotBlank(message: 'Vous devez renseigner un nom.')]
    private ?string $name = null;

    #[ORM\Column(type: 'string', length: 255, nullable: true, unique: true)]
    #[Assert\Length(max: 255, maxMessage: 'Le code ne peut pas dépasser {{ limit }} caractères.')]
    #[Assert\Regex(pattern: '/^[a-z0-9][a-z0-9-]*$/', message: 'Le code doit contenir uniquement des lettres minuscules, des chiffres et des tirets.')]
    private ?string $code = null;

    #[ORM\Column(type: 'text', nullable: true)]
    private ?string $description = null;

    #[ORM\Column(type: 'datetime', nullable: true)]
    private ?\DateTimeInterface $publishedAt = null;

    #[ORM\Column(type: 'datetime', nullable: true)]
    private ?\DateTimeInterface $unPublishedAt = null;

    #[ORM\Column(type: 'string', length: 20, options: ['default' => self::MODE_MANUAL])]
    #[Assert\Choice(choices: [self::MODE_MANUAL, self::MODE_AUTOMATIC], message: 'Vous devez choisir un mode de collection valide.')]
    private string $mode = self::MODE_MANUAL;

    #[ORM\ManyToOne(targetEntity: ProductCategory::class)]
    #[ORM\JoinColumn(name: 'automatic_category_id', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
    private ?ProductCategory $automaticCategory = null;

    #[ORM\Column(type: 'boolean', options: ['default' => true])]
    private bool $automaticOnlyCuvee = true;

    #[ORM\Column(type: 'boolean', options: ['default' => false])]
    private bool $automaticOnlyNew = false;

    #[ORM\Column(type: 'boolean', options: ['default' => false])]
    private bool $automaticExcludeSoldOut = false;

    #[ORM\Column(type: 'integer', nullable: true)]
    #[Assert\Positive(message: 'La limite automatique doit être supérieure à 0.')]
    private ?int $automaticLimit = null;

    /**
     * @var Collection<int, ProductCollectionItem>
     */
    #[ORM\OneToMany(targetEntity: ProductCollectionItem::class, mappedBy: 'productCollection', cascade: ['persist', 'remove'], orphanRemoval: true)]
    #[ORM\OrderBy(['position' => 'ASC'])]
    #[Assert\Valid]
    private Collection $items;

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

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

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(?string $name): void
    {
        $this->name = $name;
    }

    public function getCode(): ?string
    {
        return $this->code;
    }

    public function setCode(?string $code): void
    {
        $this->code = null !== $code && '' !== mb_trim($code) ? mb_trim($code) : null;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): void
    {
        $this->description = $description;
    }

    public function getPublishedAt(): ?\DateTimeInterface
    {
        return $this->publishedAt;
    }

    public function setPublishedAt(?\DateTimeInterface $publishedAt): void
    {
        $this->publishedAt = $publishedAt;
    }

    public function getUnPublishedAt(): ?\DateTimeInterface
    {
        return $this->unPublishedAt;
    }

    public function setUnPublishedAt(?\DateTimeInterface $unPublishedAt): void
    {
        $this->unPublishedAt = $unPublishedAt;
    }

    public function isCurrentlyPublished(?\DateTimeInterface $referenceDate = null): bool
    {
        $referenceDate ??= new \DateTimeImmutable('now');

        if (null !== $this->publishedAt && $this->publishedAt > $referenceDate) {
            return false;
        }

        if (null !== $this->unPublishedAt && $this->unPublishedAt < $referenceDate) {
            return false;
        }

        return true;
    }

    public function getMode(): string
    {
        return $this->mode;
    }

    public function setMode(string $mode): void
    {
        $this->mode = $mode;
    }

    public function isManual(): bool
    {
        return self::MODE_MANUAL === $this->mode;
    }

    public function isAutomatic(): bool
    {
        return self::MODE_AUTOMATIC === $this->mode;
    }

    public function getAutomaticCategory(): ?ProductCategory
    {
        return $this->automaticCategory;
    }

    public function setAutomaticCategory(?ProductCategory $automaticCategory): void
    {
        $this->automaticCategory = $automaticCategory;
    }

    public function isAutomaticOnlyCuvee(): bool
    {
        return $this->automaticOnlyCuvee;
    }

    public function setAutomaticOnlyCuvee(bool $automaticOnlyCuvee): void
    {
        $this->automaticOnlyCuvee = $automaticOnlyCuvee;
    }

    public function isAutomaticOnlyNew(): bool
    {
        return $this->automaticOnlyNew;
    }

    public function setAutomaticOnlyNew(bool $automaticOnlyNew): void
    {
        $this->automaticOnlyNew = $automaticOnlyNew;
    }

    public function isAutomaticExcludeSoldOut(): bool
    {
        return $this->automaticExcludeSoldOut;
    }

    public function setAutomaticExcludeSoldOut(bool $automaticExcludeSoldOut): void
    {
        $this->automaticExcludeSoldOut = $automaticExcludeSoldOut;
    }

    public function getAutomaticLimit(): ?int
    {
        return $this->automaticLimit;
    }

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

    public function addProduct(Product $product): self
    {
        foreach ($this->items as $item) {
            if ($item->getProduct() === $product) {
                return $this;
            }
        }

        $item = new ProductCollectionItem();
        $item->setProduct($product);
        $this->addItem($item);

        return $this;
    }

    public function removeProduct(Product $product): void
    {
        foreach ($this->items as $item) {
            if ($item->getProduct() === $product) {
                $this->removeItem($item);

                return;
            }
        }
    }

    public function addItem(ProductCollectionItem $item): self
    {
        if (!$this->items->contains($item)) {
            $this->items->add($item);
            $item->setProductCollection($this);
        }

        return $this;
    }

    public function removeItem(ProductCollectionItem $item): void
    {
        if ($this->items->removeElement($item) && $item->getProductCollection() === $this) {
            $item->setProductCollection(null);
        }
    }

    /**
     * @return Collection<int, Product>
     */
    public function getProducts(): Collection
    {
        return new ArrayCollection(array_values(array_filter(
            $this->items->map(static fn (ProductCollectionItem $item): ?Product => $item->getProduct())->toArray()
        )));
    }

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

    public function normalizeItemPositions(): void
    {
        $items = array_values($this->items->toArray());
        $initialOrder = [];

        foreach ($items as $index => $item) {
            $initialOrder[spl_object_id($item)] = $index;
        }

        usort($items, static function (ProductCollectionItem $left, ProductCollectionItem $right) use ($initialOrder): int {
            $leftPosition = $left->getPosition();
            $rightPosition = $right->getPosition();

            if ($leftPosition === $rightPosition) {
                return $initialOrder[spl_object_id($left)] <=> $initialOrder[spl_object_id($right)];
            }

            if (null === $leftPosition) {
                return 1;
            }

            if (null === $rightPosition) {
                return -1;
            }

            return $leftPosition <=> $rightPosition;
        });

        foreach ($items as $position => $item) {
            $item->setPosition($position);
        }
    }

    #[Assert\Callback]
    public function validatePublicationPeriod(ExecutionContextInterface $context): void
    {
        if (
            null !== $this->publishedAt
            && null !== $this->unPublishedAt
            && $this->unPublishedAt <= $this->publishedAt
        ) {
            $context->buildViolation('La date de fin de publication doit être postérieure à la date de début.')
                ->atPath('unPublishedAt')
                ->addViolation();
        }
    }
}
