<?php

declare(strict_types=1);

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

use App\Administration\Application\Command\CreateSubject\CreateSubjectCommand;
use App\Administration\Application\Command\CreateSubject\CreateSubjectHandler;
use App\Administration\Domain\Exception\SubjectDejaExistanteException;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\Subject\SubjectStatus;
use App\Administration\Infrastructure\Persistence\InMemory\InMemorySubjectRepository;
use App\Shared\Domain\Clock;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

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

    private InMemorySubjectRepository $subjectRepository;
    private Clock $clock;

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

    #[Test]
    public function itCreatesSubjectSuccessfully(): void
    {
        $handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);
        $command = new CreateSubjectCommand(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            name: 'Mathématiques',
            code: 'MATH',
            color: '#3B82F6',
        );

        $subject = $handler($command);

        self::assertNotEmpty((string) $subject->id);
        self::assertSame('Mathématiques', (string) $subject->name);
        self::assertSame('MATH', (string) $subject->code);
        self::assertNotNull($subject->color);
        self::assertSame('#3B82F6', (string) $subject->color);
        self::assertSame(SubjectStatus::ACTIVE, $subject->status);
    }

    #[Test]
    public function itCreatesSubjectWithNullColor(): void
    {
        $handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);
        $command = new CreateSubjectCommand(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            name: 'Arts plastiques',
            code: 'ART',
            color: null,
        );

        $subject = $handler($command);

        self::assertNotEmpty((string) $subject->id);
        self::assertSame('Arts plastiques', (string) $subject->name);
        self::assertSame('ART', (string) $subject->code);
        self::assertNull($subject->color);
    }

    #[Test]
    public function itPersistsSubjectInRepository(): void
    {
        $handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);
        $command = new CreateSubjectCommand(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            name: 'Mathématiques',
            code: 'MATH',
            color: '#3B82F6',
        );

        $createdSubject = $handler($command);

        $subject = $this->subjectRepository->get(
            SubjectId::fromString((string) $createdSubject->id),
        );

        self::assertSame('Mathématiques', (string) $subject->name);
        self::assertSame('MATH', (string) $subject->code);
        self::assertSame(SubjectStatus::ACTIVE, $subject->status);
    }

    #[Test]
    public function itThrowsExceptionWhenSubjectCodeAlreadyExists(): void
    {
        $handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);
        $command = new CreateSubjectCommand(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            name: 'Mathématiques',
            code: 'MATH',
            color: '#3B82F6',
        );

        // First creation should succeed
        $handler($command);

        // Second creation with same code should throw
        $this->expectException(SubjectDejaExistanteException::class);
        $this->expectExceptionMessage('Une matière avec le code "MATH" existe déjà dans cet établissement.');

        $commandDuplicate = new CreateSubjectCommand(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            name: 'Maths avancées',  // Different name, same code
            code: 'MATH',
            color: '#EF4444',
        );
        $handler($commandDuplicate);
    }

    #[Test]
    public function itAllowsSameCodeInDifferentTenant(): void
    {
        $handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);

        // Create in tenant 1
        $command1 = new CreateSubjectCommand(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            name: 'Mathématiques',
            code: 'MATH',
            color: '#3B82F6',
        );
        $subject1 = $handler($command1);

        // Create same code in tenant 2 should succeed
        $command2 = new CreateSubjectCommand(
            tenantId: '550e8400-e29b-41d4-a716-446655440099',
            schoolId: self::SCHOOL_ID,
            name: 'Mathématiques',
            code: 'MATH',
            color: '#3B82F6',
        );
        $subject2 = $handler($command2);

        self::assertFalse($subject1->id->equals($subject2->id));
        self::assertSame('MATH', (string) $subject1->code);
        self::assertSame('MATH', (string) $subject2->code);
    }

    #[Test]
    public function itAllowsSameCodeInDifferentSchool(): void
    {
        $handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);

        // Create in school 1
        $command1 = new CreateSubjectCommand(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            name: 'Mathématiques',
            code: 'MATH',
            color: '#3B82F6',
        );
        $subject1 = $handler($command1);

        // Create same code in school 2 should succeed
        $command2 = new CreateSubjectCommand(
            tenantId: self::TENANT_ID,
            schoolId: '550e8400-e29b-41d4-a716-446655440099',
            name: 'Mathématiques',
            code: 'MATH',
            color: '#3B82F6',
        );
        $subject2 = $handler($command2);

        self::assertFalse($subject1->id->equals($subject2->id));
        self::assertSame('MATH', (string) $subject1->code);
        self::assertSame('MATH', (string) $subject2->code);
    }

    #[Test]
    public function itNormalizesCodeToUppercase(): void
    {
        $handler = new CreateSubjectHandler($this->subjectRepository, $this->clock);
        $command = new CreateSubjectCommand(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            name: 'Mathématiques',
            code: 'math',
            color: '#3B82F6',
        );

        $subject = $handler($command);

        self::assertSame('MATH', (string) $subject->code);
    }
}
