<?php

declare(strict_types=1);

namespace App\Tests\Unit\Scolarite\Application\Command\CreateScheduleSlot;

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\Command\CreateScheduleSlot\CreateScheduleSlotCommand;
use App\Scolarite\Application\Command\CreateScheduleSlot\CreateScheduleSlotHandler;
use App\Scolarite\Application\Port\EnseignantAffectationChecker;
use App\Scolarite\Domain\Exception\EnseignantNonAffecteException;
use App\Scolarite\Domain\Model\Schedule\DayOfWeek;
use App\Scolarite\Domain\Model\Schedule\ScheduleSlot;
use App\Scolarite\Domain\Service\ScheduleConflictDetector;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryScheduleSlotRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class CreateScheduleSlotHandlerTest 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';

    private InMemoryScheduleSlotRepository $repository;
    private CreateScheduleSlotHandler $handler;

    protected function setUp(): void
    {
        $this->repository = new InMemoryScheduleSlotRepository();
        $clock = new class implements Clock {
            public function now(): DateTimeImmutable
            {
                return new DateTimeImmutable('2026-03-02 10:00:00');
            }
        };

        $this->handler = new CreateScheduleSlotHandler(
            $this->repository,
            new ScheduleConflictDetector($this->repository),
            $this->createAlwaysAssignedChecker(),
            $clock,
        );
    }

    #[Test]
    public function createsSlotSuccessfully(): void
    {
        $result = ($this->handler)($this->createCommand());

        /** @var ScheduleSlot $slot */
        $slot = $result['slot'];

        self::assertTrue($slot->classId->equals(ClassId::fromString(self::CLASS_ID)));
        self::assertSame(DayOfWeek::MONDAY, $slot->dayOfWeek);
        self::assertSame('08:00', $slot->timeSlot->startTime);
        self::assertSame('09:00', $slot->timeSlot->endTime);
        self::assertEmpty($result['conflicts']);
    }

    #[Test]
    public function savesSlotToRepository(): void
    {
        $result = ($this->handler)($this->createCommand());

        /** @var ScheduleSlot $slot */
        $slot = $result['slot'];

        $found = $this->repository->findById($slot->id, TenantId::fromString(self::TENANT_ID));

        self::assertNotNull($found);
        self::assertTrue($found->id->equals($slot->id));
    }

    #[Test]
    public function detectsConflictsWithoutSaving(): void
    {
        // Créer un slot existant
        ($this->handler)($this->createCommand());

        // Même enseignant, même créneau, classe différente
        $result = ($this->handler)(new CreateScheduleSlotCommand(
            tenantId: self::TENANT_ID,
            classId: '550e8400-e29b-41d4-a716-446655440021',
            subjectId: self::SUBJECT_ID,
            teacherId: self::TEACHER_ID,
            dayOfWeek: DayOfWeek::MONDAY->value,
            startTime: '08:30',
            endTime: '09:30',
            room: null,
        ));

        self::assertNotEmpty($result['conflicts']);
        // Le slot conflictuel ne devrait PAS être sauvegardé
        $allSlots = $this->repository->findByTeacher(
            UserId::fromString(self::TEACHER_ID),
            TenantId::fromString(self::TENANT_ID),
        );
        self::assertCount(1, $allSlots);
    }

    #[Test]
    public function forceSavesSlotWithConflicts(): void
    {
        ($this->handler)($this->createCommand());

        $result = ($this->handler)(new CreateScheduleSlotCommand(
            tenantId: self::TENANT_ID,
            classId: '550e8400-e29b-41d4-a716-446655440021',
            subjectId: self::SUBJECT_ID,
            teacherId: self::TEACHER_ID,
            dayOfWeek: DayOfWeek::MONDAY->value,
            startTime: '08:30',
            endTime: '09:30',
            room: null,
            forceConflicts: true,
        ));

        self::assertNotEmpty($result['conflicts']);
        // Malgré les conflits, le slot est sauvegardé
        $allSlots = $this->repository->findByTeacher(
            UserId::fromString(self::TEACHER_ID),
            TenantId::fromString(self::TENANT_ID),
        );
        self::assertCount(2, $allSlots);
    }

    #[Test]
    public function rejectsUnassignedTeacher(): void
    {
        $clock = new class implements Clock {
            public function now(): DateTimeImmutable
            {
                return new DateTimeImmutable('2026-03-02 10:00:00');
            }
        };

        $handler = new CreateScheduleSlotHandler(
            $this->repository,
            new ScheduleConflictDetector($this->repository),
            $this->createNeverAssignedChecker(),
            $clock,
        );

        $this->expectException(EnseignantNonAffecteException::class);
        ($handler)($this->createCommand());
    }

    private function createCommand(): CreateScheduleSlotCommand
    {
        return new CreateScheduleSlotCommand(
            tenantId: self::TENANT_ID,
            classId: self::CLASS_ID,
            subjectId: self::SUBJECT_ID,
            teacherId: self::TEACHER_ID,
            dayOfWeek: DayOfWeek::MONDAY->value,
            startTime: '08:00',
            endTime: '09:00',
            room: null,
        );
    }

    private function createAlwaysAssignedChecker(): EnseignantAffectationChecker
    {
        return new class implements EnseignantAffectationChecker {
            public function estAffecte(
                UserId $teacherId,
                ClassId $classId,
                SubjectId $subjectId,
                TenantId $tenantId,
            ): bool {
                return true;
            }
        };
    }

    private function createNeverAssignedChecker(): EnseignantAffectationChecker
    {
        return new class implements EnseignantAffectationChecker {
            public function estAffecte(
                UserId $teacherId,
                ClassId $classId,
                SubjectId $subjectId,
                TenantId $tenantId,
            ): bool {
                return false;
            }
        };
    }
}
