<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Application\Command\UpdateClass;

use App\Administration\Application\Command\UpdateClass\UpdateClassCommand;
use App\Administration\Application\Command\UpdateClass\UpdateClassHandler;
use App\Administration\Domain\Exception\ClasseDejaExistanteException;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Model\SchoolClass\ClassName;
use App\Administration\Domain\Model\SchoolClass\SchoolClass;
use App\Administration\Domain\Model\SchoolClass\SchoolId;
use App\Administration\Domain\Model\SchoolClass\SchoolLevel;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryClassRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

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

    private InMemoryClassRepository $classRepository;
    private Clock $clock;

    protected function setUp(): void
    {
        $this->classRepository = new InMemoryClassRepository();
        $this->clock = new class implements Clock {
            public function now(): DateTimeImmutable
            {
                return new DateTimeImmutable('2026-02-01 10:00:00');
            }
        };
    }

    #[Test]
    public function itUpdatesClassName(): void
    {
        $class = $this->createAndSaveClass();
        $handler = new UpdateClassHandler($this->classRepository, $this->clock);

        $command = new UpdateClassCommand(
            classId: (string) $class->id,
            name: '6ème B',
        );

        $handler($command);

        $updatedClass = $this->classRepository->get($class->id);
        self::assertSame('6ème B', (string) $updatedClass->name);
    }

    #[Test]
    public function itUpdatesClassLevel(): void
    {
        $class = $this->createAndSaveClass();
        $handler = new UpdateClassHandler($this->classRepository, $this->clock);

        $command = new UpdateClassCommand(
            classId: (string) $class->id,
            level: SchoolLevel::CINQUIEME->value,
        );

        $handler($command);

        $updatedClass = $this->classRepository->get($class->id);
        self::assertSame(SchoolLevel::CINQUIEME, $updatedClass->level);
    }

    #[Test]
    public function itClearsClassLevel(): void
    {
        $class = $this->createAndSaveClass();
        $handler = new UpdateClassHandler($this->classRepository, $this->clock);

        $command = new UpdateClassCommand(
            classId: (string) $class->id,
            clearLevel: true,
        );

        $handler($command);

        $updatedClass = $this->classRepository->get($class->id);
        self::assertNull($updatedClass->level);
    }

    #[Test]
    public function itUpdatesClassCapacity(): void
    {
        $class = $this->createAndSaveClass();
        $handler = new UpdateClassHandler($this->classRepository, $this->clock);

        $command = new UpdateClassCommand(
            classId: (string) $class->id,
            capacity: 35,
        );

        $handler($command);

        $updatedClass = $this->classRepository->get($class->id);
        self::assertSame(35, $updatedClass->capacity);
    }

    #[Test]
    public function itUpdatesClassDescription(): void
    {
        $class = $this->createAndSaveClass();
        $handler = new UpdateClassHandler($this->classRepository, $this->clock);

        $command = new UpdateClassCommand(
            classId: (string) $class->id,
            description: 'Classe option musique',
        );

        $handler($command);

        $updatedClass = $this->classRepository->get($class->id);
        self::assertSame('Classe option musique', $updatedClass->description);
    }

    #[Test]
    public function itUpdatesMultipleFields(): void
    {
        $class = $this->createAndSaveClass();
        $handler = new UpdateClassHandler($this->classRepository, $this->clock);

        $command = new UpdateClassCommand(
            classId: (string) $class->id,
            name: '5ème C',
            level: SchoolLevel::CINQUIEME->value,
            capacity: 28,
            description: 'Section européenne',
        );

        $handler($command);

        $updatedClass = $this->classRepository->get($class->id);
        self::assertSame('5ème C', (string) $updatedClass->name);
        self::assertSame(SchoolLevel::CINQUIEME, $updatedClass->level);
        self::assertSame(28, $updatedClass->capacity);
        self::assertSame('Section européenne', $updatedClass->description);
    }

    #[Test]
    public function itThrowsExceptionWhenRenamingToExistingName(): void
    {
        // Create first class
        $class1 = $this->createAndSaveClass();

        // Create second class with different name
        $class2 = 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 B'),
            level: SchoolLevel::SIXIEME,
            capacity: 30,
            createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
        );
        $this->classRepository->save($class2);

        $handler = new UpdateClassHandler($this->classRepository, $this->clock);

        // Try to rename class2 to class1's name
        $command = new UpdateClassCommand(
            classId: (string) $class2->id,
            name: '6ème A',
        );

        $this->expectException(ClasseDejaExistanteException::class);
        $this->expectExceptionMessage('Une classe avec le nom "6ème A" existe déjà pour cette année scolaire.');

        $handler($command);
    }

    #[Test]
    public function itAllowsRenamingToSameName(): void
    {
        $class = $this->createAndSaveClass();
        $handler = new UpdateClassHandler($this->classRepository, $this->clock);

        // Renaming to the same name should work
        $command = new UpdateClassCommand(
            classId: (string) $class->id,
            name: '6ème A',
        );

        $handler($command);

        $updatedClass = $this->classRepository->get($class->id);
        self::assertSame('6ème A', (string) $updatedClass->name);
    }

    private function createAndSaveClass(): SchoolClass
    {
        $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('6ème A'),
            level: SchoolLevel::SIXIEME,
            capacity: 30,
            createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
        );

        $this->classRepository->save($class);

        return $class;
    }
}
