<?php

declare(strict_types=1);

namespace App\Tests\Functional\EventListener;

use App\Module\Core\Domain\Entity\User;
use App\Module\Core\Infrastructure\Doctrine\DoctrineNotificationRepository;
use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;

/**
 * @internal
 */
class TaskNotificationListenerTest extends KernelTestCase
{
    private EntityManagerInterface $em;
    private DoctrineNotificationRepository $notifications;
    private TokenStorageInterface $tokenStorage;
    private Project $project;
    private User $actor;
    private User $alice;
    private User $bob;

    protected function setUp(): void
    {
        self::bootKernel();
        $c                   = self::getContainer();
        $this->em            = $c->get(EntityManagerInterface::class);
        $this->notifications = $c->get(DoctrineNotificationRepository::class);
        $this->tokenStorage  = $c->get(TokenStorageInterface::class);

        $project = $this->em->getRepository(Project::class)->findOneBy([]);
        self::assertNotNull($project, 'Les fixtures doivent fournir au moins un projet.');
        $this->project = $project;

        $this->actor = $this->makeUser('actor');
        $this->alice = $this->makeUser('alice');
        $this->bob   = $this->makeUser('bob');
        $this->em->flush();
    }

    public function testAssignmentToOtherUserCreatesNotification(): void
    {
        $this->loginAs($this->actor);

        $task = $this->makeTask();
        $task->setAssignee($this->alice);
        $this->em->persist($task);
        $this->em->flush();

        $rows = $this->notifications->findBy(['user' => $this->alice]);
        self::assertCount(1, $rows);
        self::assertSame('task_assigned', $rows[0]->getType());
        self::assertStringContainsString((string) $task->getTitle(), (string) $rows[0]->getMessage());
    }

    public function testSelfAssignmentCreatesNoNotification(): void
    {
        $this->loginAs($this->actor);

        $task = $this->makeTask();
        $task->setAssignee($this->actor);
        $this->em->persist($task);
        $this->em->flush();

        self::assertCount(0, $this->notifications->findBy(['user' => $this->actor]));
    }

    public function testReassignmentNotifiesOnlyNewAssignee(): void
    {
        $this->loginAs($this->actor);

        $task = $this->makeTask();
        $task->setAssignee($this->alice);
        $this->em->persist($task);
        $this->em->flush();

        $task->setAssignee($this->bob);
        $this->em->flush();

        self::assertCount(1, $this->notifications->findBy(['user' => $this->alice]));
        self::assertCount(1, $this->notifications->findBy(['user' => $this->bob]));
    }

    public function testAssigneeSetToNullCreatesNoNotificationForNull(): void
    {
        $this->loginAs($this->actor);

        $task = $this->makeTask();
        $task->setAssignee($this->alice);
        $this->em->persist($task);
        $this->em->flush();

        $task->setAssignee(null);
        $this->em->flush();

        // alice a reçu la 1re notif, mais le passage à null n'en crée aucune autre.
        self::assertCount(1, $this->notifications->findBy(['user' => $this->alice]));
    }

    public function testNoActorCreatesNoNotification(): void
    {
        $this->tokenStorage->setToken(null);

        $task = $this->makeTask();
        $task->setAssignee($this->alice);
        $this->em->persist($task);
        $this->em->flush();

        self::assertCount(0, $this->notifications->findBy(['user' => $this->alice]));
    }

    public function testAddingCollaboratorCreatesNotification(): void
    {
        $this->loginAs($this->actor);

        $task = $this->makeTask();
        $this->em->persist($task);
        $this->em->flush();

        $task->addCollaborator($this->alice);
        $this->em->flush();

        $rows = $this->notifications->findBy(['user' => $this->alice]);
        self::assertCount(1, $rows);
        self::assertSame('task_collaborator_added', $rows[0]->getType());
    }

    public function testAddingSelfAsCollaboratorCreatesNoNotification(): void
    {
        $this->loginAs($this->actor);

        $task = $this->makeTask();
        $this->em->persist($task);
        $this->em->flush();

        $task->addCollaborator($this->actor);
        $this->em->flush();

        self::assertCount(0, $this->notifications->findBy(['user' => $this->actor]));
    }

    private function makeUser(string $prefix): User
    {
        $user = new User();
        $user->setUsername($prefix.'-'.uniqid());
        $user->setPassword('x');
        $user->setRoles(['ROLE_USER']);
        $this->em->persist($user);

        return $user;
    }

    private function makeTask(): Task
    {
        $task = new Task();
        $task->setNumber(random_int(100000, 999999));
        $task->setTitle('Tâche de test '.uniqid());
        $task->setProject($this->project);

        return $task;
    }

    private function loginAs(User $user): void
    {
        $this->tokenStorage->setToken(
            new UsernamePasswordToken($user, 'main', $user->getRoles()),
        );
    }
}
