<?php

declare(strict_types=1);

namespace App\Tests\Unit\Scolarite\Application\Command\CreateHomeworkWithException;

use App\Administration\Domain\Model\SchoolCalendar\SchoolCalendar;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Application\Command\CreateHomeworkWithException\CreateHomeworkWithExceptionCommand;
use App\Scolarite\Application\Command\CreateHomeworkWithException\CreateHomeworkWithExceptionHandler;
use App\Scolarite\Application\Port\CurrentCalendarProvider;
use App\Scolarite\Application\Port\EnseignantAffectationChecker;
use App\Scolarite\Application\Port\HomeworkRulesChecker;
use App\Scolarite\Application\Port\HomeworkRulesCheckResult;
use App\Scolarite\Application\Port\RuleWarning;
use App\Scolarite\Domain\Event\ExceptionDevoirDemandee;
use App\Scolarite\Domain\Exception\EnseignantNonAffecteException;
use App\Scolarite\Domain\Exception\ExceptionNonNecessaireException;
use App\Scolarite\Domain\Exception\JustificationTropCourteException;
use App\Scolarite\Domain\Model\Homework\HomeworkStatus;
use App\Scolarite\Domain\Service\DueDateValidator;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkRepository;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkRuleExceptionRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class CreateHomeworkWithExceptionHandlerTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string CLASS_ID = '550e8400-e29b-41d4-a716-446655440020';
    private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440030';
    private const string TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';

    private InMemoryHomeworkRepository $homeworkRepository;
    private InMemoryHomeworkRuleExceptionRepository $exceptionRepository;
    private Clock $clock;

    protected function setUp(): void
    {
        $this->homeworkRepository = new InMemoryHomeworkRepository();
        $this->exceptionRepository = new InMemoryHomeworkRuleExceptionRepository();
        $this->clock = new class implements Clock {
            public function now(): DateTimeImmutable
            {
                return new DateTimeImmutable('2026-03-19 10:00:00');
            }
        };
    }

    #[Test]
    public function itCreatesHomeworkAndExceptionWhenRulesAreBlocking(): void
    {
        $handler = $this->createHandler(affecte: true, rulesResult: $this->blockingResult());
        $command = $this->createCommand();

        $result = $handler($command);

        self::assertSame(HomeworkStatus::PUBLISHED, $result['homework']->status);
        self::assertSame('Exercices chapitre 5', $result['homework']->title);
        self::assertSame('minimum_delay', $result['exception']->ruleType);
        self::assertSame('Sortie scolaire prévue, devoir urgent pour les élèves.', $result['exception']->justification);
    }

    #[Test]
    public function itPersistsBothInRepositories(): void
    {
        $handler = $this->createHandler(affecte: true, rulesResult: $this->blockingResult());
        $command = $this->createCommand();

        $result = $handler($command);

        $tenantId = TenantId::fromString(self::TENANT_ID);
        $savedHomework = $this->homeworkRepository->get($result['homework']->id, $tenantId);
        $savedExceptions = $this->exceptionRepository->findByHomework($result['homework']->id, $tenantId);

        self::assertSame('Exercices chapitre 5', $savedHomework->title);
        self::assertCount(1, $savedExceptions);
        self::assertTrue($savedExceptions[0]->homeworkId->equals($result['homework']->id));
    }

    #[Test]
    public function itUsesServerSideRuleTypesNotClientProvided(): void
    {
        $handler = $this->createHandler(affecte: true, rulesResult: $this->blockingResult());
        // Client sends ['fake_rule'] but server should use the actual rule types
        $command = $this->createCommand(ruleTypes: ['fake_rule']);

        $result = $handler($command);

        // ruleType comes from server-side check, not from client
        self::assertSame('minimum_delay', $result['exception']->ruleType);
    }

    #[Test]
    public function itRecordsDomainEvents(): void
    {
        $handler = $this->createHandler(affecte: true, rulesResult: $this->blockingResult());
        $result = $handler($this->createCommand());

        $exceptionEvents = $result['exception']->pullDomainEvents();
        self::assertCount(1, $exceptionEvents);
        self::assertInstanceOf(ExceptionDevoirDemandee::class, $exceptionEvents[0]);
        self::assertSame(['minimum_delay'], $exceptionEvents[0]->ruleTypes);
    }

    #[Test]
    public function itThrowsWhenRulesAreNotBlocking(): void
    {
        $handler = $this->createHandler(affecte: true, rulesResult: HomeworkRulesCheckResult::ok());

        $this->expectException(ExceptionNonNecessaireException::class);

        $handler($this->createCommand());
    }

    #[Test]
    public function itThrowsWhenRulesAreSoftWarningOnly(): void
    {
        $softResult = new HomeworkRulesCheckResult(
            warnings: [new RuleWarning('minimum_delay', 'Délai trop court', ['days' => 3])],
            bloquant: false,
        );
        $handler = $this->createHandler(affecte: true, rulesResult: $softResult);

        $this->expectException(ExceptionNonNecessaireException::class);

        $handler($this->createCommand());
    }

    #[Test]
    public function itThrowsWhenTeacherNotAffected(): void
    {
        $handler = $this->createHandler(affecte: false, rulesResult: $this->blockingResult());

        $this->expectException(EnseignantNonAffecteException::class);

        $handler($this->createCommand());
    }

    #[Test]
    public function itThrowsWhenJustificationTooShort(): void
    {
        $handler = $this->createHandler(affecte: true, rulesResult: $this->blockingResult());

        $this->expectException(JustificationTropCourteException::class);

        $handler($this->createCommand(justification: 'Trop court'));
    }

    #[Test]
    public function itStoresMultipleRuleTypesFromServerCheck(): void
    {
        $multiRuleResult = new HomeworkRulesCheckResult(
            warnings: [
                new RuleWarning('minimum_delay', 'Délai trop court', ['days' => 7]),
                new RuleWarning('no_monday_after', 'Pas de lundi', ['day' => 'friday', 'time' => '12:00']),
            ],
            bloquant: true,
        );
        $handler = $this->createHandler(affecte: true, rulesResult: $multiRuleResult);

        $result = $handler($this->createCommand());

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

    private function blockingResult(): HomeworkRulesCheckResult
    {
        return new HomeworkRulesCheckResult(
            warnings: [new RuleWarning('minimum_delay', 'Le devoir doit être créé au moins 7 jours avant.', ['days' => 7])],
            bloquant: true,
            suggestedDates: ['2026-03-30'],
        );
    }

    private function createHandler(bool $affecte, HomeworkRulesCheckResult $rulesResult): CreateHomeworkWithExceptionHandler
    {
        $affectationChecker = new class($affecte) implements EnseignantAffectationChecker {
            public function __construct(private readonly bool $affecte)
            {
            }

            public function estAffecte(UserId $teacherId, ClassId $classId, SubjectId $subjectId, TenantId $tenantId): bool
            {
                return $this->affecte;
            }
        };

        $calendarProvider = new class implements CurrentCalendarProvider {
            public function forCurrentYear(TenantId $tenantId): SchoolCalendar
            {
                return SchoolCalendar::reconstitute(
                    tenantId: $tenantId,
                    academicYearId: AcademicYearId::fromString('550e8400-e29b-41d4-a716-446655440002'),
                    zone: null,
                    entries: [],
                );
            }
        };

        $rulesChecker = new class($rulesResult) implements HomeworkRulesChecker {
            public function __construct(private readonly HomeworkRulesCheckResult $result)
            {
            }

            public function verifier(TenantId $tenantId, DateTimeImmutable $dueDate, DateTimeImmutable $creationDate): HomeworkRulesCheckResult
            {
                return $this->result;
            }
        };

        $connection = $this->createMock(Connection::class);

        return new CreateHomeworkWithExceptionHandler(
            $this->homeworkRepository,
            $this->exceptionRepository,
            $affectationChecker,
            $calendarProvider,
            new DueDateValidator(),
            $rulesChecker,
            $this->clock,
            $connection,
        );
    }

    /**
     * @param string[]|null $ruleTypes
     */
    private function createCommand(
        ?string $justification = null,
        ?array $ruleTypes = null,
    ): CreateHomeworkWithExceptionCommand {
        return new CreateHomeworkWithExceptionCommand(
            tenantId: self::TENANT_ID,
            classId: self::CLASS_ID,
            subjectId: self::SUBJECT_ID,
            teacherId: self::TEACHER_ID,
            title: 'Exercices chapitre 5',
            description: 'Faire les exercices 1 à 10',
            dueDate: '2026-04-15',
            justification: $justification ?? 'Sortie scolaire prévue, devoir urgent pour les élèves.',
            ruleTypes: $ruleTypes ?? ['minimum_delay'],
        );
    }
}
