<?php

declare(strict_types=1);

namespace App\Scolarite\Domain\Model\Schedule;

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\Event\CoursCree;
use App\Scolarite\Domain\Event\CoursModifie;
use App\Scolarite\Domain\Event\CoursSupprime;
use App\Shared\Domain\AggregateRoot;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;

/**
 * Aggregate Root représentant un créneau dans l'emploi du temps.
 *
 * Un créneau lie une classe, une matière et un enseignant à un jour de la semaine
 * et un horaire. Le créneau peut être récurrent (hebdomadaire) ou ponctuel.
 *
 * @see FR26: Créer et modifier l'emploi du temps des classes
 */
final class ScheduleSlot extends AggregateRoot
{
    public private(set) DateTimeImmutable $updatedAt;

    private function __construct(
        public private(set) ScheduleSlotId $id,
        public private(set) TenantId $tenantId,
        public private(set) ClassId $classId,
        public private(set) SubjectId $subjectId,
        public private(set) UserId $teacherId,
        public private(set) DayOfWeek $dayOfWeek,
        public private(set) TimeSlot $timeSlot,
        public private(set) ?string $room,
        public private(set) bool $isRecurring,
        public private(set) DateTimeImmutable $createdAt,
        public private(set) ?DateTimeImmutable $recurrenceStart = null,
        public private(set) ?DateTimeImmutable $recurrenceEnd = null,
    ) {
        $this->updatedAt = $createdAt;
    }

    /**
     * Crée un nouveau créneau dans l'emploi du temps.
     */
    public static function creer(
        TenantId $tenantId,
        ClassId $classId,
        SubjectId $subjectId,
        UserId $teacherId,
        DayOfWeek $dayOfWeek,
        TimeSlot $timeSlot,
        ?string $room,
        bool $isRecurring,
        DateTimeImmutable $now,
        ?DateTimeImmutable $recurrenceStart = null,
        ?DateTimeImmutable $recurrenceEnd = null,
    ): self {
        $room = $room !== '' ? $room : null;

        $slot = new self(
            id: ScheduleSlotId::generate(),
            tenantId: $tenantId,
            classId: $classId,
            subjectId: $subjectId,
            teacherId: $teacherId,
            dayOfWeek: $dayOfWeek,
            timeSlot: $timeSlot,
            room: $room,
            isRecurring: $isRecurring,
            createdAt: $now,
            recurrenceStart: $recurrenceStart,
            recurrenceEnd: $recurrenceEnd,
        );

        $slot->recordEvent(new CoursCree(
            slotId: $slot->id,
            classId: $classId,
            subjectId: $subjectId,
            teacherId: $teacherId,
            dayOfWeek: $dayOfWeek,
            startTime: $timeSlot->startTime,
            endTime: $timeSlot->endTime,
            room: $room,
            occurredOn: $now,
        ));

        return $slot;
    }

    /**
     * Modifie les propriétés du créneau.
     */
    public function modifier(
        ClassId $classId,
        SubjectId $subjectId,
        UserId $teacherId,
        DayOfWeek $dayOfWeek,
        TimeSlot $timeSlot,
        ?string $room,
        DateTimeImmutable $at,
    ): void {
        $room = $room !== '' ? $room : null;

        $this->classId = $classId;
        $this->subjectId = $subjectId;
        $this->teacherId = $teacherId;
        $this->dayOfWeek = $dayOfWeek;
        $this->timeSlot = $timeSlot;
        $this->room = $room;
        $this->updatedAt = $at;

        $this->recordEvent(new CoursModifie(
            slotId: $this->id,
            classId: $classId,
            subjectId: $subjectId,
            teacherId: $teacherId,
            dayOfWeek: $dayOfWeek,
            startTime: $timeSlot->startTime,
            endTime: $timeSlot->endTime,
            room: $room,
            occurredOn: $at,
        ));
    }

    /**
     * Enregistre l'événement de suppression avant le hard-delete par le repository.
     */
    public function supprimer(DateTimeImmutable $at): void
    {
        $this->recordEvent(new CoursSupprime(
            slotId: $this->id,
            classId: $this->classId,
            subjectId: $this->subjectId,
            occurredOn: $at,
        ));
    }

    /**
     * Termine la récurrence à une date donnée (pour le scénario "modifier toutes les futures").
     */
    public function terminerRecurrenceLe(DateTimeImmutable $date, DateTimeImmutable $at): void
    {
        $this->recurrenceEnd = $date;
        $this->updatedAt = $at;
    }

    /**
     * Vérifie si ce créneau récurrent est actif à une date donnée.
     *
     * Un créneau est actif si : récurrent, même jour de la semaine,
     * et la date est dans les bornes de récurrence.
     */
    public function isActiveOnDate(DateTimeImmutable $date): bool
    {
        if (!$this->isRecurring) {
            return false;
        }

        if (DayOfWeek::from((int) $date->format('N')) !== $this->dayOfWeek) {
            return false;
        }

        $dateOnly = $date->format('Y-m-d');

        if ($this->recurrenceStart !== null && $dateOnly < $this->recurrenceStart->format('Y-m-d')) {
            return false;
        }

        if ($this->recurrenceEnd !== null && $dateOnly > $this->recurrenceEnd->format('Y-m-d')) {
            return false;
        }

        return true;
    }

    /**
     * Vérifie si ce créneau entre en conflit temporel avec un autre sur le même jour.
     */
    public function conflictsAvec(self $other): bool
    {
        return $this->dayOfWeek === $other->dayOfWeek
            && $this->timeSlot->overlaps($other->timeSlot);
    }

    /**
     * Reconstitue un ScheduleSlot depuis le stockage.
     *
     * @internal Pour usage Infrastructure uniquement
     */
    public static function reconstitute(
        ScheduleSlotId $id,
        TenantId $tenantId,
        ClassId $classId,
        SubjectId $subjectId,
        UserId $teacherId,
        DayOfWeek $dayOfWeek,
        TimeSlot $timeSlot,
        ?string $room,
        bool $isRecurring,
        DateTimeImmutable $createdAt,
        DateTimeImmutable $updatedAt,
        ?DateTimeImmutable $recurrenceStart = null,
        ?DateTimeImmutable $recurrenceEnd = null,
    ): self {
        $slot = new self(
            id: $id,
            tenantId: $tenantId,
            classId: $classId,
            subjectId: $subjectId,
            teacherId: $teacherId,
            dayOfWeek: $dayOfWeek,
            timeSlot: $timeSlot,
            room: $room,
            isRecurring: $isRecurring,
            createdAt: $createdAt,
            recurrenceStart: $recurrenceStart,
            recurrenceEnd: $recurrenceEnd,
        );

        $slot->updatedAt = $updatedAt;

        return $slot;
    }
}
