<?php

declare(strict_types=1);

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

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\UpdateHomework\UpdateHomeworkCommand;
use App\Scolarite\Application\Command\UpdateHomework\UpdateHomeworkHandler;
use App\Scolarite\Application\Port\CurrentCalendarProvider;
use App\Scolarite\Application\Port\HtmlSanitizer;
use App\Scolarite\Domain\Exception\DateEcheanceInvalideException;
use App\Scolarite\Domain\Exception\DevoirDejaSupprimeException;
use App\Scolarite\Domain\Exception\HomeworkNotFoundException;
use App\Scolarite\Domain\Exception\NonProprietaireDuDevoirException;
use App\Scolarite\Domain\Model\Homework\Homework;
use App\Scolarite\Domain\Model\Homework\HomeworkId;
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 UpdateHomeworkHandlerTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';

    private InMemoryHomeworkRepository $homeworkRepository;
    private Clock $clock;
    private HomeworkId $existingHomeworkId;

    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');
            }
        };

        $this->seedHomework();
    }

    #[Test]
    public function itUpdatesHomeworkSuccessfully(): void
    {
        $handler = $this->createHandler();
        $command = new UpdateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $this->existingHomeworkId,
            teacherId: '550e8400-e29b-41d4-a716-446655440010',
            title: 'Titre modifié',
            description: 'Nouvelle description',
            dueDate: '2026-04-20',
        );

        $homework = $handler($command);

        self::assertSame('Titre modifié', $homework->title);
        self::assertSame('Nouvelle description', $homework->description);
    }

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

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

        $handler(new UpdateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) HomeworkId::generate(),
            teacherId: '550e8400-e29b-41d4-a716-446655440010',
            title: 'Test',
            description: null,
            dueDate: '2026-04-20',
        ));
    }

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

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

        $handler(new UpdateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $this->existingHomeworkId,
            teacherId: '550e8400-e29b-41d4-a716-446655440010',
            title: 'Test',
            description: null,
            dueDate: '2026-03-11',
        ));
    }

    #[Test]
    public function itThrowsWhenHomeworkDeleted(): void
    {
        $homework = $this->homeworkRepository->get($this->existingHomeworkId, TenantId::fromString(self::TENANT_ID));
        $homework->supprimer(new DateTimeImmutable('2026-03-12'));
        $this->homeworkRepository->save($homework);

        $handler = $this->createHandler();

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

        $handler(new UpdateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $this->existingHomeworkId,
            teacherId: '550e8400-e29b-41d4-a716-446655440010',
            title: 'Test',
            description: null,
            dueDate: '2026-04-20',
        ));
    }

    #[Test]
    public function itUpdatesTitleOnly(): void
    {
        $handler = $this->createHandler();
        $command = new UpdateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $this->existingHomeworkId,
            teacherId: '550e8400-e29b-41d4-a716-446655440010',
            title: 'Nouveau titre',
            description: null,
            dueDate: '2026-04-15',
        );

        $homework = $handler($command);

        self::assertSame('Nouveau titre', $homework->title);
        self::assertNull($homework->description);
        self::assertSame('2026-04-15', $homework->dueDate->format('Y-m-d'));
    }

    #[Test]
    public function itUpdatesDescriptionFromNullToText(): void
    {
        $homeworkWithoutDescription = Homework::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            classId: ClassId::fromString('550e8400-e29b-41d4-a716-446655440020'),
            subjectId: SubjectId::fromString('550e8400-e29b-41d4-a716-446655440030'),
            teacherId: UserId::fromString('550e8400-e29b-41d4-a716-446655440010'),
            title: 'Exercices sans description',
            description: null,
            dueDate: new DateTimeImmutable('2026-04-15'),
            now: new DateTimeImmutable('2026-03-10 10:00:00'),
        );
        $this->homeworkRepository->save($homeworkWithoutDescription);

        $handler = $this->createHandler();
        $command = new UpdateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $homeworkWithoutDescription->id,
            teacherId: '550e8400-e29b-41d4-a716-446655440010',
            title: 'Exercices sans description',
            description: 'Nouvelle description ajoutée',
            dueDate: '2026-04-15',
        );

        $homework = $handler($command);

        self::assertSame('Nouvelle description ajoutée', $homework->description);
        self::assertSame('Exercices sans description', $homework->title);
    }

    #[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($sanitizer);
        $command = new UpdateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $this->existingHomeworkId,
            teacherId: '550e8400-e29b-41d4-a716-446655440010',
            title: 'Test sanitize',
            description: '<p>Texte</p><script>alert("xss")</script>',
            dueDate: '2026-04-20',
        );

        $homework = $handler($command);

        self::assertSame('<p>Texte</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($sanitizer);
        $command = new UpdateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $this->existingHomeworkId,
            teacherId: '550e8400-e29b-41d4-a716-446655440010',
            title: 'Test null desc',
            description: null,
            dueDate: '2026-04-20',
        );

        $handler($command);

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

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

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

        $handler(new UpdateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $this->existingHomeworkId,
            teacherId: '550e8400-e29b-41d4-a716-446655440099',
            title: 'Test',
            description: null,
            dueDate: '2026-04-20',
        ));
    }

    private function seedHomework(): void
    {
        $homework = Homework::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            classId: ClassId::fromString('550e8400-e29b-41d4-a716-446655440020'),
            subjectId: SubjectId::fromString('550e8400-e29b-41d4-a716-446655440030'),
            teacherId: UserId::fromString('550e8400-e29b-41d4-a716-446655440010'),
            title: 'Exercices',
            description: 'Description',
            dueDate: new DateTimeImmutable('2026-04-15'),
            now: new DateTimeImmutable('2026-03-10 10:00:00'),
        );

        $this->existingHomeworkId = $homework->id;
        $this->homeworkRepository->save($homework);
    }

    private function createHandlerWithSanitizer(HtmlSanitizer $htmlSanitizer): UpdateHomeworkHandler
    {
        $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: [],
                );
            }
        };

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

    private function createHandler(): UpdateHomeworkHandler
    {
        $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: [],
                );
            }
        };

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

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