<?php

declare(strict_types=1);

namespace App\Tests\Functional\Module\TimeTracking;

use App\Module\Core\Domain\Entity\User;
use App\Module\TimeTracking\Domain\Entity\TimeEntry;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

/**
 * TimeEntry is the first adopter of TimestampableBlamableTrait.
 * Verifies the TimestampableBlamableSubscriber populates created_at on
 * persist and refreshes updated_at on update.
 *
 * @internal
 */
final class TimeEntryTimestampableTest extends KernelTestCase
{
    private EntityManagerInterface $em;

    protected function setUp(): void
    {
        self::bootKernel();
        $this->em = self::getContainer()->get(EntityManagerInterface::class);
        // Clean slate for deterministic assertions: these tests are not wrapped
        // in a rolled-back transaction.
        $this->em->getConnection()->executeStatement("DELETE FROM time_entry WHERE title = 'ts-test-entry'");
        $this->em->getConnection()->executeStatement("DELETE FROM \"user\" WHERE username = 'ts_test_user'");
    }

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

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

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

        // Confirm it was actually persisted to the DB, not just the in-memory object.
        $this->em->clear();
        $reloaded = $this->em->getRepository(TimeEntry::class)->find($entry->getId());
        self::assertNotNull($reloaded);
        self::assertNotNull($reloaded->getCreatedAt(), 'created_at must be persisted in DB');
    }

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

        $entryId          = $entry->getId();
        $initialUpdatedAt = $entry->getUpdatedAt();
        self::assertNotNull($initialUpdatedAt);

        // Force a distinguishable timestamp by backdating the persisted value,
        // so the preUpdate refresh is observable even within the same second.
        $this->em->getConnection()->executeStatement(
            "UPDATE time_entry SET updated_at = '2000-01-01 00:00:00' WHERE id = :id",
            ['id' => $entryId],
        );
        $this->em->clear();

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

        // Mutate a tracked field and flush -> preUpdate must refresh updated_at.
        $managed->setTitle('ts-test-entry');
        $managed->setDescription('changed description');
        $this->em->flush();
        $this->em->clear();

        /** @var TimeEntry $reloaded */
        $reloaded = $this->em->getRepository(TimeEntry::class)->find($entryId);
        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 makeEntry(): TimeEntry
    {
        $user = new User();
        $user->setUsername('ts_test_user');
        $user->setPassword('hashed-secret');
        $user->setRoles(['ROLE_USER']);
        $this->em->persist($user);

        $entry = new TimeEntry();
        $entry->setUser($user);
        $entry->setTitle('ts-test-entry');
        $entry->setStartedAt(new DateTimeImmutable());

        return $entry;
    }
}
