<?php

declare(strict_types=1);

namespace App\Module\ProjectManagement\Infrastructure\EventListener;

use App\Module\ProjectManagement\Domain\Entity\Task;
use App\Shared\Domain\Contract\NotifierInterface;
use App\Shared\Domain\Contract\UserInterface;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
use Doctrine\ORM\Event\OnFlushEventArgs;
use Doctrine\ORM\Event\PostFlushEventArgs;
use Doctrine\ORM\Events;
use Symfony\Bundle\SecurityBundle\Security;

#[AsDoctrineListener(event: Events::onFlush)]
#[AsDoctrineListener(event: Events::postFlush)]
final class TaskNotificationListener
{
    /** @var list<array{user: UserInterface, type: string, task: Task}> */
    private array $pending = [];

    public function __construct(
        private readonly Security $security,
        private readonly NotifierInterface $notifier,
    ) {}

    public function onFlush(OnFlushEventArgs $args): void
    {
        $actor = $this->security->getUser();
        if (!$actor instanceof UserInterface) {
            return;
        }

        $uow = $args->getObjectManager()->getUnitOfWork();

        // Assignation sur une tâche nouvellement créée.
        foreach ($uow->getScheduledEntityInsertions() as $entity) {
            if (!$entity instanceof Task) {
                continue;
            }
            $assignee = $entity->getAssignee();
            if ($assignee instanceof UserInterface && $assignee !== $actor) {
                $this->pending[] = ['user' => $assignee, 'type' => 'task_assigned', 'task' => $entity];
            }
        }

        // Changement d'assignation sur une tâche existante.
        foreach ($uow->getScheduledEntityUpdates() as $entity) {
            if (!$entity instanceof Task) {
                continue;
            }
            $changeSet = $uow->getEntityChangeSet($entity);
            if (!isset($changeSet['assignee'])) {
                continue;
            }
            $new = $changeSet['assignee'][1];
            if ($new instanceof UserInterface && $new !== $actor) {
                $this->pending[] = ['user' => $new, 'type' => 'task_assigned', 'task' => $entity];
            }
        }

        // Ajout de collaborateur(s) (tâche nouvelle ou existante).
        foreach ($uow->getScheduledCollectionUpdates() as $collection) {
            $owner = $collection->getOwner();
            if (!$owner instanceof Task) {
                continue;
            }
            if ('collaborators' !== $collection->getMapping()->fieldName) {
                continue;
            }
            foreach ($collection->getInsertDiff() as $user) {
                if ($user instanceof UserInterface && $user !== $actor) {
                    $this->pending[] = ['user' => $user, 'type' => 'task_collaborator_added', 'task' => $owner];
                }
            }
        }
    }

    public function postFlush(PostFlushEventArgs $args): void
    {
        if ([] === $this->pending) {
            return;
        }

        $pending       = $this->pending;
        $this->pending = [];

        foreach ($pending as $item) {
            [$title, $message] = $this->render($item['type'], $item['task']);
            $this->notifier->notify($item['user'], $item['type'], $title, $message);
        }
    }

    /**
     * @return array{0: string, 1: string}
     */
    private function render(string $type, Task $task): array
    {
        $projectName = $task->getProject()?->getName() ?? '';
        $suffix      = '' !== $projectName ? sprintf(' — %s', $projectName) : '';
        $context     = sprintf('« %s »%s', (string) $task->getTitle(), $suffix);

        return match ($type) {
            'task_assigned'           => ['Nouvelle tâche assignée', $context],
            'task_collaborator_added' => ['Ajout à une tâche', $context],
            default                   => ['Notification', $context],
        };
    }
}
