<?php

declare(strict_types=1);

namespace App\Scolarite\Application\Service;

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\Port\PeriodFinder;
use App\Scolarite\Application\Port\PeriodInfo;
use App\Scolarite\Domain\Model\Evaluation\Evaluation;
use App\Scolarite\Domain\Model\Evaluation\EvaluationId;
use App\Scolarite\Domain\Model\Grade\Grade;
use App\Scolarite\Domain\Model\Grade\GradeStatus;
use App\Scolarite\Domain\Repository\EvaluationRepository;
use App\Scolarite\Domain\Repository\EvaluationStatisticsRepository;
use App\Scolarite\Domain\Repository\GradeRepository;
use App\Scolarite\Domain\Repository\StudentAverageRepository;
use App\Scolarite\Domain\Service\AverageCalculator;
use App\Scolarite\Domain\Service\GradeEntry;
use App\Shared\Domain\Tenant\TenantId;

use function array_map;
use function count;

final readonly class RecalculerMoyennesService
{
    public function __construct(
        private EvaluationRepository $evaluationRepository,
        private GradeRepository $gradeRepository,
        private EvaluationStatisticsRepository $evaluationStatisticsRepository,
        private StudentAverageRepository $studentAverageRepository,
        private PeriodFinder $periodFinder,
        private AverageCalculator $calculator,
    ) {
    }

    public function recalculerStatistiquesEvaluation(EvaluationId $evaluationId, TenantId $tenantId): void
    {
        $grades = $this->gradeRepository->findByEvaluation($evaluationId, $tenantId);

        $gradedValues = [];

        foreach ($grades as $g) {
            if ($g->status === GradeStatus::GRADED && $g->value !== null) {
                $gradedValues[] = $g->value->value;
            }
        }

        $stats = $this->calculator->calculateClassStatistics($gradedValues);
        $this->evaluationStatisticsRepository->save($evaluationId, $stats);
    }

    public function recalculerMoyenneEleve(
        UserId $studentId,
        SubjectId $subjectId,
        ClassId $classId,
        PeriodInfo $period,
        TenantId $tenantId,
    ): void {
        $evaluations = $this->evaluationRepository->findWithPublishedGradesBySubjectAndClassInDateRange(
            $subjectId,
            $classId,
            $period->startDate,
            $period->endDate,
            $tenantId,
        );

        $evalIds = array_map(static fn (Evaluation $e) => $e->id, $evaluations);
        $allGradesByEval = $this->gradeRepository->findByEvaluations($evalIds, $tenantId);

        $entries = [];

        foreach ($evaluations as $eval) {
            /** @var list<Grade> $evalGrades */
            $evalGrades = $allGradesByEval[(string) $eval->id] ?? [];

            foreach ($evalGrades as $grade) {
                if ($grade->studentId->equals($studentId)
                    && $grade->status === GradeStatus::GRADED
                    && $grade->value !== null
                ) {
                    $entries[] = new GradeEntry(
                        value: $grade->value->value,
                        gradeScale: $eval->gradeScale,
                        coefficient: $eval->coefficient,
                    );
                }
            }
        }

        $subjectAverage = $this->calculator->calculateSubjectAverage($entries);

        if ($subjectAverage !== null) {
            $this->studentAverageRepository->saveSubjectAverage(
                $tenantId,
                $studentId,
                $subjectId,
                $period->periodId,
                $subjectAverage,
                count($entries),
            );
        } else {
            $this->studentAverageRepository->deleteSubjectAverage(
                $studentId,
                $subjectId,
                $period->periodId,
                $tenantId,
            );
        }

        // Moyenne générale
        $allSubjectAverages = $this->studentAverageRepository->findSubjectAveragesForStudent(
            $studentId,
            $period->periodId,
            $tenantId,
        );

        $generalAverage = $this->calculator->calculateGeneralAverage($allSubjectAverages);

        if ($generalAverage !== null) {
            $this->studentAverageRepository->saveGeneralAverage(
                $tenantId,
                $studentId,
                $period->periodId,
                $generalAverage,
            );
        } else {
            $this->studentAverageRepository->deleteGeneralAverage(
                $studentId,
                $period->periodId,
                $tenantId,
            );
        }
    }

    public function recalculerTousElevesPourEvaluation(EvaluationId $evaluationId, TenantId $tenantId): void
    {
        $evaluation = $this->evaluationRepository->findById($evaluationId, $tenantId);

        if ($evaluation === null || !$evaluation->notesPubliees()) {
            return;
        }

        $period = $this->periodFinder->findForDate($evaluation->evaluationDate, $tenantId);

        if ($period === null) {
            return;
        }

        $grades = $this->gradeRepository->findByEvaluation($evaluationId, $tenantId);
        $studentIds = [];

        foreach ($grades as $g) {
            $studentIds[(string) $g->studentId] = $g->studentId;
        }

        foreach ($studentIds as $studentId) {
            $this->recalculerMoyenneEleve(
                $studentId,
                $evaluation->subjectId,
                $evaluation->classId,
                $period,
                $tenantId,
            );
        }
    }
}
