<?php

declare(strict_types=1);

namespace App\Tests\Unit\Scolarite\Application\Query\GetScheduleSlots;

use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Application\Query\GetScheduleSlots\GetScheduleSlotsHandler;
use App\Scolarite\Application\Query\GetScheduleSlots\GetScheduleSlotsQuery;
use App\Scolarite\Domain\Model\Schedule\DayOfWeek;
use App\Scolarite\Domain\Model\Schedule\ScheduleSlot;
use App\Scolarite\Domain\Model\Schedule\TimeSlot;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryScheduleSlotRepository;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class GetScheduleSlotsHandlerTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string CLASS_A = '550e8400-e29b-41d4-a716-446655440020';
    private const string CLASS_B = '550e8400-e29b-41d4-a716-446655440021';
    private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440030';
    private const string TEACHER_A = '550e8400-e29b-41d4-a716-446655440010';
    private const string TEACHER_B = '550e8400-e29b-41d4-a716-446655440011';

    private InMemoryScheduleSlotRepository $repository;
    private GetScheduleSlotsHandler $handler;

    protected function setUp(): void
    {
        $this->repository = new InMemoryScheduleSlotRepository();
        $this->handler = new GetScheduleSlotsHandler($this->repository);
    }

    #[Test]
    public function returnsSlotsFilteredByClass(): void
    {
        $this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);
        $this->createSlot(classId: self::CLASS_B, teacherId: self::TEACHER_A);

        $result = ($this->handler)(new GetScheduleSlotsQuery(
            tenantId: self::TENANT_ID,
            classId: self::CLASS_A,
        ));

        self::assertCount(1, $result);
        self::assertSame(self::CLASS_A, $result[0]->classId);
    }

    #[Test]
    public function returnsSlotsFilteredByTeacher(): void
    {
        $this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);
        $this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_B, day: DayOfWeek::TUESDAY);

        $result = ($this->handler)(new GetScheduleSlotsQuery(
            tenantId: self::TENANT_ID,
            teacherId: self::TEACHER_A,
        ));

        self::assertCount(1, $result);
        self::assertSame(self::TEACHER_A, $result[0]->teacherId);
    }

    #[Test]
    public function returnsSlotsFilteredByClassAndTeacher(): void
    {
        // Classe A, enseignant A
        $this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);
        // Classe A, enseignant B
        $this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_B, day: DayOfWeek::TUESDAY);
        // Classe B, enseignant A
        $this->createSlot(classId: self::CLASS_B, teacherId: self::TEACHER_A, day: DayOfWeek::WEDNESDAY);

        $result = ($this->handler)(new GetScheduleSlotsQuery(
            tenantId: self::TENANT_ID,
            classId: self::CLASS_A,
            teacherId: self::TEACHER_A,
        ));

        self::assertCount(1, $result);
        self::assertSame(self::CLASS_A, $result[0]->classId);
        self::assertSame(self::TEACHER_A, $result[0]->teacherId);
    }

    #[Test]
    public function returnsEmptyWhenClassAndTeacherDontMatch(): void
    {
        $this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);

        $result = ($this->handler)(new GetScheduleSlotsQuery(
            tenantId: self::TENANT_ID,
            classId: self::CLASS_A,
            teacherId: self::TEACHER_B,
        ));

        self::assertCount(0, $result);
    }

    #[Test]
    public function returnsEmptyWhenNoFilters(): void
    {
        $this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);

        $result = ($this->handler)(new GetScheduleSlotsQuery(
            tenantId: self::TENANT_ID,
        ));

        self::assertCount(0, $result);
    }

    #[Test]
    public function returnsEmptyForInvalidClassIdUuid(): void
    {
        $this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);

        $result = ($this->handler)(new GetScheduleSlotsQuery(
            tenantId: self::TENANT_ID,
            classId: 'not-a-valid-uuid',
        ));

        self::assertCount(0, $result);
    }

    #[Test]
    public function returnsEmptyForInvalidTeacherIdUuid(): void
    {
        $this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);

        $result = ($this->handler)(new GetScheduleSlotsQuery(
            tenantId: self::TENANT_ID,
            teacherId: 'invalid',
        ));

        self::assertCount(0, $result);
    }

    #[Test]
    public function teacherFilterReturnsAllClassesForTeacher(): void
    {
        $this->createSlot(classId: self::CLASS_A, teacherId: self::TEACHER_A);
        $this->createSlot(classId: self::CLASS_B, teacherId: self::TEACHER_A, day: DayOfWeek::TUESDAY);

        $result = ($this->handler)(new GetScheduleSlotsQuery(
            tenantId: self::TENANT_ID,
            teacherId: self::TEACHER_A,
        ));

        self::assertCount(2, $result);
    }

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

        return $slot;
    }
}
