<?php

declare(strict_types=1);

namespace App\Tests\Functional\Module\ProjectManagement;

use App\Module\ProjectManagement\Domain\Entity\Project;
use App\Module\ProjectManagement\Domain\Entity\Task;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

/**
 * Task adopts TimestampableBlamableTrait (LST-65 slice 3).
 * Verifies the TimestampableBlamableSubscriber populates created_at on
 * persist and refreshes updated_at on update.
 *
 * @internal
 */
final class TaskTimestampableTest extends KernelTestCase
{
    private EntityManagerInterface $em;

    protected function setUp(): void
    {
        self::bootKernel();
        $this->em = self::getContainer()->get(EntityManagerInterface::class);
        $this->em->getConnection()->executeStatement("DELETE FROM task WHERE title = 'ts-test-task'");
    }

    protected function tearDown(): void
    {
        $this->em->getConnection()->executeStatement("DELETE FROM task WHERE title = 'ts-test-task'");
        parent::tearDown();
        unset($this->em);
    }

    public function testCreatedAtIsSetOnPersist(): void
    {
        $task = $this->makeTask();
        $this->em->persist($task);
        $this->em->flush();

        self::assertInstanceOf(DateTimeImmutable::class, $task->getCreatedAt(), 'createdAt must be set after flush');
        self::assertInstanceOf(DateTimeImmutable::class, $task->getUpdatedAt(), 'updatedAt must be set after flush');

        $taskId = $task->getId();
        $this->em->clear();
        $reloaded = $this->em->getRepository(Task::class)->find($taskId);
        self::assertNotNull($reloaded);
        self::assertNotNull($reloaded->getCreatedAt(), 'created_at must be persisted in DB');
    }

    public function testUpdatedAtIsRefreshedOnUpdate(): void
    {
        $task = $this->makeTask();
        $this->em->persist($task);
        $this->em->flush();

        $taskId           = $task->getId();
        $initialUpdatedAt = $task->getUpdatedAt();
        self::assertNotNull($initialUpdatedAt);

        // Backdate the persisted value so the preUpdate refresh is observable
        // even within the same second.
        $this->em->getConnection()->executeStatement(
            "UPDATE task SET updated_at = '2000-01-01 00:00:00' WHERE id = :id",
            ['id' => $taskId],
        );
        $this->em->clear();

        /** @var Task $managed */
        $managed = $this->em->getRepository(Task::class)->find($taskId);
        self::assertSame('2000-01-01 00:00:00', $managed->getUpdatedAt()?->format('Y-m-d H:i:s'));

        $managed->setDescription('changed description');
        $this->em->flush();
        $this->em->clear();

        /** @var Task $reloaded */
        $reloaded = $this->em->getRepository(Task::class)->find($taskId);
        self::assertNotNull($reloaded->getUpdatedAt());
        self::assertNotSame(
            '2000-01-01 00:00:00',
            $reloaded->getUpdatedAt()->format('Y-m-d H:i:s'),
            'updated_at must be refreshed and persisted on UPDATE',
        );
    }

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

        $task = new Task();
        $task->setNumber(random_int(100000, 999999));
        $task->setTitle('ts-test-task');
        $task->setProject($project);

        return $task;
    }
}
