<?php

declare(strict_types=1);

namespace App\Tests\Unit\Scolarite\Domain\Model\Schedule;

use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Domain\Model\Schedule\ScheduleException;
use App\Scolarite\Domain\Model\Schedule\ScheduleExceptionId;
use App\Scolarite\Domain\Model\Schedule\ScheduleExceptionType;
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 ScheduleExceptionTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string SLOT_ID = '550e8400-e29b-41d4-a716-446655440050';
    private const string TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';
    private const string CREATOR_ID = '550e8400-e29b-41d4-a716-446655440099';

    #[Test]
    public function annulerCreatesACancelledExceptionWithCorrectProperties(): void
    {
        $exception = ScheduleException::annuler(
            tenantId: TenantId::fromString(self::TENANT_ID),
            slotId: ScheduleSlotId::fromString(self::SLOT_ID),
            exceptionDate: new DateTimeImmutable('2026-10-05'),
            reason: 'Grève',
            createdBy: UserId::fromString(self::CREATOR_ID),
            now: new DateTimeImmutable('2026-10-01 10:00:00'),
        );

        self::assertTrue($exception->tenantId->equals(TenantId::fromString(self::TENANT_ID)));
        self::assertTrue($exception->slotId->equals(ScheduleSlotId::fromString(self::SLOT_ID)));
        self::assertSame('2026-10-05', $exception->exceptionDate->format('Y-m-d'));
        self::assertSame(ScheduleExceptionType::CANCELLED, $exception->type);
        self::assertSame('Grève', $exception->reason);
        self::assertNull($exception->newTimeSlot);
        self::assertNull($exception->newRoom);
        self::assertNull($exception->newTeacherId);
        self::assertTrue($exception->createdBy->equals(UserId::fromString(self::CREATOR_ID)));
    }

    #[Test]
    public function modifierCreatesAModifiedExceptionWithNewValues(): void
    {
        $newTimeSlot = new TimeSlot('10:00', '11:00');
        $newTeacherId = UserId::fromString(self::TEACHER_ID);

        $exception = ScheduleException::modifier(
            tenantId: TenantId::fromString(self::TENANT_ID),
            slotId: ScheduleSlotId::fromString(self::SLOT_ID),
            exceptionDate: new DateTimeImmutable('2026-10-05'),
            newTimeSlot: $newTimeSlot,
            newRoom: 'Salle 301',
            newTeacherId: $newTeacherId,
            reason: 'Changement de salle',
            createdBy: UserId::fromString(self::CREATOR_ID),
            now: new DateTimeImmutable('2026-10-01 10:00:00'),
        );

        self::assertSame(ScheduleExceptionType::MODIFIED, $exception->type);
        self::assertNotNull($exception->newTimeSlot);
        self::assertSame('10:00', $exception->newTimeSlot->startTime);
        self::assertSame('11:00', $exception->newTimeSlot->endTime);
        self::assertSame('Salle 301', $exception->newRoom);
        self::assertTrue($exception->newTeacherId->equals($newTeacherId));
    }

    #[Test]
    public function modifierWithPartialOverridesKeepsNullsForUnchangedFields(): void
    {
        $exception = ScheduleException::modifier(
            tenantId: TenantId::fromString(self::TENANT_ID),
            slotId: ScheduleSlotId::fromString(self::SLOT_ID),
            exceptionDate: new DateTimeImmutable('2026-10-05'),
            newTimeSlot: null,
            newRoom: 'Salle 302',
            newTeacherId: null,
            reason: null,
            createdBy: UserId::fromString(self::CREATOR_ID),
            now: new DateTimeImmutable('2026-10-01 10:00:00'),
        );

        self::assertSame(ScheduleExceptionType::MODIFIED, $exception->type);
        self::assertNull($exception->newTimeSlot);
        self::assertSame('Salle 302', $exception->newRoom);
        self::assertNull($exception->newTeacherId);
        self::assertNull($exception->reason);
    }

    #[Test]
    public function isCancelledReturnsTrueForCancelledType(): void
    {
        $exception = ScheduleException::annuler(
            tenantId: TenantId::fromString(self::TENANT_ID),
            slotId: ScheduleSlotId::fromString(self::SLOT_ID),
            exceptionDate: new DateTimeImmutable('2026-10-05'),
            reason: null,
            createdBy: UserId::fromString(self::CREATOR_ID),
            now: new DateTimeImmutable('2026-10-01 10:00:00'),
        );

        self::assertTrue($exception->isCancelled());
        self::assertFalse($exception->isModified());
    }

    #[Test]
    public function isModifiedReturnsTrueForModifiedType(): void
    {
        $exception = ScheduleException::modifier(
            tenantId: TenantId::fromString(self::TENANT_ID),
            slotId: ScheduleSlotId::fromString(self::SLOT_ID),
            exceptionDate: new DateTimeImmutable('2026-10-05'),
            newTimeSlot: new TimeSlot('14:00', '15:00'),
            newRoom: null,
            newTeacherId: null,
            reason: null,
            createdBy: UserId::fromString(self::CREATOR_ID),
            now: new DateTimeImmutable('2026-10-01 10:00:00'),
        );

        self::assertTrue($exception->isModified());
        self::assertFalse($exception->isCancelled());
    }

    #[Test]
    public function reconstituteRestoresAllPropertiesWithoutEvents(): void
    {
        $id = ScheduleExceptionId::generate();
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $slotId = ScheduleSlotId::fromString(self::SLOT_ID);
        $newTimeSlot = new TimeSlot('11:00', '12:00');
        $newTeacherId = UserId::fromString(self::TEACHER_ID);
        $createdBy = UserId::fromString(self::CREATOR_ID);
        $createdAt = new DateTimeImmutable('2026-10-01 10:00:00');

        $exception = ScheduleException::reconstitute(
            id: $id,
            tenantId: $tenantId,
            slotId: $slotId,
            exceptionDate: new DateTimeImmutable('2026-10-05'),
            type: ScheduleExceptionType::MODIFIED,
            newTimeSlot: $newTimeSlot,
            newRoom: 'Salle 303',
            newTeacherId: $newTeacherId,
            reason: 'Interversion',
            createdBy: $createdBy,
            createdAt: $createdAt,
        );

        self::assertTrue($exception->id->equals($id));
        self::assertTrue($exception->tenantId->equals($tenantId));
        self::assertTrue($exception->slotId->equals($slotId));
        self::assertSame('2026-10-05', $exception->exceptionDate->format('Y-m-d'));
        self::assertSame(ScheduleExceptionType::MODIFIED, $exception->type);
        self::assertSame('11:00', $exception->newTimeSlot->startTime);
        self::assertSame('Salle 303', $exception->newRoom);
        self::assertTrue($exception->newTeacherId->equals($newTeacherId));
        self::assertSame('Interversion', $exception->reason);
        self::assertTrue($exception->createdBy->equals($createdBy));
        self::assertEquals($createdAt, $exception->createdAt);
    }
}
