<?php

declare(strict_types=1);

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

use App\Administration\Domain\Event\ClasseArchivee;
use App\Administration\Domain\Event\ClasseCreee;
use App\Administration\Domain\Event\ClasseModifiee;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Model\SchoolClass\ClassName;
use App\Administration\Domain\Model\SchoolClass\ClassStatus;
use App\Administration\Domain\Model\SchoolClass\SchoolClass;
use App\Administration\Domain\Model\SchoolClass\SchoolId;
use App\Administration\Domain\Model\SchoolClass\SchoolLevel;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class SchoolClassTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string SCHOOL_ID = '550e8400-e29b-41d4-a716-446655440002';
    private const string ACADEMIC_YEAR_ID = '550e8400-e29b-41d4-a716-446655440003';

    #[Test]
    public function creerCreatesClassWithActiveStatus(): void
    {
        $class = $this->createClass();

        self::assertSame(ClassStatus::ACTIVE, $class->status);
        self::assertTrue($class->estActive());
        self::assertTrue($class->peutRecevoirEleves());
    }

    #[Test]
    public function creerRecordsClasseCreeeEvent(): void
    {
        $class = $this->createClass();

        $events = $class->pullDomainEvents();

        self::assertCount(1, $events);
        self::assertInstanceOf(ClasseCreee::class, $events[0]);
        self::assertSame($class->id, $events[0]->classId);
        self::assertSame($class->tenantId, $events[0]->tenantId);
        self::assertSame($class->name, $events[0]->name);
    }

    #[Test]
    public function creerSetsAllProperties(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $schoolId = SchoolId::fromString(self::SCHOOL_ID);
        $academicYearId = AcademicYearId::fromString(self::ACADEMIC_YEAR_ID);
        $name = new ClassName('6ème A');
        $level = SchoolLevel::SIXIEME;
        $capacity = 30;
        $createdAt = new DateTimeImmutable('2026-01-15 10:00:00');

        $class = SchoolClass::creer(
            tenantId: $tenantId,
            schoolId: $schoolId,
            academicYearId: $academicYearId,
            name: $name,
            level: $level,
            capacity: $capacity,
            createdAt: $createdAt,
        );

        self::assertTrue($class->tenantId->equals($tenantId));
        self::assertTrue($class->schoolId->equals($schoolId));
        self::assertTrue($class->academicYearId->equals($academicYearId));
        self::assertTrue($class->name->equals($name));
        self::assertSame($level, $class->level);
        self::assertSame($capacity, $class->capacity);
        self::assertEquals($createdAt, $class->createdAt);
        self::assertEquals($createdAt, $class->updatedAt);
        self::assertNull($class->deletedAt);
        self::assertNull($class->description);
    }

    #[Test]
    public function creerWithNullLevelAndCapacity(): void
    {
        $class = SchoolClass::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolId: SchoolId::fromString(self::SCHOOL_ID),
            academicYearId: AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
            name: new ClassName('Classe spéciale'),
            level: null,
            capacity: null,
            createdAt: new DateTimeImmutable(),
        );

        self::assertNull($class->level);
        self::assertNull($class->capacity);
    }

    #[Test]
    public function renommerChangesNameAndRecordsEvent(): void
    {
        $class = $this->createClass();
        $class->pullDomainEvents();
        $ancienNom = $class->name;
        $nouveauNom = new ClassName('6ème B');
        $at = new DateTimeImmutable('2026-02-01 10:00:00');

        $class->renommer($nouveauNom, $at);

        self::assertTrue($class->name->equals($nouveauNom));
        self::assertEquals($at, $class->updatedAt);

        $events = $class->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(ClasseModifiee::class, $events[0]);
        self::assertTrue($events[0]->ancienNom->equals($ancienNom));
        self::assertTrue($events[0]->nouveauNom->equals($nouveauNom));
    }

    #[Test]
    public function renommerWithSameNameDoesNothing(): void
    {
        $class = $this->createClass();
        $class->pullDomainEvents();
        $originalUpdatedAt = $class->updatedAt;

        $class->renommer(new ClassName('6ème A'), new DateTimeImmutable('2026-02-01 10:00:00'));

        self::assertEquals($originalUpdatedAt, $class->updatedAt);
        self::assertEmpty($class->pullDomainEvents());
    }

    #[Test]
    public function changerNiveauUpdatesLevel(): void
    {
        $class = $this->createClass();
        $at = new DateTimeImmutable('2026-02-01 10:00:00');

        $class->changerNiveau(SchoolLevel::CINQUIEME, $at);

        self::assertSame(SchoolLevel::CINQUIEME, $class->level);
        self::assertEquals($at, $class->updatedAt);
    }

    #[Test]
    public function changerNiveauWithSameLevelDoesNothing(): void
    {
        $class = $this->createClass();
        $originalUpdatedAt = $class->updatedAt;

        $class->changerNiveau(SchoolLevel::SIXIEME, new DateTimeImmutable('2026-02-01 10:00:00'));

        self::assertEquals($originalUpdatedAt, $class->updatedAt);
    }

    #[Test]
    public function changerCapaciteUpdatesCapacity(): void
    {
        $class = $this->createClass();
        $at = new DateTimeImmutable('2026-02-01 10:00:00');

        $class->changerCapacite(35, $at);

        self::assertSame(35, $class->capacity);
        self::assertEquals($at, $class->updatedAt);
    }

    #[Test]
    public function changerCapaciteWithSameValueDoesNothing(): void
    {
        $class = $this->createClass();
        $originalUpdatedAt = $class->updatedAt;

        $class->changerCapacite(30, new DateTimeImmutable('2026-02-01 10:00:00'));

        self::assertEquals($originalUpdatedAt, $class->updatedAt);
    }

    #[Test]
    public function decrireUpdatesDescription(): void
    {
        $class = $this->createClass();
        $at = new DateTimeImmutable('2026-02-01 10:00:00');

        $class->decrire('Classe option musique', $at);

        self::assertSame('Classe option musique', $class->description);
        self::assertEquals($at, $class->updatedAt);
    }

    #[Test]
    public function archiverChangesStatusAndRecordsEvent(): void
    {
        $class = $this->createClass();
        $class->pullDomainEvents();
        $at = new DateTimeImmutable('2026-02-01 10:00:00');

        $class->archiver($at);

        self::assertSame(ClassStatus::ARCHIVED, $class->status);
        self::assertFalse($class->estActive());
        self::assertFalse($class->peutRecevoirEleves());
        self::assertEquals($at, $class->deletedAt);
        self::assertEquals($at, $class->updatedAt);

        $events = $class->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(ClasseArchivee::class, $events[0]);
    }

    #[Test]
    public function archiverAlreadyArchivedClassDoesNothing(): void
    {
        $class = $this->createClass();
        $class->archiver(new DateTimeImmutable('2026-02-01 10:00:00'));
        $class->pullDomainEvents();
        $originalDeletedAt = $class->deletedAt;

        $class->archiver(new DateTimeImmutable('2026-02-02 10:00:00'));

        self::assertEquals($originalDeletedAt, $class->deletedAt);
        self::assertEmpty($class->pullDomainEvents());
    }

    #[Test]
    public function reconstituteRestoresAllProperties(): void
    {
        $id = \App\Administration\Domain\Model\SchoolClass\ClassId::generate();
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $schoolId = SchoolId::fromString(self::SCHOOL_ID);
        $academicYearId = AcademicYearId::fromString(self::ACADEMIC_YEAR_ID);
        $name = new ClassName('6ème A');
        $level = SchoolLevel::SIXIEME;
        $capacity = 30;
        $status = ClassStatus::ARCHIVED;
        $description = 'Test description';
        $createdAt = new DateTimeImmutable('2026-01-15 10:00:00');
        $updatedAt = new DateTimeImmutable('2026-02-01 10:00:00');
        $deletedAt = new DateTimeImmutable('2026-02-01 10:00:00');

        $class = SchoolClass::reconstitute(
            id: $id,
            tenantId: $tenantId,
            schoolId: $schoolId,
            academicYearId: $academicYearId,
            name: $name,
            level: $level,
            capacity: $capacity,
            status: $status,
            description: $description,
            createdAt: $createdAt,
            updatedAt: $updatedAt,
            deletedAt: $deletedAt,
        );

        self::assertTrue($class->id->equals($id));
        self::assertTrue($class->tenantId->equals($tenantId));
        self::assertTrue($class->schoolId->equals($schoolId));
        self::assertTrue($class->academicYearId->equals($academicYearId));
        self::assertTrue($class->name->equals($name));
        self::assertSame($level, $class->level);
        self::assertSame($capacity, $class->capacity);
        self::assertSame($status, $class->status);
        self::assertSame($description, $class->description);
        self::assertEquals($createdAt, $class->createdAt);
        self::assertEquals($updatedAt, $class->updatedAt);
        self::assertEquals($deletedAt, $class->deletedAt);
        self::assertEmpty($class->pullDomainEvents());
    }

    private function createClass(): SchoolClass
    {
        return SchoolClass::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolId: SchoolId::fromString(self::SCHOOL_ID),
            academicYearId: AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
            name: new ClassName('6ème A'),
            level: SchoolLevel::SIXIEME,
            capacity: 30,
            createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
        );
    }
}
