<?php

declare(strict_types=1);

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

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\CreateHomework\CreateHomeworkCommand;
use App\Scolarite\Application\Command\CreateHomework\CreateHomeworkHandler;
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\HtmlSanitizer;
use App\Scolarite\Application\Port\RuleWarning;
use App\Scolarite\Domain\Exception\DateEcheanceInvalideException;
use App\Scolarite\Domain\Exception\EnseignantNonAffecteException;
use App\Scolarite\Domain\Exception\ReglesDevoirsNonRespecteesException;
use App\Scolarite\Domain\Model\Homework\HomeworkId;
use App\Scolarite\Domain\Model\Homework\HomeworkStatus;
use App\Scolarite\Domain\Service\DueDateValidator;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class CreateHomeworkHandlerTest 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 Clock $clock;

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

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

        $homework = $handler($command);

        self::assertNotEmpty((string) $homework->id);
        self::assertSame(HomeworkStatus::PUBLISHED, $homework->status);
        self::assertSame('Exercices chapitre 5', $homework->title);
    }

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

        $created = $handler($command);

        $homework = $this->homeworkRepository->get(
            HomeworkId::fromString((string) $created->id),
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertSame('Exercices chapitre 5', $homework->title);
    }

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

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

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

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

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

        $handler($this->createCommand(dueDate: '2026-03-11'));
    }

    #[Test]
    public function itAllowsNullDescription(): void
    {
        $handler = $this->createHandler(affecte: true);
        $command = $this->createCommand(description: null);

        $homework = $handler($command);

        self::assertNull($homework->description);
    }

    #[Test]
    public function itSanitizesHtmlDescription(): void
    {
        $sanitizer = new class implements HtmlSanitizer {
            public function sanitize(string $html): string
            {
                return strip_tags($html, '<p><strong><em><ul><ol><li><a>');
            }
        };

        $handler = $this->createHandlerWithSanitizer(affecte: true, htmlSanitizer: $sanitizer);
        $command = $this->createCommand(description: '<p>Texte <strong>gras</strong></p><script>alert("xss")</script>');

        $homework = $handler($command);

        self::assertSame('<p>Texte <strong>gras</strong></p>alert("xss")', $homework->description);
    }

    #[Test]
    public function itDoesNotSanitizeNullDescription(): void
    {
        $sanitizer = new class implements HtmlSanitizer {
            public bool $called = false;

            public function sanitize(string $html): string
            {
                $this->called = true;

                return $html;
            }
        };

        $handler = $this->createHandlerWithSanitizer(affecte: true, htmlSanitizer: $sanitizer);
        $command = $this->createCommand(description: null);

        $handler($command);

        self::assertFalse($sanitizer->called);
    }

    #[Test]
    public function itThrowsWhenSoftRulesViolatedAndNotAcknowledged(): void
    {
        $warning = new RuleWarning(
            ruleType: 'minimum_delay',
            message: 'Le devoir doit être créé au moins 3 jours avant.',
            params: ['days' => 3],
        );
        $rulesResult = new HomeworkRulesCheckResult(warnings: [$warning], bloquant: false);
        $handler = $this->createHandler(affecte: true, rulesResult: $rulesResult);

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

        $handler($this->createCommand(acknowledgeWarning: false));
    }

    #[Test]
    public function itThrowsWhenHardRulesViolatedAndNotAcknowledged(): void
    {
        $warning = new RuleWarning(
            ruleType: 'minimum_delay',
            message: 'Le devoir doit être créé au moins 3 jours avant.',
            params: ['days' => 3],
        );
        $rulesResult = new HomeworkRulesCheckResult(warnings: [$warning], bloquant: true);
        $handler = $this->createHandler(affecte: true, rulesResult: $rulesResult);

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

        $handler($this->createCommand(acknowledgeWarning: false));
    }

    #[Test]
    public function itThrowsWhenHardRulesViolatedEvenWhenAcknowledged(): void
    {
        $warning = new RuleWarning(
            ruleType: 'minimum_delay',
            message: 'Le devoir doit être créé au moins 3 jours avant.',
            params: ['days' => 3],
        );
        $rulesResult = new HomeworkRulesCheckResult(warnings: [$warning], bloquant: true);
        $handler = $this->createHandler(affecte: true, rulesResult: $rulesResult);

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

        $handler($this->createCommand(acknowledgeWarning: true));
    }

    #[Test]
    public function hardBlockingExceptionCarriesSuggestedDatesAndBloquantFlag(): void
    {
        $warning = new RuleWarning(
            ruleType: 'minimum_delay',
            message: 'Le devoir doit être créé au moins 7 jours avant.',
            params: ['days' => 7],
        );
        $suggestedDates = ['2026-03-25', '2026-03-26', '2026-03-27'];
        $rulesResult = new HomeworkRulesCheckResult(
            warnings: [$warning],
            bloquant: true,
            suggestedDates: $suggestedDates,
        );
        $handler = $this->createHandler(affecte: true, rulesResult: $rulesResult);

        try {
            $handler($this->createCommand(acknowledgeWarning: false));
            self::fail('Expected ReglesDevoirsNonRespecteesException');
        } catch (ReglesDevoirsNonRespecteesException $e) {
            self::assertTrue($e->bloquant);
            self::assertSame($suggestedDates, $e->suggestedDates);
            self::assertCount(1, $e->warnings);
            self::assertSame('minimum_delay', $e->warnings[0]['ruleType']);
        }
    }

    #[Test]
    public function itCreatesHomeworkWhenSoftRulesViolatedButAcknowledged(): void
    {
        $warning = new RuleWarning(
            ruleType: 'minimum_delay',
            message: 'Le devoir doit être créé au moins 3 jours avant.',
            params: ['days' => 3],
        );
        $rulesResult = new HomeworkRulesCheckResult(warnings: [$warning], bloquant: false);
        $handler = $this->createHandler(affecte: true, rulesResult: $rulesResult);

        $homework = $handler($this->createCommand(acknowledgeWarning: true));

        self::assertNotEmpty((string) $homework->id);
        self::assertSame(HomeworkStatus::PUBLISHED, $homework->status);
    }

    #[Test]
    public function itRecordsRuleOverrideWhenAcknowledged(): void
    {
        $warning = new RuleWarning(
            ruleType: 'minimum_delay',
            message: 'Le devoir doit être créé au moins 3 jours avant.',
            params: ['days' => 3],
        );
        $rulesResult = new HomeworkRulesCheckResult(warnings: [$warning], bloquant: false);
        $handler = $this->createHandler(affecte: true, rulesResult: $rulesResult);

        $homework = $handler($this->createCommand(acknowledgeWarning: true));

        self::assertNotNull($homework->ruleOverride);
        self::assertArrayHasKey('warnings', $homework->ruleOverride);
        self::assertArrayHasKey('acknowledgedAt', $homework->ruleOverride);
        self::assertSame(['minimum_delay'], $homework->ruleOverride['warnings']);
        self::assertSame('2026-03-12T10:00:00+00:00', $homework->ruleOverride['acknowledgedAt']);
    }

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

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

        self::assertNull($homework->ruleOverride);
    }

    private function createHandler(bool $affecte, ?HomeworkRulesCheckResult $rulesResult = null): CreateHomeworkHandler
    {
        $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 ?? HomeworkRulesCheckResult::ok()) implements HomeworkRulesChecker {
            public function __construct(private readonly HomeworkRulesCheckResult $result)
            {
            }

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

        $htmlSanitizer = new class implements HtmlSanitizer {
            public function sanitize(string $html): string
            {
                return $html;
            }
        };

        return new CreateHomeworkHandler(
            $this->homeworkRepository,
            $affectationChecker,
            $calendarProvider,
            new DueDateValidator(),
            $rulesChecker,
            $htmlSanitizer,
            $this->clock,
        );
    }

    private function createHandlerWithSanitizer(bool $affecte, HtmlSanitizer $htmlSanitizer, ?HomeworkRulesCheckResult $rulesResult = null): CreateHomeworkHandler
    {
        $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 ?? HomeworkRulesCheckResult::ok()) implements HomeworkRulesChecker {
            public function __construct(private readonly HomeworkRulesCheckResult $result)
            {
            }

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

        return new CreateHomeworkHandler(
            $this->homeworkRepository,
            $affectationChecker,
            $calendarProvider,
            new DueDateValidator(),
            $rulesChecker,
            $htmlSanitizer,
            $this->clock,
        );
    }

    private function createCommand(
        ?string $dueDate = null,
        ?string $description = 'Faire les exercices 1 à 10',
        bool $acknowledgeWarning = false,
    ): CreateHomeworkCommand {
        return new CreateHomeworkCommand(
            tenantId: self::TENANT_ID,
            classId: self::CLASS_ID,
            subjectId: self::SUBJECT_ID,
            teacherId: self::TEACHER_ID,
            title: 'Exercices chapitre 5',
            description: $description,
            dueDate: $dueDate ?? '2026-04-15',
            acknowledgeWarning: $acknowledgeWarning,
        );
    }
}
