<?php

declare(strict_types=1);

namespace App\Scolarite\Application\Command\SaveCompetencyResults;

use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Application\Port\ClassStudentsReader;
use App\Scolarite\Domain\Exception\NonProprietaireDeLEvaluationException;
use App\Scolarite\Domain\Model\Competency\CompetencyEvaluationId;
use App\Scolarite\Domain\Model\Competency\CompetencyLevel;
use App\Scolarite\Domain\Model\Competency\StudentCompetencyResult;
use App\Scolarite\Domain\Model\Evaluation\EvaluationId;
use App\Scolarite\Domain\Repository\CompetencyEvaluationRepository;
use App\Scolarite\Domain\Repository\CustomCompetencyLevelRepository;
use App\Scolarite\Domain\Repository\EvaluationRepository;
use App\Scolarite\Domain\Repository\StudentCompetencyResultRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;

use function array_column;
use function array_map;

use DomainException;

use function in_array;

use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler(bus: 'command.bus')]
final readonly class SaveCompetencyResultsHandler
{
    public function __construct(
        private EvaluationRepository $evaluationRepository,
        private CompetencyEvaluationRepository $competencyEvaluationRepository,
        private StudentCompetencyResultRepository $resultRepository,
        private CustomCompetencyLevelRepository $customLevelRepository,
        private ClassStudentsReader $classStudentsReader,
        private Clock $clock,
    ) {
    }

    /** @return array<StudentCompetencyResult> */
    public function __invoke(SaveCompetencyResultsCommand $command): array
    {
        $tenantId = TenantId::fromString($command->tenantId);
        $evaluationId = EvaluationId::fromString($command->evaluationId);
        $teacherId = UserId::fromString($command->teacherId);
        $now = $this->clock->now();

        $evaluation = $this->evaluationRepository->get($evaluationId, $tenantId);

        if ((string) $evaluation->teacherId !== (string) $teacherId) {
            throw NonProprietaireDeLEvaluationException::withId($evaluationId);
        }

        // Build set of valid student IDs in the class
        $classStudents = $this->classStudentsReader->studentsInClass((string) $evaluation->classId, $tenantId);
        $validStudentIds = array_column($classStudents, 'id');

        // Build set of valid competencyEvaluationIds for this evaluation
        $validCeIds = $this->competencyEvaluationRepository->findByEvaluation($evaluationId);
        $validCeIdSet = array_map(static fn ($ce) => (string) $ce->id, $validCeIds);

        // Build set of valid level codes (custom or standard)
        $customLevels = $this->customLevelRepository->findByTenant($tenantId);
        $validLevelCodes = $customLevels !== []
            ? array_column(array_map(static fn ($l) => ['code' => $l->code], $customLevels), 'code')
            : array_map(static fn ($l) => $l->value, CompetencyLevel::cases());

        $savedResults = [];

        foreach ($command->results as $resultInput) {
            $studentId = UserId::fromString($resultInput['studentId']);
            $ceId = CompetencyEvaluationId::fromString($resultInput['competencyEvaluationId']);
            /** @var ?string $levelCode */
            $levelCode = $resultInput['levelCode'] ?? null;

            if (!in_array($resultInput['studentId'], $validStudentIds, true)) {
                throw new DomainException('L\'élève ne fait pas partie de la classe de cette évaluation.');
            }

            if (!in_array((string) $ceId, $validCeIdSet, true)) {
                throw new DomainException('La compétence évaluée ne fait pas partie de cette évaluation.');
            }

            $existing = $this->resultRepository->findByCompetencyEvaluationAndStudent(
                $ceId,
                $studentId,
                $tenantId,
            );

            // Toggle OFF: delete existing result
            if ($levelCode === null) {
                if ($existing !== null) {
                    $this->resultRepository->delete($existing);
                }
                continue;
            }

            if (!in_array($levelCode, $validLevelCodes, true)) {
                throw new DomainException('Code de niveau invalide : ' . $levelCode);
            }

            if ($existing !== null) {
                $existing->modifier(
                    levelCode: $levelCode,
                    now: $now,
                );
                $this->resultRepository->save($existing);
                $savedResults[] = $existing;
            } else {
                $result = StudentCompetencyResult::saisir(
                    tenantId: $tenantId,
                    competencyEvaluationId: $ceId,
                    studentId: $studentId,
                    levelCode: $levelCode,
                    now: $now,
                );
                $this->resultRepository->save($result);
                $savedResults[] = $result;
            }
        }

        return $savedResults;
    }
}
