<?php

declare(strict_types=1);

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

use App\Administration\Domain\Event\MatiereCreee;
use App\Administration\Domain\Event\MatiereModifiee;
use App\Administration\Domain\Event\MatiereSupprimee;
use App\Administration\Domain\Model\SchoolClass\SchoolId;
use App\Administration\Domain\Model\Subject\Subject;
use App\Administration\Domain\Model\Subject\SubjectCode;
use App\Administration\Domain\Model\Subject\SubjectColor;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\Subject\SubjectName;
use App\Administration\Domain\Model\Subject\SubjectStatus;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

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

    #[Test]
    public function creerCreatesSubjectWithActiveStatus(): void
    {
        $subject = $this->createSubject();

        self::assertSame(SubjectStatus::ACTIVE, $subject->status);
        self::assertTrue($subject->estActive());
        self::assertTrue($subject->peutEtreUtilisee());
    }

    #[Test]
    public function creerRecordsMatiereCreeeEvent(): void
    {
        $subject = $this->createSubject();

        $events = $subject->pullDomainEvents();

        self::assertCount(1, $events);
        self::assertInstanceOf(MatiereCreee::class, $events[0]);
        self::assertSame($subject->id, $events[0]->subjectId);
        self::assertSame($subject->tenantId, $events[0]->tenantId);
        self::assertSame($subject->name, $events[0]->name);
        self::assertSame($subject->code, $events[0]->code);
    }

    #[Test]
    public function creerSetsAllProperties(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $schoolId = SchoolId::fromString(self::SCHOOL_ID);
        $name = new SubjectName('Mathématiques');
        $code = new SubjectCode('MATH');
        $color = new SubjectColor('#3B82F6');
        $createdAt = new DateTimeImmutable('2026-01-15 10:00:00');

        $subject = Subject::creer(
            tenantId: $tenantId,
            schoolId: $schoolId,
            name: $name,
            code: $code,
            color: $color,
            createdAt: $createdAt,
        );

        self::assertTrue($subject->tenantId->equals($tenantId));
        self::assertTrue($subject->schoolId->equals($schoolId));
        self::assertTrue($subject->name->equals($name));
        self::assertTrue($subject->code->equals($code));
        self::assertNotNull($subject->color);
        self::assertTrue($subject->color->equals($color));
        self::assertEquals($createdAt, $subject->createdAt);
        self::assertEquals($createdAt, $subject->updatedAt);
        self::assertNull($subject->deletedAt);
        self::assertNull($subject->description);
    }

    #[Test]
    public function creerWithNullColor(): void
    {
        $subject = Subject::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolId: SchoolId::fromString(self::SCHOOL_ID),
            name: new SubjectName('Arts plastiques'),
            code: new SubjectCode('ART'),
            color: null,
            createdAt: new DateTimeImmutable(),
        );

        self::assertNull($subject->color);
    }

    #[Test]
    public function renommerChangesNameAndRecordsEvent(): void
    {
        $subject = $this->createSubject();
        $subject->pullDomainEvents();
        $ancienNom = $subject->name;
        $nouveauNom = new SubjectName('Mathématiques avancées');
        $at = new DateTimeImmutable('2026-02-01 10:00:00');

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

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

        $events = $subject->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(MatiereModifiee::class, $events[0]);
        self::assertSame('nom', $events[0]->champ);
        self::assertSame((string) $ancienNom, $events[0]->ancienneValeur);
        self::assertSame((string) $nouveauNom, $events[0]->nouvelleValeur);
    }

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

        $subject->renommer(new SubjectName('Mathématiques'), new DateTimeImmutable('2026-02-01 10:00:00'));

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

    #[Test]
    public function changerCodeUpdatesCodeAndRecordsEvent(): void
    {
        $subject = $this->createSubject();
        $subject->pullDomainEvents();
        $at = new DateTimeImmutable('2026-02-01 10:00:00');
        $nouveauCode = new SubjectCode('MATHS');

        $subject->changerCode($nouveauCode, $at);

        self::assertTrue($subject->code->equals($nouveauCode));
        self::assertEquals($at, $subject->updatedAt);

        $events = $subject->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(MatiereModifiee::class, $events[0]);
        self::assertSame('code', $events[0]->champ);
        self::assertSame('MATH', $events[0]->ancienneValeur);
        self::assertSame('MATHS', $events[0]->nouvelleValeur);
    }

    #[Test]
    public function changerCodeWithSameCodeDoesNothing(): void
    {
        $subject = $this->createSubject();
        $subject->pullDomainEvents();
        $originalUpdatedAt = $subject->updatedAt;

        $subject->changerCode(new SubjectCode('MATH'), new DateTimeImmutable('2026-02-01 10:00:00'));

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

    #[Test]
    public function changerCouleurUpdatesColorAndRecordsEvent(): void
    {
        $subject = $this->createSubject();
        $subject->pullDomainEvents();
        $at = new DateTimeImmutable('2026-02-01 10:00:00');
        $nouvelleCouleur = new SubjectColor('#EF4444');

        $subject->changerCouleur($nouvelleCouleur, $at);

        self::assertNotNull($subject->color);
        self::assertTrue($subject->color->equals($nouvelleCouleur));
        self::assertEquals($at, $subject->updatedAt);

        $events = $subject->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(MatiereModifiee::class, $events[0]);
        self::assertSame('couleur', $events[0]->champ);
        self::assertSame('#3B82F6', $events[0]->ancienneValeur);
        self::assertSame('#EF4444', $events[0]->nouvelleValeur);
    }

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

        $subject->changerCouleur(null, $at);

        self::assertNull($subject->color);
        self::assertEquals($at, $subject->updatedAt);

        $events = $subject->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(MatiereModifiee::class, $events[0]);
        self::assertSame('couleur', $events[0]->champ);
        self::assertSame('#3B82F6', $events[0]->ancienneValeur);
        self::assertNull($events[0]->nouvelleValeur);
    }

    #[Test]
    public function changerCouleurWithSameColorDoesNothing(): void
    {
        $subject = $this->createSubject();
        $subject->pullDomainEvents();
        $originalUpdatedAt = $subject->updatedAt;

        $subject->changerCouleur(new SubjectColor('#3B82F6'), new DateTimeImmutable('2026-02-01 10:00:00'));

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

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

        $subject->decrire('Cours de mathématiques généralistes', $at);

        self::assertSame('Cours de mathématiques généralistes', $subject->description);
        self::assertEquals($at, $subject->updatedAt);

        $events = $subject->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(MatiereModifiee::class, $events[0]);
        self::assertSame('description', $events[0]->champ);
        self::assertNull($events[0]->ancienneValeur);
        self::assertSame('Cours de mathématiques généralistes', $events[0]->nouvelleValeur);
    }

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

        $subject->archiver($at);

        self::assertSame(SubjectStatus::ARCHIVED, $subject->status);
        self::assertFalse($subject->estActive());
        self::assertFalse($subject->peutEtreUtilisee());
        self::assertEquals($at, $subject->deletedAt);
        self::assertEquals($at, $subject->updatedAt);

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

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

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

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

    #[Test]
    public function reconstituteRestoresAllProperties(): void
    {
        $id = SubjectId::generate();
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $schoolId = SchoolId::fromString(self::SCHOOL_ID);
        $name = new SubjectName('Mathématiques');
        $code = new SubjectCode('MATH');
        $color = new SubjectColor('#3B82F6');
        $status = SubjectStatus::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');

        $subject = Subject::reconstitute(
            id: $id,
            tenantId: $tenantId,
            schoolId: $schoolId,
            name: $name,
            code: $code,
            color: $color,
            status: $status,
            description: $description,
            createdAt: $createdAt,
            updatedAt: $updatedAt,
            deletedAt: $deletedAt,
        );

        self::assertTrue($subject->id->equals($id));
        self::assertTrue($subject->tenantId->equals($tenantId));
        self::assertTrue($subject->schoolId->equals($schoolId));
        self::assertTrue($subject->name->equals($name));
        self::assertTrue($subject->code->equals($code));
        self::assertNotNull($subject->color);
        self::assertTrue($subject->color->equals($color));
        self::assertSame($status, $subject->status);
        self::assertSame($description, $subject->description);
        self::assertEquals($createdAt, $subject->createdAt);
        self::assertEquals($updatedAt, $subject->updatedAt);
        self::assertEquals($deletedAt, $subject->deletedAt);
        self::assertEmpty($subject->pullDomainEvents());
    }

    private function createSubject(): Subject
    {
        return Subject::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolId: SchoolId::fromString(self::SCHOOL_ID),
            name: new SubjectName('Mathématiques'),
            code: new SubjectCode('MATH'),
            color: new SubjectColor('#3B82F6'),
            createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
        );
    }
}
