<?php

declare(strict_types=1);

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

use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Application\Command\SaveDraftSubmission\SaveDraftSubmissionCommand;
use App\Scolarite\Application\Command\SaveDraftSubmission\SaveDraftSubmissionHandler;
use App\Scolarite\Application\Port\HtmlSanitizer;
use App\Scolarite\Application\Port\StudentClassReader;
use App\Scolarite\Domain\Exception\EleveNonAffecteAuDevoirException;
use App\Scolarite\Domain\Exception\RenduDejaSoumisException;
use App\Scolarite\Domain\Model\Homework\Homework;
use App\Scolarite\Domain\Model\Homework\HomeworkId;
use App\Scolarite\Domain\Model\HomeworkSubmission\SubmissionStatus;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkRepository;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkSubmissionRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class SaveDraftSubmissionHandlerTest 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 const string STUDENT_ID = '550e8400-e29b-41d4-a716-446655440060';

    private InMemoryHomeworkRepository $homeworkRepository;
    private InMemoryHomeworkSubmissionRepository $submissionRepository;
    private Clock $clock;
    private Homework $homework;

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

        $this->homework = Homework::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            classId: ClassId::fromString(self::CLASS_ID),
            subjectId: \App\Administration\Domain\Model\Subject\SubjectId::fromString(self::SUBJECT_ID),
            teacherId: UserId::fromString(self::TEACHER_ID),
            title: 'Exercices chapitre 5',
            description: null,
            dueDate: new DateTimeImmutable('2026-04-15'),
            now: new DateTimeImmutable('2026-03-12 10:00:00'),
        );
        $this->homeworkRepository->save($this->homework);
    }

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

        $submission = $handler($command);

        self::assertSame(SubmissionStatus::DRAFT, $submission->status);
        self::assertSame('<p>Ma réponse</p>', $submission->responseHtml);
        self::assertNull($submission->submittedAt);
    }

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

        $created = $handler($command);

        $found = $this->submissionRepository->findByHomeworkAndStudent(
            HomeworkId::fromString((string) $this->homework->id),
            UserId::fromString(self::STUDENT_ID),
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertNotNull($found);
        self::assertTrue($found->id->equals($created->id));
    }

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

        $handler($this->createCommand(responseHtml: '<p>Première version</p>'));
        $updated = $handler($this->createCommand(responseHtml: '<p>Version modifiée</p>'));

        self::assertSame('<p>Version modifiée</p>', $updated->responseHtml);
        self::assertSame(SubmissionStatus::DRAFT, $updated->status);
    }

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

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

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

    #[Test]
    public function itThrowsWhenStudentInDifferentClass(): void
    {
        $handler = $this->createHandler(studentClassId: '550e8400-e29b-41d4-a716-446655440099');

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

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

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

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

        // Soumettre le rendu
        $submission = $this->submissionRepository->findByHomeworkAndStudent(
            HomeworkId::fromString((string) $this->homework->id),
            UserId::fromString(self::STUDENT_ID),
            TenantId::fromString(self::TENANT_ID),
        );
        self::assertNotNull($submission);
        $submission->soumettre(
            dueDate: new DateTimeImmutable('2026-04-15'),
            now: new DateTimeImmutable('2026-03-24 11:00:00'),
        );
        $this->submissionRepository->save($submission);

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

        $handler($this->createCommand(responseHtml: '<p>Tentative de modification</p>'));
    }

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

        $handler = $this->createHandler(htmlSanitizer: $sanitizer);
        $command = $this->createCommand(responseHtml: '<p>Texte</p><script>alert("xss")</script>');

        $submission = $handler($command);

        self::assertSame('<p>Texte</p>alert("xss")', $submission->responseHtml);
    }

    #[Test]
    public function itAllowsNullResponse(): void
    {
        $handler = $this->createHandler();
        $command = $this->createCommand(responseHtml: null);

        $submission = $handler($command);

        self::assertNull($submission->responseHtml);
    }

    private function createHandler(
        ?string $studentClassId = self::CLASS_ID,
        ?HtmlSanitizer $htmlSanitizer = null,
    ): SaveDraftSubmissionHandler {
        $studentClassReader = new class($studentClassId) implements StudentClassReader {
            public function __construct(private readonly ?string $classId)
            {
            }

            public function currentClassId(string $studentId, TenantId $tenantId): ?string
            {
                return $this->classId;
            }
        };

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

        return new SaveDraftSubmissionHandler(
            $this->homeworkRepository,
            $this->submissionRepository,
            $studentClassReader,
            $sanitizer,
            $this->clock,
        );
    }

    private function createCommand(?string $responseHtml = '<p>Ma réponse</p>'): SaveDraftSubmissionCommand
    {
        return new SaveDraftSubmissionCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $this->homework->id,
            studentId: self::STUDENT_ID,
            responseHtml: $responseHtml,
        );
    }
}
