<?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\Bridge\Doctrine\EventSubscriber;

use CpCreation\VitiCore\Cart\Model\Cart;
use CpCreation\VitiCore\Cart\Model\CartCheckout;
use CpCreation\VitiCore\Cart\Repository\CartRepository;
use CpCreation\VitiCore\TransactionalEmail\TransactionalEmailScheduler;
use CpCreation\VitiCore\TransactionalEmail\TransactionalEmailTrigger;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Event\PostFlushEventArgs;
use Doctrine\ORM\Events;

final class TransactionalEmailCartCheckoutSubscriber implements EventSubscriber
{
    /**
     * @var array<string, array<string, true>>
     */
    private array $cartTriggers = [];

    private bool $flushingTransactionalEmails = false;

    public function __construct(
        private readonly CartRepository $cartRepository,
        private readonly TransactionalEmailScheduler $transactionalEmailScheduler,
    ) {
    }

    /**
     * @return list<string>
     */
    public function getSubscribedEvents(): array
    {
        return [
            Events::onFlush,
            Events::postFlush,
        ];
    }

    public function onFlush(OnFlushEventArgs $event): void
    {
        if ($this->flushingTransactionalEmails) {
            return;
        }

        $unitOfWork = $event->getObjectManager()->getUnitOfWork();

        foreach ($unitOfWork->getScheduledEntityInsertions() as $entity) {
            if (!$entity instanceof CartCheckout) {
                continue;
            }

            if ($entity->getPayedAt() instanceof \DateTimeInterface) {
                $this->rememberCartTrigger($entity->getCart(), TransactionalEmailTrigger::ORDER_PAID);
            }
        }

        foreach ($unitOfWork->getScheduledEntityUpdates() as $entity) {
            if (!$entity instanceof CartCheckout) {
                continue;
            }

            $changeSet = $unitOfWork->getEntityChangeSet($entity);
            $this->rememberTriggerWhenDateBecomesFilled($entity, $changeSet, 'payedAt', TransactionalEmailTrigger::ORDER_PAID);
        }
    }

    public function postFlush(PostFlushEventArgs $event): void
    {
        if ([] === $this->cartTriggers || $this->flushingTransactionalEmails) {
            return;
        }

        $cartTriggers = $this->cartTriggers;
        $this->cartTriggers = [];
        $this->flushingTransactionalEmails = true;

        try {
            $created = 0;
            foreach ($cartTriggers as $cartId => $triggers) {
                $cart = $this->cartRepository->find($cartId);
                if (!$cart instanceof Cart) {
                    continue;
                }

                foreach (array_keys($triggers) as $triggerEvent) {
                    $created += \count($this->transactionalEmailScheduler->scheduleCartTrigger($cart, $triggerEvent));
                }
            }

            if ($created > 0) {
                $event->getObjectManager()->flush();
            }
        } finally {
            $this->flushingTransactionalEmails = false;
        }
    }

    /**
     * @param array<string, array{0: mixed, 1: mixed}> $changeSet
     */
    private function rememberTriggerWhenDateBecomesFilled(CartCheckout $cartCheckout, array $changeSet, string $field, string $triggerEvent): void
    {
        if (!isset($changeSet[$field])) {
            return;
        }

        [$oldDate, $newDate] = $changeSet[$field];
        if (null === $oldDate && $newDate instanceof \DateTimeInterface) {
            $this->rememberCartTrigger($cartCheckout->getCart(), $triggerEvent);
        }
    }

    private function rememberCartTrigger(?Cart $cart, string $triggerEvent): void
    {
        if (!$cart instanceof Cart) {
            return;
        }

        $this->cartTriggers[$cart->getId()->toString()][$triggerEvent] = true;
    }
}
