<?php

declare(strict_types=1);

namespace App\Tests\Unit\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\Scolarite\Domain\Model\Schedule\DayOfWeek;
use App\Scolarite\Domain\Model\Schedule\ScheduleSlot;
use App\Scolarite\Domain\Model\Schedule\ScheduleSlotId;
use App\Scolarite\Domain\Model\Schedule\TimeSlot;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class ScheduleSlotTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string CLASS_ID = '550e8400-e29b-41d4-a716-446655440020';
    private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440030';
    private const string TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';

    #[Test]
    public function creerCreatesSlotWithCorrectProperties(): void
    {
        $slot = $this->createSlot();

        self::assertTrue($slot->tenantId->equals(TenantId::fromString(self::TENANT_ID)));
        self::assertTrue($slot->classId->equals(ClassId::fromString(self::CLASS_ID)));
        self::assertTrue($slot->subjectId->equals(SubjectId::fromString(self::SUBJECT_ID)));
        self::assertTrue($slot->teacherId->equals(UserId::fromString(self::TEACHER_ID)));
        self::assertSame(DayOfWeek::MONDAY, $slot->dayOfWeek);
        self::assertSame('08:00', $slot->timeSlot->startTime);
        self::assertSame('09:00', $slot->timeSlot->endTime);
        self::assertNull($slot->room);
        self::assertTrue($slot->isRecurring);
    }

    #[Test]
    public function creerRecordsCoursCreeEvent(): void
    {
        $slot = $this->createSlot();

        $events = $slot->pullDomainEvents();

        self::assertCount(1, $events);
        self::assertInstanceOf(CoursCree::class, $events[0]);
        self::assertTrue($slot->id->equals($events[0]->slotId));
    }

    #[Test]
    public function creerWithRoomSetsRoom(): void
    {
        $slot = $this->createSlot(room: 'Salle 101');

        self::assertSame('Salle 101', $slot->room);
    }

    #[Test]
    public function modifierUpdatesProperties(): void
    {
        $slot = $this->createSlot();
        $slot->pullDomainEvents();

        $newSubjectId = SubjectId::fromString('550e8400-e29b-41d4-a716-446655440031');
        $newTeacherId = UserId::fromString('550e8400-e29b-41d4-a716-446655440011');
        $newTimeSlot = new TimeSlot('10:00', '11:00');
        $now = new DateTimeImmutable('2026-03-02 15:00:00');

        $newClassId = ClassId::fromString('550e8400-e29b-41d4-a716-446655440021');

        $slot->modifier(
            classId: $newClassId,
            subjectId: $newSubjectId,
            teacherId: $newTeacherId,
            dayOfWeek: DayOfWeek::TUESDAY,
            timeSlot: $newTimeSlot,
            room: 'Salle 202',
            at: $now,
        );

        self::assertTrue($slot->classId->equals($newClassId));
        self::assertTrue($slot->subjectId->equals($newSubjectId));
        self::assertTrue($slot->teacherId->equals($newTeacherId));
        self::assertSame(DayOfWeek::TUESDAY, $slot->dayOfWeek);
        self::assertSame('10:00', $slot->timeSlot->startTime);
        self::assertSame('11:00', $slot->timeSlot->endTime);
        self::assertSame('Salle 202', $slot->room);
        self::assertEquals($now, $slot->updatedAt);
    }

    #[Test]
    public function modifierRecordsCoursModifieEvent(): void
    {
        $slot = $this->createSlot();
        $slot->pullDomainEvents();

        $now = new DateTimeImmutable('2026-03-02 15:00:00');
        $slot->modifier(
            classId: $slot->classId,
            subjectId: $slot->subjectId,
            teacherId: $slot->teacherId,
            dayOfWeek: DayOfWeek::TUESDAY,
            timeSlot: new TimeSlot('10:00', '11:00'),
            room: null,
            at: $now,
        );

        $events = $slot->pullDomainEvents();

        self::assertCount(1, $events);
        self::assertInstanceOf(CoursModifie::class, $events[0]);
        self::assertTrue($slot->id->equals($events[0]->slotId));
    }

    #[Test]
    public function supprimerRecordsCoursSupprime(): void
    {
        $slot = $this->createSlot();
        $slot->pullDomainEvents();

        $now = new DateTimeImmutable('2026-03-02 15:00:00');
        $slot->supprimer($now);

        $events = $slot->pullDomainEvents();

        self::assertCount(1, $events);
        self::assertInstanceOf(CoursSupprime::class, $events[0]);
        self::assertTrue($slot->id->equals($events[0]->slotId));
    }

    #[Test]
    public function reconstituteRestoresAllPropertiesWithoutEvents(): void
    {
        $id = ScheduleSlotId::generate();
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $classId = ClassId::fromString(self::CLASS_ID);
        $subjectId = SubjectId::fromString(self::SUBJECT_ID);
        $teacherId = UserId::fromString(self::TEACHER_ID);
        $timeSlot = new TimeSlot('14:00', '15:30');
        $createdAt = new DateTimeImmutable('2026-03-01 10:00:00');
        $updatedAt = new DateTimeImmutable('2026-03-02 14:00:00');

        $slot = ScheduleSlot::reconstitute(
            id: $id,
            tenantId: $tenantId,
            classId: $classId,
            subjectId: $subjectId,
            teacherId: $teacherId,
            dayOfWeek: DayOfWeek::FRIDAY,
            timeSlot: $timeSlot,
            room: 'Salle 305',
            isRecurring: false,
            createdAt: $createdAt,
            updatedAt: $updatedAt,
        );

        self::assertTrue($slot->id->equals($id));
        self::assertTrue($slot->tenantId->equals($tenantId));
        self::assertTrue($slot->classId->equals($classId));
        self::assertTrue($slot->subjectId->equals($subjectId));
        self::assertTrue($slot->teacherId->equals($teacherId));
        self::assertSame(DayOfWeek::FRIDAY, $slot->dayOfWeek);
        self::assertSame('14:00', $slot->timeSlot->startTime);
        self::assertSame('15:30', $slot->timeSlot->endTime);
        self::assertSame('Salle 305', $slot->room);
        self::assertFalse($slot->isRecurring);
        self::assertEquals($createdAt, $slot->createdAt);
        self::assertEquals($updatedAt, $slot->updatedAt);
        self::assertEmpty($slot->pullDomainEvents());
    }

    #[Test]
    public function creerWithRecurrenceBoundsSetsDates(): void
    {
        $recurrenceStart = new DateTimeImmutable('2026-09-01');
        $recurrenceEnd = new DateTimeImmutable('2027-07-04');

        $slot = ScheduleSlot::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            classId: ClassId::fromString(self::CLASS_ID),
            subjectId: SubjectId::fromString(self::SUBJECT_ID),
            teacherId: UserId::fromString(self::TEACHER_ID),
            dayOfWeek: DayOfWeek::MONDAY,
            timeSlot: new TimeSlot('08:00', '09:00'),
            room: null,
            isRecurring: true,
            now: new DateTimeImmutable('2026-09-01 10:00:00'),
            recurrenceStart: $recurrenceStart,
            recurrenceEnd: $recurrenceEnd,
        );

        self::assertEquals($recurrenceStart, $slot->recurrenceStart);
        self::assertEquals($recurrenceEnd, $slot->recurrenceEnd);
    }

    #[Test]
    public function creerWithoutRecurrenceBoundsDefaultsToNull(): void
    {
        $slot = $this->createSlot();

        self::assertNull($slot->recurrenceStart);
        self::assertNull($slot->recurrenceEnd);
    }

    #[Test]
    public function isActiveOnDateReturnsTrueForMatchingDayWithinBounds(): void
    {
        $slot = $this->createRecurringSlot(
            dayOfWeek: DayOfWeek::MONDAY,
            recurrenceStart: new DateTimeImmutable('2026-09-01'),
            recurrenceEnd: new DateTimeImmutable('2027-07-04'),
        );

        // 2026-09-07 is a Monday within bounds
        self::assertTrue($slot->isActiveOnDate(new DateTimeImmutable('2026-09-07')));
    }

    #[Test]
    public function isActiveOnDateReturnsFalseForWrongDayOfWeek(): void
    {
        $slot = $this->createRecurringSlot(
            dayOfWeek: DayOfWeek::MONDAY,
            recurrenceStart: new DateTimeImmutable('2026-09-01'),
            recurrenceEnd: new DateTimeImmutable('2027-07-04'),
        );

        // 2026-09-08 is a Tuesday
        self::assertFalse($slot->isActiveOnDate(new DateTimeImmutable('2026-09-08')));
    }

    #[Test]
    public function isActiveOnDateReturnsFalseBeforeRecurrenceStart(): void
    {
        $slot = $this->createRecurringSlot(
            dayOfWeek: DayOfWeek::MONDAY,
            recurrenceStart: new DateTimeImmutable('2026-09-01'),
            recurrenceEnd: new DateTimeImmutable('2027-07-04'),
        );

        // 2026-08-25 is a Monday but before start
        self::assertFalse($slot->isActiveOnDate(new DateTimeImmutable('2026-08-25')));
    }

    #[Test]
    public function isActiveOnDateReturnsFalseAfterRecurrenceEnd(): void
    {
        $slot = $this->createRecurringSlot(
            dayOfWeek: DayOfWeek::MONDAY,
            recurrenceStart: new DateTimeImmutable('2026-09-01'),
            recurrenceEnd: new DateTimeImmutable('2027-07-04'),
        );

        // 2027-07-07 is a Monday but after end
        self::assertFalse($slot->isActiveOnDate(new DateTimeImmutable('2027-07-07')));
    }

    #[Test]
    public function isActiveOnDateReturnsTrueWithNoBounds(): void
    {
        $slot = $this->createRecurringSlot(
            dayOfWeek: DayOfWeek::MONDAY,
            recurrenceStart: null,
            recurrenceEnd: null,
        );

        // Any Monday should work
        self::assertTrue($slot->isActiveOnDate(new DateTimeImmutable('2026-09-07')));
    }

    #[Test]
    public function isActiveOnDateReturnsFalseForNonRecurringSlot(): void
    {
        $slot = ScheduleSlot::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            classId: ClassId::fromString(self::CLASS_ID),
            subjectId: SubjectId::fromString(self::SUBJECT_ID),
            teacherId: UserId::fromString(self::TEACHER_ID),
            dayOfWeek: DayOfWeek::MONDAY,
            timeSlot: new TimeSlot('08:00', '09:00'),
            room: null,
            isRecurring: false,
            now: new DateTimeImmutable('2026-09-01 10:00:00'),
        );

        self::assertFalse($slot->isActiveOnDate(new DateTimeImmutable('2026-09-07')));
    }

    #[Test]
    public function isActiveOnDateIncludesBoundaryDates(): void
    {
        $slot = $this->createRecurringSlot(
            dayOfWeek: DayOfWeek::MONDAY,
            recurrenceStart: new DateTimeImmutable('2026-09-07'),
            recurrenceEnd: new DateTimeImmutable('2026-09-07'),
        );

        // Exact start = exact end date
        self::assertTrue($slot->isActiveOnDate(new DateTimeImmutable('2026-09-07')));
    }

    #[Test]
    public function reconstituteRestoresRecurrenceBounds(): void
    {
        $recurrenceStart = new DateTimeImmutable('2026-09-01');
        $recurrenceEnd = new DateTimeImmutable('2027-07-04');

        $slot = ScheduleSlot::reconstitute(
            id: ScheduleSlotId::generate(),
            tenantId: TenantId::fromString(self::TENANT_ID),
            classId: ClassId::fromString(self::CLASS_ID),
            subjectId: SubjectId::fromString(self::SUBJECT_ID),
            teacherId: UserId::fromString(self::TEACHER_ID),
            dayOfWeek: DayOfWeek::FRIDAY,
            timeSlot: new TimeSlot('14:00', '15:30'),
            room: null,
            isRecurring: true,
            createdAt: new DateTimeImmutable('2026-03-01 10:00:00'),
            updatedAt: new DateTimeImmutable('2026-03-02 14:00:00'),
            recurrenceStart: $recurrenceStart,
            recurrenceEnd: $recurrenceEnd,
        );

        self::assertEquals($recurrenceStart, $slot->recurrenceStart);
        self::assertEquals($recurrenceEnd, $slot->recurrenceEnd);
    }

    private function createSlot(?string $room = null): ScheduleSlot
    {
        return ScheduleSlot::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            classId: ClassId::fromString(self::CLASS_ID),
            subjectId: SubjectId::fromString(self::SUBJECT_ID),
            teacherId: UserId::fromString(self::TEACHER_ID),
            dayOfWeek: DayOfWeek::MONDAY,
            timeSlot: new TimeSlot('08:00', '09:00'),
            room: $room,
            isRecurring: true,
            now: new DateTimeImmutable('2026-03-01 10:00:00'),
        );
    }

    private function createRecurringSlot(
        DayOfWeek $dayOfWeek,
        ?DateTimeImmutable $recurrenceStart,
        ?DateTimeImmutable $recurrenceEnd,
    ): ScheduleSlot {
        return ScheduleSlot::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            classId: ClassId::fromString(self::CLASS_ID),
            subjectId: SubjectId::fromString(self::SUBJECT_ID),
            teacherId: UserId::fromString(self::TEACHER_ID),
            dayOfWeek: $dayOfWeek,
            timeSlot: new TimeSlot('08:00', '09:00'),
            room: null,
            isRecurring: true,
            now: new DateTimeImmutable('2026-09-01 10:00:00'),
            recurrenceStart: $recurrenceStart,
            recurrenceEnd: $recurrenceEnd,
        );
    }
}
