<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Domain\Model\TeacherAssignment;

use App\Administration\Domain\Event\AffectationRetiree;
use App\Administration\Domain\Event\EnseignantAffecte;
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\AssignmentStatus;
use App\Administration\Domain\Model\TeacherAssignment\TeacherAssignment;
use App\Administration\Domain\Model\TeacherAssignment\TeacherAssignmentId;
use App\Administration\Domain\Model\User\UserId;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class TeacherAssignmentTest 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';

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

        self::assertSame(AssignmentStatus::ACTIVE, $assignment->status);
        self::assertTrue($assignment->estActive());
        self::assertNull($assignment->endDate);
    }

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

        $events = $assignment->pullDomainEvents();

        self::assertCount(1, $events);
        self::assertInstanceOf(EnseignantAffecte::class, $events[0]);
        self::assertSame($assignment->id, $events[0]->assignmentId);
        self::assertSame($assignment->teacherId, $events[0]->teacherId);
        self::assertSame($assignment->classId, $events[0]->classId);
        self::assertSame($assignment->subjectId, $events[0]->subjectId);
    }

    #[Test]
    public function creerSetsAllProperties(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $teacherId = UserId::fromString(self::TEACHER_ID);
        $classId = ClassId::fromString(self::CLASS_ID);
        $subjectId = SubjectId::fromString(self::SUBJECT_ID);
        $academicYearId = AcademicYearId::fromString(self::ACADEMIC_YEAR_ID);
        $createdAt = new DateTimeImmutable('2026-02-12 10:00:00');

        $assignment = TeacherAssignment::creer(
            tenantId: $tenantId,
            teacherId: $teacherId,
            classId: $classId,
            subjectId: $subjectId,
            academicYearId: $academicYearId,
            createdAt: $createdAt,
        );

        self::assertTrue($assignment->tenantId->equals($tenantId));
        self::assertTrue($assignment->teacherId->equals($teacherId));
        self::assertTrue($assignment->classId->equals($classId));
        self::assertTrue($assignment->subjectId->equals($subjectId));
        self::assertTrue($assignment->academicYearId->equals($academicYearId));
        self::assertEquals($createdAt, $assignment->startDate);
        self::assertEquals($createdAt, $assignment->createdAt);
        self::assertEquals($createdAt, $assignment->updatedAt);
        self::assertNull($assignment->endDate);
    }

    #[Test]
    public function retirerChangesStatusAndRecordsEvent(): void
    {
        $assignment = $this->createAssignment();
        $assignment->pullDomainEvents();
        $at = new DateTimeImmutable('2026-03-01 10:00:00');

        $assignment->retirer($at);

        self::assertSame(AssignmentStatus::REMOVED, $assignment->status);
        self::assertFalse($assignment->estActive());
        self::assertEquals($at, $assignment->endDate);
        self::assertEquals($at, $assignment->updatedAt);

        $events = $assignment->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(AffectationRetiree::class, $events[0]);
        self::assertSame($assignment->id, $events[0]->assignmentId);
        self::assertSame($assignment->teacherId, $events[0]->teacherId);
        self::assertSame($assignment->classId, $events[0]->classId);
        self::assertSame($assignment->subjectId, $events[0]->subjectId);
    }

    #[Test]
    public function retirerAlreadyRemovedAssignmentDoesNothing(): void
    {
        $assignment = $this->createAssignment();
        $assignment->retirer(new DateTimeImmutable('2026-03-01 10:00:00'));
        $assignment->pullDomainEvents();
        $originalEndDate = $assignment->endDate;

        $assignment->retirer(new DateTimeImmutable('2026-03-02 10:00:00'));

        self::assertEquals($originalEndDate, $assignment->endDate);
        self::assertEmpty($assignment->pullDomainEvents());
    }

    #[Test]
    public function reconstituteRestoresAllProperties(): void
    {
        $id = TeacherAssignmentId::generate();
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $teacherId = UserId::fromString(self::TEACHER_ID);
        $classId = ClassId::fromString(self::CLASS_ID);
        $subjectId = SubjectId::fromString(self::SUBJECT_ID);
        $academicYearId = AcademicYearId::fromString(self::ACADEMIC_YEAR_ID);
        $startDate = new DateTimeImmutable('2026-02-12 10:00:00');
        $endDate = new DateTimeImmutable('2026-03-01 10:00:00');
        $status = AssignmentStatus::REMOVED;
        $createdAt = new DateTimeImmutable('2026-02-12 10:00:00');
        $updatedAt = new DateTimeImmutable('2026-03-01 10:00:00');

        $assignment = TeacherAssignment::reconstitute(
            id: $id,
            tenantId: $tenantId,
            teacherId: $teacherId,
            classId: $classId,
            subjectId: $subjectId,
            academicYearId: $academicYearId,
            startDate: $startDate,
            endDate: $endDate,
            status: $status,
            createdAt: $createdAt,
            updatedAt: $updatedAt,
        );

        self::assertTrue($assignment->id->equals($id));
        self::assertTrue($assignment->tenantId->equals($tenantId));
        self::assertTrue($assignment->teacherId->equals($teacherId));
        self::assertTrue($assignment->classId->equals($classId));
        self::assertTrue($assignment->subjectId->equals($subjectId));
        self::assertTrue($assignment->academicYearId->equals($academicYearId));
        self::assertEquals($startDate, $assignment->startDate);
        self::assertEquals($endDate, $assignment->endDate);
        self::assertSame($status, $assignment->status);
        self::assertEquals($createdAt, $assignment->createdAt);
        self::assertEquals($updatedAt, $assignment->updatedAt);
        self::assertEmpty($assignment->pullDomainEvents());
    }

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