<?php

declare(strict_types=1);

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

use App\Administration\Domain\Exception\AffectationNotFoundException;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\TeacherAssignment\TeacherAssignment;
use App\Administration\Domain\Model\TeacherAssignment\TeacherAssignmentId;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryTeacherAssignmentRepository;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class InMemoryTeacherAssignmentRepositoryTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';
    private const string CLASS_ID = '550e8400-e29b-41d4-a716-446655440020';
    private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440030';
    private const string ACADEMIC_YEAR_ID = '550e8400-e29b-41d4-a716-446655440040';

    private InMemoryTeacherAssignmentRepository $repository;

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

    #[Test]
    public function saveAndGet(): void
    {
        $assignment = $this->createAssignment();

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

        $tenantId = TenantId::fromString(self::TENANT_ID);
        $retrieved = $this->repository->get($assignment->id, $tenantId);
        self::assertTrue($assignment->id->equals($retrieved->id));
    }

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

        $this->repository->get(
            TeacherAssignmentId::generate(),
            TenantId::fromString(self::TENANT_ID),
        );
    }

    #[Test]
    public function getThrowsForDifferentTenant(): void
    {
        $assignment = $this->createAssignment();
        $this->repository->save($assignment);

        $this->expectException(AffectationNotFoundException::class);

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

    #[Test]
    public function findByIdReturnsNullForUnknownId(): void
    {
        $result = $this->repository->findById(
            TeacherAssignmentId::generate(),
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertNull($result);
    }

    #[Test]
    public function findByIdReturnsNullForDifferentTenant(): void
    {
        $assignment = $this->createAssignment();
        $this->repository->save($assignment);

        $found = $this->repository->findById(
            $assignment->id,
            TenantId::fromString('550e8400-e29b-41d4-a716-446655440099'),
        );

        self::assertNull($found);
    }

    #[Test]
    public function findByIdReturnsAssignment(): void
    {
        $assignment = $this->createAssignment();
        $this->repository->save($assignment);

        $found = $this->repository->findById(
            $assignment->id,
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertNotNull($found);
        self::assertTrue($assignment->id->equals($found->id));
    }

    #[Test]
    public function findByTeacherClassSubjectReturnsActiveAssignment(): void
    {
        $assignment = $this->createAssignment();
        $this->repository->save($assignment);

        $found = $this->repository->findByTeacherClassSubject(
            UserId::fromString(self::TEACHER_ID),
            ClassId::fromString(self::CLASS_ID),
            SubjectId::fromString(self::SUBJECT_ID),
            AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertNotNull($found);
        self::assertTrue($assignment->id->equals($found->id));
    }

    #[Test]
    public function findByTeacherClassSubjectReturnsNullForRemovedAssignment(): void
    {
        $assignment = $this->createAssignment();
        $assignment->retirer(new DateTimeImmutable('2026-02-11 10:00:00'));
        $this->repository->save($assignment);

        $found = $this->repository->findByTeacherClassSubject(
            UserId::fromString(self::TEACHER_ID),
            ClassId::fromString(self::CLASS_ID),
            SubjectId::fromString(self::SUBJECT_ID),
            AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertNull($found);
    }

    #[Test]
    public function findByTeacherClassSubjectReturnsNullForDifferentTenant(): void
    {
        $assignment = $this->createAssignment();
        $this->repository->save($assignment);

        $found = $this->repository->findByTeacherClassSubject(
            UserId::fromString(self::TEACHER_ID),
            ClassId::fromString(self::CLASS_ID),
            SubjectId::fromString(self::SUBJECT_ID),
            AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
            TenantId::fromString('550e8400-e29b-41d4-a716-446655440099'),
        );

        self::assertNull($found);
    }

    #[Test]
    public function findRemovedByTeacherClassSubjectReturnsRemovedAssignment(): void
    {
        $assignment = $this->createAssignment();
        $assignment->retirer(new DateTimeImmutable('2026-02-11 10:00:00'));
        $this->repository->save($assignment);

        $found = $this->repository->findRemovedByTeacherClassSubject(
            UserId::fromString(self::TEACHER_ID),
            ClassId::fromString(self::CLASS_ID),
            SubjectId::fromString(self::SUBJECT_ID),
            AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertNotNull($found);
        self::assertTrue($assignment->id->equals($found->id));
    }

    #[Test]
    public function findRemovedByTeacherClassSubjectReturnsNullForActiveAssignment(): void
    {
        $assignment = $this->createAssignment();
        $this->repository->save($assignment);

        $found = $this->repository->findRemovedByTeacherClassSubject(
            UserId::fromString(self::TEACHER_ID),
            ClassId::fromString(self::CLASS_ID),
            SubjectId::fromString(self::SUBJECT_ID),
            AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertNull($found);
    }

    #[Test]
    public function findActiveByTeacher(): void
    {
        $assignment1 = $this->createAssignment();
        $assignment2 = $this->createAssignment(
            subjectId: '550e8400-e29b-41d4-a716-446655440031',
        );
        $removedAssignment = $this->createAssignment(
            subjectId: '550e8400-e29b-41d4-a716-446655440032',
        );
        $removedAssignment->retirer(new DateTimeImmutable('2026-02-11 10:00:00'));

        $this->repository->save($assignment1);
        $this->repository->save($assignment2);
        $this->repository->save($removedAssignment);

        $result = $this->repository->findActiveByTeacher(
            UserId::fromString(self::TEACHER_ID),
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertCount(2, $result);
    }

    #[Test]
    public function findActiveByTeacherReturnsEmptyForDifferentTenant(): void
    {
        $assignment = $this->createAssignment();
        $this->repository->save($assignment);

        $result = $this->repository->findActiveByTeacher(
            UserId::fromString(self::TEACHER_ID),
            TenantId::fromString('550e8400-e29b-41d4-a716-446655440099'),
        );

        self::assertEmpty($result);
    }

    #[Test]
    public function findActiveByClass(): void
    {
        $assignment1 = $this->createAssignment();
        $assignment2 = $this->createAssignment(
            teacherId: '550e8400-e29b-41d4-a716-446655440011',
        );
        $removedAssignment = $this->createAssignment(
            teacherId: '550e8400-e29b-41d4-a716-446655440012',
        );
        $removedAssignment->retirer(new DateTimeImmutable('2026-02-11 10:00:00'));

        $this->repository->save($assignment1);
        $this->repository->save($assignment2);
        $this->repository->save($removedAssignment);

        $result = $this->repository->findActiveByClass(
            ClassId::fromString(self::CLASS_ID),
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertCount(2, $result);
    }

    #[Test]
    public function findActiveByClassReturnsEmptyForDifferentTenant(): void
    {
        $assignment = $this->createAssignment();
        $this->repository->save($assignment);

        $result = $this->repository->findActiveByClass(
            ClassId::fromString(self::CLASS_ID),
            TenantId::fromString('550e8400-e29b-41d4-a716-446655440099'),
        );

        self::assertEmpty($result);
    }

    private function createAssignment(
        ?string $teacherId = null,
        ?string $subjectId = null,
    ): TeacherAssignment {
        return TeacherAssignment::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            teacherId: UserId::fromString($teacherId ?? self::TEACHER_ID),
            classId: ClassId::fromString(self::CLASS_ID),
            subjectId: SubjectId::fromString($subjectId ?? self::SUBJECT_ID),
            academicYearId: AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
            createdAt: new DateTimeImmutable('2026-02-10 10:00:00'),
        );
    }
}
