<?php

declare(strict_types=1);

namespace App\Tests\Unit\Scolarite\Application\Query\GetActiveReplacements;

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\Query\GetActiveReplacements\GetActiveReplacementsHandler;
use App\Scolarite\Application\Query\GetActiveReplacements\GetActiveReplacementsQuery;
use App\Scolarite\Application\Query\GetActiveReplacements\ReplacementDto;
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\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;

final class GetActiveReplacementsHandlerTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string OTHER_TENANT_ID = '550e8400-e29b-41d4-a716-446655440002';
    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 DateTimeImmutable $now;

    protected function setUp(): void
    {
        $this->repository = new InMemoryTeacherReplacementRepository();
        $this->now = new DateTimeImmutable('2026-03-15 10:00:00');
    }

    #[Test]
    public function itReturnsActiveReplacementsForTenant(): void
    {
        $this->saveActiveReplacement();

        $handler = $this->createHandler();
        $result = $handler(new GetActiveReplacementsQuery(self::TENANT_ID));

        self::assertCount(1, $result);
        self::assertContainsOnlyInstancesOf(ReplacementDto::class, $result);
    }

    #[Test]
    public function itReturnsEmptyArrayWhenNoneExist(): void
    {
        $handler = $this->createHandler();
        $result = $handler(new GetActiveReplacementsQuery(self::TENANT_ID));

        self::assertSame([], $result);
    }

    #[Test]
    public function itMapsCorrectlyToDtos(): void
    {
        $replacement = $this->saveActiveReplacement();

        $handler = $this->createHandler();
        $result = $handler(new GetActiveReplacementsQuery(self::TENANT_ID));

        self::assertCount(1, $result);

        $dto = $result[0];
        self::assertSame((string) $replacement->id, $dto->id);
        self::assertSame(self::REPLACED_TEACHER_ID, $dto->replacedTeacherId);
        self::assertSame(self::REPLACEMENT_TEACHER_ID, $dto->replacementTeacherId);
        self::assertEquals(new DateTimeImmutable('2026-03-01'), $dto->startDate);
        self::assertEquals(new DateTimeImmutable('2026-03-31'), $dto->endDate);
        self::assertSame(ReplacementStatus::ACTIVE->value, $dto->status);
        self::assertSame('Congé maladie', $dto->reason);
        self::assertCount(1, $dto->classes);
        self::assertSame(self::CLASS_ID, $dto->classes[0]['classId']);
        self::assertSame(self::SUBJECT_ID, $dto->classes[0]['subjectId']);
    }

    #[Test]
    public function itFiltersOutExpiredReplacements(): void
    {
        $this->saveReplacement(
            startDate: new DateTimeImmutable('2026-01-01'),
            endDate: new DateTimeImmutable('2026-02-01'),
        );

        $handler = $this->createHandler();
        $result = $handler(new GetActiveReplacementsQuery(self::TENANT_ID));

        self::assertSame([], $result);
    }

    #[Test]
    public function itFiltersOutEndedReplacements(): void
    {
        $replacement = $this->saveActiveReplacement();
        $replacement->terminer(new DateTimeImmutable('2026-03-10'));
        $this->repository->save($replacement);

        $handler = $this->createHandler();
        $result = $handler(new GetActiveReplacementsQuery(self::TENANT_ID));

        self::assertSame([], $result);
    }

    #[Test]
    public function itDoesNotReturnReplacementsFromOtherTenants(): void
    {
        $this->saveReplacement(tenantId: self::OTHER_TENANT_ID);

        $handler = $this->createHandler();
        $result = $handler(new GetActiveReplacementsQuery(self::TENANT_ID));

        self::assertSame([], $result);
    }

    private function saveActiveReplacement(): TeacherReplacement
    {
        return $this->saveReplacement();
    }

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

        $this->repository->save($replacement);

        return $replacement;
    }

    private function createHandler(): GetActiveReplacementsHandler
    {
        $now = $this->now;
        $clock = new class($now) implements Clock {
            public function __construct(private readonly DateTimeImmutable $now)
            {
            }

            public function now(): DateTimeImmutable
            {
                return $this->now;
            }
        };

        return new GetActiveReplacementsHandler($this->repository, $clock);
    }
}
