<?php

declare(strict_types=1);

namespace App\Administration\Application\Command\UpdatePeriod;

use App\Administration\Application\Port\GradeExistenceChecker;
use App\Administration\Domain\Event\PeriodeModifiee;
use App\Administration\Domain\Exception\PeriodeAvecNotesException;
use App\Administration\Domain\Exception\PeriodeNonTrouveeException;
use App\Administration\Domain\Exception\PeriodesNonConfigureesException;
use App\Administration\Domain\Model\AcademicYear\AcademicPeriod;
use App\Administration\Domain\Model\AcademicYear\PeriodConfiguration;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Repository\PeriodConfigurationRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\MessageBusInterface;

/**
 * Handler pour modifier les dates d'une période scolaire.
 *
 * Reconstruit la PeriodConfiguration avec la période modifiée,
 * ce qui déclenche la revalidation complète (chevauchement, contiguïté).
 * Si la période contient des notes, une confirmation explicite est requise.
 */
#[AsMessageHandler(bus: 'command.bus')]
final readonly class UpdatePeriodHandler
{
    public function __construct(
        private PeriodConfigurationRepository $repository,
        private GradeExistenceChecker $gradeExistenceChecker,
        private Clock $clock,
        private MessageBusInterface $eventBus,
    ) {
    }

    public function __invoke(UpdatePeriodCommand $command): PeriodConfiguration
    {
        $tenantId = TenantId::fromString($command->tenantId);
        $academicYearId = AcademicYearId::fromString($command->academicYearId);

        $existing = $this->repository->findByAcademicYear($tenantId, $academicYearId);
        if ($existing === null) {
            throw PeriodesNonConfigureesException::pourAnnee($command->academicYearId);
        }

        $found = false;
        $targetLabel = '';
        $updatedPeriods = [];
        foreach ($existing->periods as $period) {
            if ($period->sequence === $command->sequence) {
                $found = true;
                $targetLabel = $period->label;
                $updatedPeriods[] = new AcademicPeriod(
                    sequence: $period->sequence,
                    label: $period->label,
                    startDate: new DateTimeImmutable($command->startDate),
                    endDate: new DateTimeImmutable($command->endDate),
                );
            } else {
                $updatedPeriods[] = $period;
            }
        }

        if (!$found) {
            throw PeriodeNonTrouveeException::pourSequence($command->sequence, $command->academicYearId);
        }

        if (!$command->confirmImpact) {
            $hasGrades = $this->gradeExistenceChecker->hasGradesInPeriod(
                $tenantId,
                $academicYearId,
                $command->sequence,
            );

            if ($hasGrades) {
                throw PeriodeAvecNotesException::confirmationRequise($targetLabel);
            }
        }

        $newConfiguration = new PeriodConfiguration($existing->type, $updatedPeriods);

        $this->repository->save($tenantId, $academicYearId, $newConfiguration);

        $this->eventBus->dispatch(new PeriodeModifiee(
            academicYearId: $academicYearId,
            tenantId: $tenantId,
            periodSequence: $command->sequence,
            periodLabel: $targetLabel,
            occurredOn: $this->clock->now(),
        ));

        return $newConfiguration;
    }
}
