<?php

declare(strict_types=1);

namespace App\Scolarite\Application\Command\DuplicateHomework;

use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Application\Port\CurrentCalendarProvider;
use App\Scolarite\Application\Port\EnseignantAffectationChecker;
use App\Scolarite\Domain\Exception\DateEcheanceInvalideException;
use App\Scolarite\Domain\Exception\DuplicationValidationException;
use App\Scolarite\Domain\Exception\NonProprietaireDuDevoirException;
use App\Scolarite\Domain\Model\Homework\HomeworkAttachment;
use App\Scolarite\Domain\Model\Homework\HomeworkAttachmentId;
use App\Scolarite\Domain\Model\Homework\HomeworkId;
use App\Scolarite\Domain\Repository\HomeworkAttachmentRepository;
use App\Scolarite\Domain\Repository\HomeworkRepository;
use App\Scolarite\Domain\Service\DueDateValidator;
use App\Scolarite\Domain\Service\HomeworkDuplicator;
use App\Scolarite\Domain\ValueObject\ClassValidationResult;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;

use function array_filter;
use function array_unique;
use function array_values;

use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use Exception;

use function in_array;

use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Throwable;

#[AsMessageHandler(bus: 'command.bus')]
final readonly class DuplicateHomeworkHandler
{
    public function __construct(
        private HomeworkRepository $homeworkRepository,
        private HomeworkAttachmentRepository $attachmentRepository,
        private EnseignantAffectationChecker $affectationChecker,
        private CurrentCalendarProvider $calendarProvider,
        private DueDateValidator $dueDateValidator,
        private HomeworkDuplicator $duplicator,
        private Clock $clock,
        private Connection $connection,
    ) {
    }

    public function __invoke(DuplicateHomeworkCommand $command): DuplicationResult
    {
        $tenantId = TenantId::fromString($command->tenantId);
        $homeworkId = HomeworkId::fromString($command->homeworkId);
        $teacherId = UserId::fromString($command->teacherId);
        $now = $this->clock->now();

        $source = $this->homeworkRepository->get($homeworkId, $tenantId);

        if ((string) $source->teacherId !== (string) $teacherId) {
            throw NonProprietaireDuDevoirException::withId($homeworkId);
        }

        $calendar = $this->calendarProvider->forCurrentYear($tenantId);

        // Dédupliquer et exclure la classe source
        $uniqueClassIds = array_values(array_unique($command->targetClassIds));
        $sourceClassIdStr = (string) $source->classId;

        $targetClassIds = [];
        $dueDates = [];
        $validationResults = [];
        $seen = [];

        foreach ($uniqueClassIds as $classIdStr) {
            if ($classIdStr === $sourceClassIdStr) {
                continue;
            }

            if (in_array($classIdStr, $seen, true)) {
                continue;
            }

            $seen[] = $classIdStr;
            $classId = ClassId::fromString($classIdStr);

            try {
                $dueDate = isset($command->dueDates[$classIdStr])
                    ? new DateTimeImmutable($command->dueDates[$classIdStr])
                    : $source->dueDate;
            } catch (Exception) {
                $validationResults[] = ClassValidationResult::failure(
                    $classId,
                    "Date d'échéance invalide : {$command->dueDates[$classIdStr]}.",
                );

                continue;
            }

            if (!$this->affectationChecker->estAffecte($teacherId, $classId, $source->subjectId, $tenantId)) {
                $validationResults[] = ClassValidationResult::failure(
                    $classId,
                    "L'enseignant n'est pas affecté à cette classe pour cette matière.",
                );

                continue;
            }

            try {
                $this->dueDateValidator->valider($dueDate, $now, $calendar);
                $validationResults[] = ClassValidationResult::success($classId);
                $targetClassIds[] = $classId;
                $dueDates[] = $dueDate;
            } catch (DateEcheanceInvalideException $e) {
                $validationResults[] = ClassValidationResult::failure($classId, $e->getMessage());
            }
        }

        $failures = array_filter($validationResults, static fn (ClassValidationResult $r): bool => !$r->valid);

        if ($failures !== []) {
            throw DuplicationValidationException::withResults($validationResults);
        }

        $duplicates = $this->duplicator->dupliquer($source, $targetClassIds, $dueDates, $now);

        $sourceAttachments = $this->attachmentRepository->findByHomeworkId($source->id);

        $this->connection->beginTransaction();

        try {
            foreach ($duplicates as $duplicate) {
                $this->homeworkRepository->save($duplicate);

                foreach ($sourceAttachments as $attachment) {
                    $this->attachmentRepository->save(
                        $duplicate->id,
                        new HomeworkAttachment(
                            id: HomeworkAttachmentId::generate(),
                            filename: $attachment->filename,
                            filePath: $attachment->filePath,
                            fileSize: $attachment->fileSize,
                            mimeType: $attachment->mimeType,
                            uploadedAt: $now,
                        ),
                    );
                }
            }

            $this->connection->commit();
        } catch (Throwable $e) {
            $this->connection->rollBack();

            throw $e;
        }

        $warnings = array_values(array_filter(
            $validationResults,
            static fn (ClassValidationResult $r): bool => $r->valid && $r->warning !== null,
        ));

        return new DuplicationResult($duplicates, $warnings);
    }
}
