<?php

declare(strict_types=1);

namespace App\Tests\Unit\Scolarite\Infrastructure\Console;

use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Application\Command\EndReplacement\EndReplacementHandler;
use App\Scolarite\Domain\Model\TeacherReplacement\ClassSubjectPair;
use App\Scolarite\Domain\Model\TeacherReplacement\ReplacementStatus;
use App\Scolarite\Domain\Model\TeacherReplacement\TeacherReplacement;
use App\Scolarite\Infrastructure\Console\EndExpiredReplacementsCommand;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryTeacherReplacementRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Component\Console\Tester\CommandTester;

final class EndExpiredReplacementsCommandTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string REPLACED_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';
    private const string REPLACEMENT_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440011';
    private const string CLASS_ID = '550e8400-e29b-41d4-a716-446655440020';
    private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440030';
    private const string CREATED_BY_ID = '550e8400-e29b-41d4-a716-446655440099';

    private InMemoryTeacherReplacementRepository $repository;
    private Clock $clock;

    protected function setUp(): void
    {
        $this->repository = new InMemoryTeacherReplacementRepository();
        $this->clock = new class implements Clock {
            public function now(): DateTimeImmutable
            {
                return new DateTimeImmutable('2026-04-15 10:00:00');
            }
        };
    }

    #[Test]
    public function itEndsExpiredReplacements(): void
    {
        $replacement = $this->createReplacement(
            startDate: '2026-03-01',
            endDate: '2026-03-31',
        );
        $this->repository->save($replacement);

        $tester = $this->executeCommand();

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('1 remplacement(s) expiré(s) trouvé(s)', $tester->getDisplay());
        self::assertStringContainsString('1 remplacement(s) terminé(s) avec succès.', $tester->getDisplay());
    }

    #[Test]
    public function itHandlesNoExpiredReplacements(): void
    {
        $replacement = $this->createReplacement(
            startDate: '2026-04-01',
            endDate: '2026-04-30',
        );
        $this->repository->save($replacement);

        $tester = $this->executeCommand();

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('Aucun remplacement expiré à traiter.', $tester->getDisplay());
    }

    #[Test]
    public function itHandlesIndividualErrorsGracefully(): void
    {
        // The command's findExpired query uses one repository while the handler's
        // get() uses another that holds an already-terminated copy of the same
        // replacement, simulating a concurrent termination (race condition).
        $commandRepository = new InMemoryTeacherReplacementRepository();
        $activeReplacement = $this->createReplacement(
            startDate: '2026-03-01',
            endDate: '2026-03-31',
        );
        $commandRepository->save($activeReplacement);

        $handlerRepository = new InMemoryTeacherReplacementRepository();
        $terminatedReplacement = TeacherReplacement::reconstitute(
            id: $activeReplacement->id,
            tenantId: $activeReplacement->tenantId,
            replacedTeacherId: $activeReplacement->replacedTeacherId,
            replacementTeacherId: $activeReplacement->replacementTeacherId,
            startDate: $activeReplacement->startDate,
            endDate: $activeReplacement->endDate,
            status: ReplacementStatus::ENDED,
            classes: $activeReplacement->classes,
            reason: $activeReplacement->reason,
            createdBy: $activeReplacement->createdBy,
            createdAt: $activeReplacement->createdAt,
            endedAt: new DateTimeImmutable('2026-03-31'),
            updatedAt: new DateTimeImmutable('2026-03-31'),
        );
        $handlerRepository->save($terminatedReplacement);

        $handler = new EndReplacementHandler($handlerRepository, $this->clock);

        $command = new EndExpiredReplacementsCommand(
            $commandRepository,
            $handler,
            $this->clock,
            new NullLogger(),
        );

        $tester = new CommandTester($command);
        $tester->execute([]);

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('1 remplacement(s) expiré(s) trouvé(s)', $tester->getDisplay());
        self::assertStringContainsString('déjà terminé', $tester->getDisplay());
        self::assertStringContainsString('0 remplacement(s) terminé(s) avec succès.', $tester->getDisplay());
    }

    #[Test]
    public function itOutputsCorrectMessagesForMultipleReplacements(): void
    {
        $replacement1 = $this->createReplacement(
            startDate: '2026-02-01',
            endDate: '2026-02-28',
        );
        $replacement2 = $this->createReplacement(
            startDate: '2026-03-01',
            endDate: '2026-03-31',
            replacementTeacherId: '550e8400-e29b-41d4-a716-446655440012',
        );
        $this->repository->save($replacement1);
        $this->repository->save($replacement2);

        $tester = $this->executeCommand();

        self::assertSame(0, $tester->getStatusCode());
        self::assertStringContainsString('2 remplacement(s) expiré(s) trouvé(s)', $tester->getDisplay());
        self::assertStringContainsString('2 remplacement(s) terminé(s) avec succès.', $tester->getDisplay());
    }

    private function executeCommand(): CommandTester
    {
        $handler = new EndReplacementHandler($this->repository, $this->clock);

        $command = new EndExpiredReplacementsCommand(
            $this->repository,
            $handler,
            $this->clock,
            new NullLogger(),
        );

        $tester = new CommandTester($command);
        $tester->execute([]);

        return $tester;
    }

    private function createReplacement(
        string $startDate = '2026-03-01',
        string $endDate = '2026-03-31',
        string $replacementTeacherId = self::REPLACEMENT_TEACHER_ID,
    ): TeacherReplacement {
        return TeacherReplacement::designer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            replacedTeacherId: UserId::fromString(self::REPLACED_TEACHER_ID),
            replacementTeacherId: UserId::fromString($replacementTeacherId),
            startDate: new DateTimeImmutable($startDate),
            endDate: new DateTimeImmutable($endDate),
            classes: [
                new ClassSubjectPair(
                    ClassId::fromString(self::CLASS_ID),
                    SubjectId::fromString(self::SUBJECT_ID),
                ),
            ],
            reason: null,
            createdBy: UserId::fromString(self::CREATED_BY_ID),
            now: new DateTimeImmutable('2026-02-15 10:00:00'),
        );
    }
}
