<?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\Commerce\Catalog\Configuration\CatalogIntegrationConfiguration;
use CpCreation\VitiCore\Commerce\Catalog\Outbox\CatalogChangeClassifier;
use CpCreation\VitiCore\Commerce\Catalog\Outbox\CatalogChangeDraft;
use CpCreation\VitiCore\Commerce\Catalog\Outbox\CatalogMutationContext;
use CpCreation\VitiCore\Product\Model\Product;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Events;
use Doctrine\ORM\PersistentCollection;

#[AsDoctrineListener(event: Events::onFlush)]
final readonly class CatalogChangeSubscriber
{
    public function __construct(
        private CatalogIntegrationConfiguration $configuration,
        private CatalogChangeClassifier $classifier,
    ) {}

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

        try {
            $instanceId = $this->configuration->instanceId();
        } catch (\LogicException) {
            // Une activation incomplète ne doit jamais bloquer un flush métier.
            return;
        }

        $entityManager = $event->getObjectManager();
        if (!$entityManager instanceof EntityManagerInterface) {
            return;
        }

        $unitOfWork = $entityManager->getUnitOfWork();
        $drafts = [];

        foreach ($unitOfWork->getScheduledEntityInsertions() as $entity) {
            $this->collect($drafts, $this->classifier->classify(
                $entity,
                CatalogMutationContext::insertion($unitOfWork->getEntityChangeSet($entity)),
            ));
        }

        foreach ($unitOfWork->getScheduledEntityUpdates() as $entity) {
            $changeSet = $unitOfWork->getEntityChangeSet($entity);
            $removed = $entity instanceof Product && $this->isProductRemoval($changeSet);
            $this->collect($drafts, $this->classifier->classify(
                $entity,
                CatalogMutationContext::update($changeSet, $removed),
            ));
        }

        foreach ($unitOfWork->getScheduledEntityDeletions() as $entity) {
            $this->collect($drafts, $this->classifier->classify(
                $entity,
                CatalogMutationContext::deletion($entity instanceof Product),
            ));
        }

        foreach ($unitOfWork->getScheduledCollectionUpdates() as $collection) {
            $this->collectCollectionOwner($drafts, $collection);
        }

        foreach ($unitOfWork->getScheduledCollectionDeletions() as $collection) {
            $this->collectCollectionOwner($drafts, $collection);
        }

        if ([] === $drafts) {
            return;
        }

        $occurredAt = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
        $metadata = $entityManager->getClassMetadata(\CpCreation\VitiCore\Commerce\Catalog\Model\CatalogChange::class);

        foreach ($drafts as $draft) {
            $change = $draft->toCatalogChange($instanceId, $occurredAt);
            $entityManager->persist($change);
            $unitOfWork->computeChangeSet($metadata, $change);
        }
    }

    /**
     * @param array<string, CatalogChangeDraft> $drafts
     */
    private function collect(array &$drafts, ?CatalogChangeDraft $draft): void
    {
        if (!$draft instanceof CatalogChangeDraft) {
            return;
        }

        $key = $draft->deduplicationKey();
        $existing = $drafts[$key] ?? null;

        if (!$existing instanceof CatalogChangeDraft || $draft->isProductRemoval()) {
            $drafts[$key] = $draft;
        }
    }

    /**
     * @param array<string, CatalogChangeDraft> $drafts
     */
    private function collectCollectionOwner(array &$drafts, PersistentCollection $collection): void
    {
        $owner = $collection->getOwner();
        if (null !== $owner) {
            $this->collect($drafts, $this->classifier->classify(
                $owner,
                CatalogMutationContext::update(['items' => [null, null]]),
            ));
        }
    }

    /**
     * @param array<string, array{0: mixed, 1: mixed}> $changeSet
     */
    private function isProductRemoval(array $changeSet): bool
    {
        return (isset($changeSet['deletedAt']) && null !== $changeSet['deletedAt'][1])
            || (isset($changeSet['disabled']) && true === $changeSet['disabled'][1])
            || (isset($changeSet['shop']) && false === $changeSet['shop'][1]);
    }
}
