<?php

declare(strict_types=1);

namespace App\Scolarite\Application\Command\UpdateRecurringSlot;

use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Exception\DateExceptionInvalideException;
use App\Scolarite\Domain\Model\Schedule\DayOfWeek;
use App\Scolarite\Domain\Model\Schedule\ScheduleException;
use App\Scolarite\Domain\Model\Schedule\ScheduleSlot;
use App\Scolarite\Domain\Model\Schedule\ScheduleSlotId;
use App\Scolarite\Domain\Model\Schedule\TimeSlot;
use App\Scolarite\Domain\Repository\ScheduleExceptionRepository;
use App\Scolarite\Domain\Repository\ScheduleSlotRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler(bus: 'command.bus')]
final readonly class UpdateRecurringSlotHandler
{
    public function __construct(
        private ScheduleSlotRepository $slotRepository,
        private ScheduleExceptionRepository $exceptionRepository,
        private Clock $clock,
    ) {
    }

    /**
     * @return array{exception: ?ScheduleException, newSlot: ?ScheduleSlot}
     */
    public function __invoke(UpdateRecurringSlotCommand $command): array
    {
        $tenantId = TenantId::fromString($command->tenantId);
        $slotId = ScheduleSlotId::fromString($command->slotId);
        $slot = $this->slotRepository->get($slotId, $tenantId);
        $now = $this->clock->now();
        $occurrenceDate = new DateTimeImmutable($command->occurrenceDate);
        $newTimeSlot = new TimeSlot($command->startTime, $command->endTime);

        if (!$slot->isActiveOnDate($occurrenceDate)) {
            throw DateExceptionInvalideException::pourSlotEtDate($slotId, $occurrenceDate);
        }

        if ($command->scope === 'this_occurrence') {
            return $this->handleThisOccurrence(
                $tenantId,
                $slotId,
                $occurrenceDate,
                $newTimeSlot,
                $command->room,
                UserId::fromString($command->teacherId),
                UserId::fromString($command->updatedBy),
                $now,
            );
        }

        // all_future: end current recurrence, create new slot
        return $this->handleAllFuture(
            $slot,
            $tenantId,
            $occurrenceDate,
            $command,
            $now,
        );
    }

    /**
     * @return array{exception: ScheduleException, newSlot: null}
     */
    private function handleThisOccurrence(
        TenantId $tenantId,
        ScheduleSlotId $slotId,
        DateTimeImmutable $occurrenceDate,
        TimeSlot $newTimeSlot,
        ?string $newRoom,
        UserId $newTeacherId,
        UserId $createdBy,
        DateTimeImmutable $now,
    ): array {
        $exception = ScheduleException::modifier(
            tenantId: $tenantId,
            slotId: $slotId,
            exceptionDate: $occurrenceDate,
            newTimeSlot: $newTimeSlot,
            newRoom: $newRoom,
            newTeacherId: $newTeacherId,
            reason: null,
            createdBy: $createdBy,
            now: $now,
        );

        $this->exceptionRepository->save($exception);

        return ['exception' => $exception, 'newSlot' => null];
    }

    /**
     * @return array{exception: null, newSlot: ScheduleSlot}
     */
    private function handleAllFuture(
        ScheduleSlot $slot,
        TenantId $tenantId,
        DateTimeImmutable $occurrenceDate,
        UpdateRecurringSlotCommand $command,
        DateTimeImmutable $now,
    ): array {
        // Save original end date before modifying
        $originalRecurrenceEnd = $slot->recurrenceEnd;

        // End original slot one day before the change
        $dayBefore = $occurrenceDate->modify('-1 day');
        $slot->terminerRecurrenceLe($dayBefore, $now);
        $this->slotRepository->save($slot);

        // Use the Monday of the occurrence week as recurrenceStart so the new slot
        // is visible in the current week even when moved to an earlier day-of-week.
        $dayN = (int) $occurrenceDate->format('N');
        $weekMonday = $occurrenceDate->modify('-' . ($dayN - 1) . ' days');

        // Create new slot starting from the Monday of the occurrence week
        $newSlot = ScheduleSlot::creer(
            tenantId: $tenantId,
            classId: ClassId::fromString($command->classId),
            subjectId: SubjectId::fromString($command->subjectId),
            teacherId: UserId::fromString($command->teacherId),
            dayOfWeek: DayOfWeek::from($command->dayOfWeek),
            timeSlot: new TimeSlot($command->startTime, $command->endTime),
            room: $command->room,
            isRecurring: true,
            now: $now,
            recurrenceStart: $weekMonday,
            recurrenceEnd: $originalRecurrenceEnd,
        );

        $this->slotRepository->save($newSlot);

        return ['exception' => null, 'newSlot' => $newSlot];
    }
}
