<?php

declare(strict_types=1);

namespace App\Tests\Unit\Scolarite\Infrastructure\Persistence\InMemory;

use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Exception\RemplacementNotFoundException;
use App\Scolarite\Domain\Model\TeacherReplacement\ClassSubjectPair;
use App\Scolarite\Domain\Model\TeacherReplacement\TeacherReplacement;
use App\Scolarite\Domain\Model\TeacherReplacement\TeacherReplacementId;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryTeacherReplacementRepository;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class InMemoryTeacherReplacementRepositoryTest 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 OTHER_REPLACEMENT_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440012';
    private const string CLASS_ID = '550e8400-e29b-41d4-a716-446655440020';
    private const string OTHER_CLASS_ID = '550e8400-e29b-41d4-a716-446655440021';
    private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440030';
    private const string OTHER_SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440031';
    private const string CREATED_BY_ID = '550e8400-e29b-41d4-a716-446655440099';

    private InMemoryTeacherReplacementRepository $repository;

    protected function setUp(): void
    {
        $this->repository = new InMemoryTeacherReplacementRepository();
    }

    #[Test]
    public function saveAndGet(): void
    {
        $replacement = $this->createReplacement();
        $this->repository->save($replacement);

        $found = $this->repository->get($replacement->id, $replacement->tenantId);

        self::assertSame($replacement, $found);
    }

    #[Test]
    public function findByIdReturnsNullWhenNotFound(): void
    {
        $result = $this->repository->findById(
            TeacherReplacementId::fromString('550e8400-e29b-41d4-a716-446655440099'),
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertNull($result);
    }

    #[Test]
    public function findByIdReturnsNullWhenTenantDoesNotMatch(): void
    {
        $replacement = $this->createReplacement();
        $this->repository->save($replacement);

        $result = $this->repository->findById(
            $replacement->id,
            TenantId::fromString(self::OTHER_TENANT_ID),
        );

        self::assertNull($result);
    }

    #[Test]
    public function getThrowsWhenNotFound(): void
    {
        $this->expectException(RemplacementNotFoundException::class);

        $this->repository->get(
            TeacherReplacementId::fromString('550e8400-e29b-41d4-a716-446655440099'),
            TenantId::fromString(self::TENANT_ID),
        );
    }

    #[Test]
    public function findActiveByTenantReturnsOnlyActiveInDateRange(): void
    {
        $activeReplacement = $this->createReplacement();
        $this->repository->save($activeReplacement);

        $endedReplacement = $this->createReplacement(
            replacementTeacherId: self::OTHER_REPLACEMENT_TEACHER_ID,
        );
        $endedReplacement->terminer(new DateTimeImmutable('2026-03-10'));
        $this->repository->save($endedReplacement);

        $result = $this->repository->findActiveByTenant(
            TenantId::fromString(self::TENANT_ID),
            new DateTimeImmutable('2026-03-15'),
        );

        self::assertCount(1, $result);
        self::assertSame($activeReplacement, $result[0]);
    }

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

        $result = $this->repository->findActiveByTenant(
            TenantId::fromString(self::TENANT_ID),
            new DateTimeImmutable('2026-03-15'),
        );

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

    #[Test]
    public function findActiveByReplacementTeacherFiltersCorrectly(): void
    {
        $replacement = $this->createReplacement();
        $this->repository->save($replacement);

        $otherReplacement = $this->createReplacement(
            replacementTeacherId: self::OTHER_REPLACEMENT_TEACHER_ID,
        );
        $this->repository->save($otherReplacement);

        $result = $this->repository->findActiveByReplacementTeacher(
            UserId::fromString(self::REPLACEMENT_TEACHER_ID),
            TenantId::fromString(self::TENANT_ID),
            new DateTimeImmutable('2026-03-15'),
        );

        self::assertCount(1, $result);
        self::assertSame($replacement, $result[0]);
    }

    #[Test]
    public function findActiveByReplacementTeacherExcludesInactiveReplacements(): void
    {
        $replacement = $this->createReplacement();
        $this->repository->save($replacement);

        $result = $this->repository->findActiveByReplacementTeacher(
            UserId::fromString(self::REPLACEMENT_TEACHER_ID),
            TenantId::fromString(self::TENANT_ID),
            new DateTimeImmutable('2026-04-15'),
        );

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

    #[Test]
    public function findActiveReplacementMatchesClassSubjectPair(): void
    {
        $replacement = $this->createReplacement();
        $this->repository->save($replacement);

        $result = $this->repository->findActiveReplacement(
            UserId::fromString(self::REPLACEMENT_TEACHER_ID),
            ClassId::fromString(self::CLASS_ID),
            SubjectId::fromString(self::SUBJECT_ID),
            new DateTimeImmutable('2026-03-15'),
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertNotNull($result);
        self::assertSame($replacement, $result);
    }

    #[Test]
    public function findActiveReplacementReturnsNullForNonMatchingPair(): void
    {
        $replacement = $this->createReplacement();
        $this->repository->save($replacement);

        $result = $this->repository->findActiveReplacement(
            UserId::fromString(self::REPLACEMENT_TEACHER_ID),
            ClassId::fromString(self::OTHER_CLASS_ID),
            SubjectId::fromString(self::OTHER_SUBJECT_ID),
            new DateTimeImmutable('2026-03-15'),
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertNull($result);
    }

    #[Test]
    public function findExpiredReturnsOnlyExpiredAcrossTenants(): void
    {
        $expiredReplacement = $this->createReplacement(
            startDate: '2026-02-01',
            endDate: '2026-02-28',
        );
        $this->repository->save($expiredReplacement);

        $expiredOtherTenant = $this->createReplacement(
            tenantId: self::OTHER_TENANT_ID,
            startDate: '2026-02-01',
            endDate: '2026-02-28',
            replacementTeacherId: self::OTHER_REPLACEMENT_TEACHER_ID,
        );
        $this->repository->save($expiredOtherTenant);

        $activeReplacement = $this->createReplacement(
            startDate: '2026-03-01',
            endDate: '2026-04-30',
            replacementTeacherId: '550e8400-e29b-41d4-a716-446655440013',
        );
        $this->repository->save($activeReplacement);

        $result = $this->repository->findExpired(new DateTimeImmutable('2026-03-15'));

        self::assertCount(2, $result);
        self::assertContains($expiredReplacement, $result);
        self::assertContains($expiredOtherTenant, $result);
    }

    #[Test]
    public function findExpiredExcludesAlreadyEndedReplacements(): void
    {
        $replacement = $this->createReplacement(
            startDate: '2026-02-01',
            endDate: '2026-02-28',
        );
        $replacement->terminer(new DateTimeImmutable('2026-02-28'));
        $this->repository->save($replacement);

        $result = $this->repository->findExpired(new DateTimeImmutable('2026-03-15'));

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

    private function createReplacement(
        string $tenantId = self::TENANT_ID,
        string $startDate = '2026-03-01',
        string $endDate = '2026-03-31',
        string $replacementTeacherId = self::REPLACEMENT_TEACHER_ID,
    ): TeacherReplacement {
        return TeacherReplacement::designer(
            tenantId: TenantId::fromString($tenantId),
            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'),
        );
    }
}
