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

use CpCreation\VitiCore\Behavior\HasTimestamp;
use CpCreation\VitiCore\Behavior\Impl\Timestamp;
use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Model\Promo;
use CpCreation\VitiCore\Marketing\Repository\AbandonedCartReminderRepository;
use Doctrine\ORM\Mapping as ORM;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;

#[ORM\Entity(repositoryClass: AbandonedCartReminderRepository::class)]
#[ORM\Table(name: 'marketing_abandoned_cart_reminder')]
#[ORM\UniqueConstraint(name: 'uniq_marketing_abandoned_cart_reminder_cart_rule', columns: ['cart_id', 'rule_id'])]
#[ORM\UniqueConstraint(name: 'uniq_marketing_abandoned_cart_reminder_token_hash', columns: ['token_hash'])]
#[ORM\Index(name: 'marketing_abandoned_cart_reminder_status_idx', columns: ['status'])]
#[ORM\HasLifecycleCallbacks]
class AbandonedCartReminder implements HasTimestamp
{
    use Timestamp;

    public const string STATUS_SCHEDULED = 'scheduled';
    public const string STATUS_SENT = 'sent';
    public const string STATUS_CANCELED = 'canceled';
    public const string STATUS_EXPIRED = 'expired';
    public const string STATUS_RECOVERED = 'recovered';

    public const array STATUSES = [
        self::STATUS_SCHEDULED,
        self::STATUS_SENT,
        self::STATUS_RECOVERED,
        self::STATUS_CANCELED,
        self::STATUS_EXPIRED,
    ];

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

    #[ORM\ManyToOne(targetEntity: Cart::class)]
    #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
    private Cart $cart;

    #[ORM\ManyToOne(targetEntity: AbandonedCartRule::class)]
    #[ORM\JoinColumn(nullable: false, onDelete: 'RESTRICT')]
    private AbandonedCartRule $rule;

    #[ORM\OneToOne(targetEntity: Promo::class)]
    #[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
    private ?Promo $promo;

    #[ORM\Column(type: 'string', length: 255)]
    private string $recipientEmail;

    #[ORM\Column(type: 'string', length: 64)]
    private string $tokenHash;

    #[ORM\Column(type: 'string', length: 24)]
    private string $status = self::STATUS_SCHEDULED;

    #[ORM\Column(type: 'datetime_immutable')]
    private \DateTimeImmutable $scheduledAt;

    #[ORM\Column(type: 'datetime_immutable')]
    private \DateTimeImmutable $expiresAt;

    #[ORM\Column(type: 'datetime_immutable', nullable: true)]
    private ?\DateTimeImmutable $sentAt = null;

    #[ORM\Column(type: 'datetime_immutable', nullable: true)]
    private ?\DateTimeImmutable $recoveredAt = null;

    public function __construct(
        Cart $cart,
        AbandonedCartRule $rule,
        ?Promo $promo,
        string $recipientEmail,
        string $tokenHash,
        \DateTimeImmutable $scheduledAt,
        \DateTimeImmutable $expiresAt,
    ) {
        $this->id = Uuid::uuid4();
        $this->cart = $cart;
        $this->rule = $rule;
        $this->promo = $promo;
        $this->recipientEmail = mb_strtolower(mb_trim($recipientEmail));
        $this->tokenHash = $tokenHash;
        $this->scheduledAt = $scheduledAt;
        $this->expiresAt = $expiresAt;
    }

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

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

    public function getRule(): AbandonedCartRule
    {
        return $this->rule;
    }

    public function getPromo(): ?Promo
    {
        return $this->promo;
    }

    public function getRecipientEmail(): string
    {
        return $this->recipientEmail;
    }

    public function getTokenHash(): string
    {
        return $this->tokenHash;
    }

    public function getStatus(): string
    {
        return $this->status;
    }

    public function getScheduledAt(): \DateTimeImmutable
    {
        return $this->scheduledAt;
    }

    public function getExpiresAt(): \DateTimeImmutable
    {
        return $this->expiresAt;
    }

    public function getSentAt(): ?\DateTimeImmutable
    {
        return $this->sentAt;
    }

    public function getRecoveredAt(): ?\DateTimeImmutable
    {
        return $this->recoveredAt;
    }

    public function isScheduled(): bool
    {
        return self::STATUS_SCHEDULED === $this->status;
    }

    public function isUsableAt(\DateTimeImmutable $now): bool
    {
        return \in_array($this->status, [self::STATUS_SENT, self::STATUS_RECOVERED], true)
            && $this->expiresAt > $now;
    }

    public function markSent(?\DateTimeImmutable $sentAt = null): void
    {
        $this->status = self::STATUS_SENT;
        $this->sentAt = $sentAt ?? new \DateTimeImmutable();
    }

    public function markRecovered(?\DateTimeImmutable $recoveredAt = null): void
    {
        if ($this->isUsableAt($recoveredAt ?? new \DateTimeImmutable())) {
            $this->status = self::STATUS_RECOVERED;
            $this->recoveredAt ??= $recoveredAt ?? new \DateTimeImmutable();
        }
    }

    public function cancel(): void
    {
        $this->status = self::STATUS_CANCELED;
        $this->disablePromo();
    }

    public function markExpired(): void
    {
        $this->status = self::STATUS_EXPIRED;
        $this->disablePromo();
    }

    private function disablePromo(): void
    {
        $this->promo?->setDisabled(true);

        $promoCode = $this->promo?->getTitle();
        $cartPromoCode = $this->cart->getPromoCodeInput();
        if (
            null === $promoCode
            || null === $cartPromoCode
            || mb_strtoupper(mb_trim($promoCode)) !== mb_strtoupper(mb_trim($cartPromoCode))
        ) {
            return;
        }

        $this->cart->setPromoCodeInput(null);
        $this->cart->clearPromoCodeFeedback();
        $this->cart->clearPricing();
    }
}
