<?php

declare(strict_types=1);

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

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\UpdateEvaluation\UpdateEvaluationCommand;
use App\Scolarite\Application\Command\UpdateEvaluation\UpdateEvaluationHandler;
use App\Scolarite\Application\Port\EvaluationGradesChecker;
use App\Scolarite\Domain\Exception\BaremeNonModifiableException;
use App\Scolarite\Domain\Exception\EvaluationDejaSupprimeeException;
use App\Scolarite\Domain\Exception\EvaluationNotFoundException;
use App\Scolarite\Domain\Exception\NonProprietaireDeLEvaluationException;
use App\Scolarite\Domain\Model\Evaluation\Coefficient;
use App\Scolarite\Domain\Model\Evaluation\Evaluation;
use App\Scolarite\Domain\Model\Evaluation\EvaluationId;
use App\Scolarite\Domain\Model\Evaluation\GradeScale;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryEvaluationRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class UpdateEvaluationHandlerTest 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 OTHER_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440099';

    private InMemoryEvaluationRepository $evaluationRepository;
    private Clock $clock;

    protected function setUp(): void
    {
        $this->evaluationRepository = new InMemoryEvaluationRepository();
        $this->clock = new class implements Clock {
            public function now(): DateTimeImmutable
            {
                return new DateTimeImmutable('2026-03-13 14:00:00');
            }
        };
    }

    #[Test]
    public function itUpdatesEvaluationSuccessfully(): void
    {
        $evaluation = $this->createAndSaveEvaluation();
        $handler = $this->createHandler(hasGrades: false);

        $command = new UpdateEvaluationCommand(
            tenantId: self::TENANT_ID,
            evaluationId: (string) $evaluation->id,
            teacherId: self::TEACHER_ID,
            title: 'Titre modifié',
            description: 'Nouvelle description',
            evaluationDate: '2026-04-20',
            coefficient: 2.0,
        );

        $updated = $handler($command);

        self::assertSame('Titre modifié', $updated->title);
        self::assertSame('Nouvelle description', $updated->description);
        self::assertSame(2.0, $updated->coefficient->value);
    }

    #[Test]
    public function itAllowsGradeScaleChangeWhenNoGrades(): void
    {
        $evaluation = $this->createAndSaveEvaluation();
        $handler = $this->createHandler(hasGrades: false);

        $command = new UpdateEvaluationCommand(
            tenantId: self::TENANT_ID,
            evaluationId: (string) $evaluation->id,
            teacherId: self::TEACHER_ID,
            title: $evaluation->title,
            description: $evaluation->description,
            evaluationDate: '2026-04-15',
            gradeScale: 10,
        );

        $updated = $handler($command);

        self::assertSame(10, $updated->gradeScale->maxValue);
    }

    #[Test]
    public function itBlocksGradeScaleChangeWhenGradesExist(): void
    {
        $evaluation = $this->createAndSaveEvaluation();
        $handler = $this->createHandler(hasGrades: true);

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

        $handler(new UpdateEvaluationCommand(
            tenantId: self::TENANT_ID,
            evaluationId: (string) $evaluation->id,
            teacherId: self::TEACHER_ID,
            title: $evaluation->title,
            description: $evaluation->description,
            evaluationDate: '2026-04-15',
            gradeScale: 10,
        ));
    }

    #[Test]
    public function itThrowsWhenTeacherIsNotOwner(): void
    {
        $evaluation = $this->createAndSaveEvaluation();
        $handler = $this->createHandler(hasGrades: false);

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

        $handler(new UpdateEvaluationCommand(
            tenantId: self::TENANT_ID,
            evaluationId: (string) $evaluation->id,
            teacherId: self::OTHER_TEACHER_ID,
            title: 'Titre',
            description: null,
            evaluationDate: '2026-04-15',
        ));
    }

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

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

        $handler(new UpdateEvaluationCommand(
            tenantId: self::TENANT_ID,
            evaluationId: '550e8400-e29b-41d4-a716-446655449999',
            teacherId: self::TEACHER_ID,
            title: 'Titre',
            description: null,
            evaluationDate: '2026-04-15',
        ));
    }

    #[Test]
    public function itThrowsWhenEvaluationIsDeleted(): void
    {
        $evaluation = $this->createAndSaveEvaluation();
        $evaluation->supprimer(new DateTimeImmutable('2026-03-13'));
        $this->evaluationRepository->save($evaluation);
        $handler = $this->createHandler(hasGrades: false);

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

        $handler(new UpdateEvaluationCommand(
            tenantId: self::TENANT_ID,
            evaluationId: (string) $evaluation->id,
            teacherId: self::TEACHER_ID,
            title: 'Titre',
            description: null,
            evaluationDate: '2026-04-15',
        ));
    }

    private function createHandler(bool $hasGrades): UpdateEvaluationHandler
    {
        $gradesChecker = new class($hasGrades) implements EvaluationGradesChecker {
            public function __construct(private readonly bool $hasGrades)
            {
            }

            public function hasGrades(EvaluationId $evaluationId, TenantId $tenantId): bool
            {
                return $this->hasGrades;
            }
        };

        return new UpdateEvaluationHandler(
            $this->evaluationRepository,
            $gradesChecker,
            $this->clock,
        );
    }

    private function createAndSaveEvaluation(): Evaluation
    {
        $evaluation = Evaluation::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            classId: ClassId::fromString(self::CLASS_ID),
            subjectId: SubjectId::fromString(self::SUBJECT_ID),
            teacherId: UserId::fromString(self::TEACHER_ID),
            title: 'Contrôle chapitre 5',
            description: 'Évaluation sur les fonctions',
            evaluationDate: new DateTimeImmutable('2026-04-15'),
            gradeScale: new GradeScale(20),
            coefficient: new Coefficient(1.0),
            now: new DateTimeImmutable('2026-03-12 10:00:00'),
        );

        $this->evaluationRepository->save($evaluation);

        return $evaluation;
    }
}
