<?php

declare(strict_types=1);

namespace App\Tests\Unit\Scolarite\Domain\Model\Homework;

use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Event\ExceptionDevoirDemandee;
use App\Scolarite\Domain\Exception\JustificationTropCourteException;
use App\Scolarite\Domain\Model\Homework\HomeworkId;
use App\Scolarite\Domain\Model\Homework\HomeworkRuleException;
use App\Scolarite\Domain\Model\Homework\HomeworkRuleExceptionId;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

use function str_repeat;

final class HomeworkRuleExceptionTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string HOMEWORK_ID = '550e8400-e29b-41d4-a716-446655440050';
    private const string TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';

    #[Test]
    public function demanderCreatesExceptionWithAllFields(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $homeworkId = HomeworkId::fromString(self::HOMEWORK_ID);
        $teacherId = UserId::fromString(self::TEACHER_ID);
        $now = new DateTimeImmutable('2026-03-19 10:00:00');

        $exception = HomeworkRuleException::demander(
            tenantId: $tenantId,
            homeworkId: $homeworkId,
            ruleTypes: ['minimum_delay'],
            justification: 'Sortie scolaire prévue, les élèves doivent préparer leur dossier.',
            createdBy: $teacherId,
            now: $now,
        );

        self::assertNotEmpty((string) $exception->id);
        self::assertTrue($exception->tenantId->equals($tenantId));
        self::assertTrue($exception->homeworkId->equals($homeworkId));
        self::assertSame('minimum_delay', $exception->ruleType);
        self::assertSame(['minimum_delay'], $exception->ruleTypes());
        self::assertSame('Sortie scolaire prévue, les élèves doivent préparer leur dossier.', $exception->justification);
        self::assertTrue($exception->createdBy->equals($teacherId));
        self::assertEquals($now, $exception->createdAt);
    }

    #[Test]
    public function demanderStoresMultipleRuleTypesCommaSeparated(): void
    {
        $exception = HomeworkRuleException::demander(
            tenantId: TenantId::fromString(self::TENANT_ID),
            homeworkId: HomeworkId::fromString(self::HOMEWORK_ID),
            ruleTypes: ['minimum_delay', 'no_monday_after'],
            justification: 'Sortie scolaire et lundi exceptionnel.',
            createdBy: UserId::fromString(self::TEACHER_ID),
            now: new DateTimeImmutable('2026-03-19 10:00:00'),
        );

        self::assertSame('minimum_delay,no_monday_after', $exception->ruleType);
        self::assertSame(['minimum_delay', 'no_monday_after'], $exception->ruleTypes());
    }

    #[Test]
    public function demanderRecordsExceptionDevoirDemandeeEvent(): void
    {
        $exception = $this->createException();

        $events = $exception->pullDomainEvents();

        self::assertCount(1, $events);
        self::assertInstanceOf(ExceptionDevoirDemandee::class, $events[0]);
        self::assertSame($exception->id, $events[0]->exceptionId);
        self::assertSame(['minimum_delay'], $events[0]->ruleTypes);
    }

    #[Test]
    public function demanderThrowsWhenJustificationTooShort(): void
    {
        $this->expectException(JustificationTropCourteException::class);
        $this->expectExceptionMessage('au moins 20 caractères');

        HomeworkRuleException::demander(
            tenantId: TenantId::fromString(self::TENANT_ID),
            homeworkId: HomeworkId::fromString(self::HOMEWORK_ID),
            ruleTypes: ['minimum_delay'],
            justification: 'Trop court',
            createdBy: UserId::fromString(self::TEACHER_ID),
            now: new DateTimeImmutable('2026-03-19 10:00:00'),
        );
    }

    #[Test]
    public function demanderAcceptsExactlyMinimumLength(): void
    {
        $justification = str_repeat('x', 20);

        $exception = HomeworkRuleException::demander(
            tenantId: TenantId::fromString(self::TENANT_ID),
            homeworkId: HomeworkId::fromString(self::HOMEWORK_ID),
            ruleTypes: ['minimum_delay'],
            justification: $justification,
            createdBy: UserId::fromString(self::TEACHER_ID),
            now: new DateTimeImmutable('2026-03-19 10:00:00'),
        );

        self::assertSame($justification, $exception->justification);
    }

    #[Test]
    public function demanderRejectsNineteenCharacters(): void
    {
        $this->expectException(JustificationTropCourteException::class);

        HomeworkRuleException::demander(
            tenantId: TenantId::fromString(self::TENANT_ID),
            homeworkId: HomeworkId::fromString(self::HOMEWORK_ID),
            ruleTypes: ['minimum_delay'],
            justification: str_repeat('x', 19),
            createdBy: UserId::fromString(self::TEACHER_ID),
            now: new DateTimeImmutable('2026-03-19 10:00:00'),
        );
    }

    #[Test]
    public function reconstituteRestoresAllPropertiesWithoutEvents(): void
    {
        $id = HomeworkRuleExceptionId::generate();
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $homeworkId = HomeworkId::fromString(self::HOMEWORK_ID);
        $teacherId = UserId::fromString(self::TEACHER_ID);
        $createdAt = new DateTimeImmutable('2026-03-19 10:00:00');

        $exception = HomeworkRuleException::reconstitute(
            id: $id,
            tenantId: $tenantId,
            homeworkId: $homeworkId,
            ruleType: 'minimum_delay',
            justification: 'Justification suffisamment longue pour passer.',
            createdBy: $teacherId,
            createdAt: $createdAt,
        );

        self::assertTrue($exception->id->equals($id));
        self::assertTrue($exception->tenantId->equals($tenantId));
        self::assertTrue($exception->homeworkId->equals($homeworkId));
        self::assertSame('minimum_delay', $exception->ruleType);
        self::assertSame('Justification suffisamment longue pour passer.', $exception->justification);
        self::assertTrue($exception->createdBy->equals($teacherId));
        self::assertEquals($createdAt, $exception->createdAt);
        self::assertEmpty($exception->pullDomainEvents());
    }

    private function createException(): HomeworkRuleException
    {
        return HomeworkRuleException::demander(
            tenantId: TenantId::fromString(self::TENANT_ID),
            homeworkId: HomeworkId::fromString(self::HOMEWORK_ID),
            ruleTypes: ['minimum_delay'],
            justification: 'Sortie scolaire prévue, les élèves doivent préparer leur dossier.',
            createdBy: UserId::fromString(self::TEACHER_ID),
            now: new DateTimeImmutable('2026-03-19 10:00:00'),
        );
    }
}
