<?php

declare(strict_types=1);

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

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\DuplicateHomework\DuplicateHomeworkCommand;
use App\Scolarite\Application\Command\DuplicateHomework\DuplicateHomeworkHandler;
use App\Scolarite\Application\Port\CurrentCalendarProvider;
use App\Scolarite\Application\Port\EnseignantAffectationChecker;
use App\Scolarite\Domain\Exception\DuplicationValidationException;
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\HomeworkAttachment;
use App\Scolarite\Domain\Model\Homework\HomeworkAttachmentId;
use App\Scolarite\Domain\Model\Homework\HomeworkStatus;
use App\Scolarite\Domain\Service\DueDateValidator;
use App\Scolarite\Domain\Service\HomeworkDuplicator;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkAttachmentRepository;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkRepository;
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 DuplicateHomeworkHandlerTest 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 const string TARGET_CLASS_1 = '550e8400-e29b-41d4-a716-446655440021';
    private const string TARGET_CLASS_2 = '550e8400-e29b-41d4-a716-446655440022';

    private InMemoryHomeworkRepository $homeworkRepository;
    private InMemoryHomeworkAttachmentRepository $attachmentRepository;
    private Clock $clock;

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

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

        $command = new DuplicateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $source->id,
            teacherId: self::TEACHER_ID,
            targetClassIds: [self::TARGET_CLASS_1],
        );

        $result = $handler($command);

        self::assertCount(1, $result->homeworks);
        self::assertSame($source->title, $result->homeworks[0]->title);
        self::assertSame($source->description, $result->homeworks[0]->description);
        self::assertTrue($result->homeworks[0]->classId->equals(ClassId::fromString(self::TARGET_CLASS_1)));
        self::assertSame(HomeworkStatus::PUBLISHED, $result->homeworks[0]->status);
    }

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

        $command = new DuplicateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $source->id,
            teacherId: self::TEACHER_ID,
            targetClassIds: [self::TARGET_CLASS_1, self::TARGET_CLASS_2],
        );

        $result = $handler($command);

        self::assertCount(2, $result->homeworks);
        self::assertTrue($result->homeworks[0]->classId->equals(ClassId::fromString(self::TARGET_CLASS_1)));
        self::assertTrue($result->homeworks[1]->classId->equals(ClassId::fromString(self::TARGET_CLASS_2)));
    }

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

        $command = new DuplicateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $source->id,
            teacherId: self::TEACHER_ID,
            targetClassIds: [self::TARGET_CLASS_1],
        );

        $result = $handler($command);

        $found = $this->homeworkRepository->get(
            $result->homeworks[0]->id,
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertSame($source->title, $found->title);
    }

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

        $command = new DuplicateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $source->id,
            teacherId: self::TEACHER_ID,
            targetClassIds: [self::TARGET_CLASS_1, self::TARGET_CLASS_2],
            dueDates: [
                self::TARGET_CLASS_1 => '2026-04-20',
                self::TARGET_CLASS_2 => '2026-04-21',
            ],
        );

        $result = $handler($command);

        self::assertSame('2026-04-20', $result->homeworks[0]->dueDate->format('Y-m-d'));
        self::assertSame('2026-04-21', $result->homeworks[1]->dueDate->format('Y-m-d'));
    }

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

        $command = new DuplicateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $source->id,
            teacherId: self::TEACHER_ID,
            targetClassIds: [self::TARGET_CLASS_1],
        );

        $result = $handler($command);

        self::assertSame($source->dueDate->format('Y-m-d'), $result->homeworks[0]->dueDate->format('Y-m-d'));
    }

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

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

        $handler(new DuplicateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: '550e8400-e29b-41d4-a716-446655440099',
            teacherId: self::TEACHER_ID,
            targetClassIds: [self::TARGET_CLASS_1],
        ));
    }

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

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

        $handler(new DuplicateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $source->id,
            teacherId: self::OTHER_TEACHER_ID,
            targetClassIds: [self::TARGET_CLASS_1],
        ));
    }

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

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

        $handler(new DuplicateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $source->id,
            teacherId: self::TEACHER_ID,
            targetClassIds: [self::TARGET_CLASS_1],
        ));
    }

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

        try {
            $handler(new DuplicateHomeworkCommand(
                tenantId: self::TENANT_ID,
                homeworkId: (string) $source->id,
                teacherId: self::TEACHER_ID,
                targetClassIds: [self::TARGET_CLASS_1, self::TARGET_CLASS_2],
            ));
            self::fail('Expected DuplicationValidationException');
        } catch (DuplicationValidationException $e) {
            self::assertCount(2, $e->results);
            self::assertFalse($e->results[0]->valid);
            self::assertFalse($e->results[1]->valid);
        }
    }

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

        $attachment = new HomeworkAttachment(
            id: HomeworkAttachmentId::generate(),
            filename: 'exercice.pdf',
            filePath: 'homework/tenant/source/exercice.pdf',
            fileSize: 1024,
            mimeType: 'application/pdf',
            uploadedAt: new DateTimeImmutable('2026-03-12'),
        );
        $this->attachmentRepository->save($source->id, $attachment);

        $command = new DuplicateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $source->id,
            teacherId: self::TEACHER_ID,
            targetClassIds: [self::TARGET_CLASS_1, self::TARGET_CLASS_2],
        );

        $result = $handler($command);

        foreach ($result->homeworks as $duplicate) {
            $attachments = $this->attachmentRepository->findByHomeworkId($duplicate->id);
            self::assertCount(1, $attachments);
            self::assertSame('exercice.pdf', $attachments[0]->filename);
            self::assertSame('homework/tenant/source/exercice.pdf', $attachments[0]->filePath);
            self::assertFalse($attachments[0]->id->equals($attachment->id));
        }
    }

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

        $command = new DuplicateHomeworkCommand(
            tenantId: self::TENANT_ID,
            homeworkId: (string) $source->id,
            teacherId: self::TEACHER_ID,
            targetClassIds: [self::TARGET_CLASS_1, self::TARGET_CLASS_2],
        );

        $result = $handler($command);

        self::assertFalse($result->homeworks[0]->id->equals($source->id));
        self::assertFalse($result->homeworks[1]->id->equals($source->id));
        self::assertFalse($result->homeworks[0]->id->equals($result->homeworks[1]->id));
    }

    private function createAndSaveHomework(): Homework
    {
        $homework = Homework::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: 'Exercices chapitre 5',
            description: 'Faire les exercices 1 à 10',
            dueDate: new DateTimeImmutable('2026-04-15'),
            now: new DateTimeImmutable('2026-03-10 10:00:00'),
        );

        $homework->pullDomainEvents();
        $this->homeworkRepository->save($homework);

        return $homework;
    }

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

        $connection = $this->createMock(Connection::class);
        $connection->method('beginTransaction');
        $connection->method('commit');
        $connection->method('rollBack');

        return new DuplicateHomeworkHandler(
            $this->homeworkRepository,
            $this->attachmentRepository,
            $affectationChecker,
            $calendarProvider,
            new DueDateValidator(),
            new HomeworkDuplicator(),
            $this->clock,
            $connection,
        );
    }
}
