<?php

declare(strict_types=1);

namespace App\Tests\Unit\Scolarite\Domain\Service;

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\Model\Schedule\DayOfWeek;
use App\Scolarite\Domain\Model\Schedule\ScheduleSlot;
use App\Scolarite\Domain\Model\Schedule\TimeSlot;
use App\Scolarite\Domain\Service\ScheduleConflictDetector;
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 ScheduleConflictDetectorTest 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 ScheduleConflictDetector $detector;

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

    #[Test]
    public function detectsClassConflict(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $existingSlot = $this->createSlot(
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:00',
            endTime: '09:00',
        );
        $this->repository->save($existingSlot);

        // Même classe, même créneau, enseignant différent
        $newSlot = $this->createSlot(
            teacherId: '550e8400-e29b-41d4-a716-446655440011',
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:30',
            endTime: '09:30',
        );

        $conflicts = $this->detector->detectConflicts($newSlot, $tenantId);

        self::assertNotEmpty($conflicts);
        $types = array_map(static fn ($c) => $c->type, $conflicts);
        self::assertContains('class', $types);
    }

    #[Test]
    public function noClassConflictWhenDifferentClass(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $existingSlot = $this->createSlot(
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:00',
            endTime: '09:00',
        );
        $this->repository->save($existingSlot);

        $newSlot = $this->createSlot(
            teacherId: '550e8400-e29b-41d4-a716-446655440011',
            classId: '550e8400-e29b-41d4-a716-446655440021',
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:00',
            endTime: '09:00',
        );

        $conflicts = $this->detector->detectConflicts($newSlot, $tenantId);

        self::assertEmpty($conflicts);
    }

    #[Test]
    public function detectsTeacherConflict(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $existingSlot = $this->createSlot(
            teacherId: self::TEACHER_ID,
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:00',
            endTime: '09:00',
        );
        $this->repository->save($existingSlot);

        $newSlot = $this->createSlot(
            teacherId: self::TEACHER_ID,
            classId: '550e8400-e29b-41d4-a716-446655440021',
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:30',
            endTime: '09:30',
        );

        $conflicts = $this->detector->detectConflicts($newSlot, $tenantId);

        self::assertNotEmpty($conflicts);
        self::assertSame('teacher', $conflicts[0]->type);
    }

    #[Test]
    public function noConflictWhenDifferentDay(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $existingSlot = $this->createSlot(
            teacherId: self::TEACHER_ID,
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:00',
            endTime: '09:00',
        );
        $this->repository->save($existingSlot);

        $newSlot = $this->createSlot(
            teacherId: self::TEACHER_ID,
            classId: '550e8400-e29b-41d4-a716-446655440021',
            dayOfWeek: DayOfWeek::TUESDAY,
            startTime: '08:00',
            endTime: '09:00',
        );

        $conflicts = $this->detector->detectConflicts($newSlot, $tenantId);

        self::assertEmpty($conflicts);
    }

    #[Test]
    public function noConflictWhenAdjacentTimes(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $existingSlot = $this->createSlot(
            teacherId: self::TEACHER_ID,
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:00',
            endTime: '09:00',
        );
        $this->repository->save($existingSlot);

        $newSlot = $this->createSlot(
            teacherId: self::TEACHER_ID,
            classId: '550e8400-e29b-41d4-a716-446655440021',
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '09:00',
            endTime: '10:00',
        );

        $conflicts = $this->detector->detectConflicts($newSlot, $tenantId);

        self::assertEmpty($conflicts);
    }

    #[Test]
    public function detectsRoomConflict(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $existingSlot = $this->createSlot(
            teacherId: self::TEACHER_ID,
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:00',
            endTime: '09:00',
            room: 'Salle 101',
        );
        $this->repository->save($existingSlot);

        $newSlot = $this->createSlot(
            teacherId: '550e8400-e29b-41d4-a716-446655440011',
            classId: '550e8400-e29b-41d4-a716-446655440021',
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:30',
            endTime: '09:30',
            room: 'Salle 101',
        );

        $conflicts = $this->detector->detectConflicts($newSlot, $tenantId);

        self::assertNotEmpty($conflicts);
        self::assertSame('room', $conflicts[0]->type);
    }

    #[Test]
    public function noRoomConflictWhenNoRoom(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $existingSlot = $this->createSlot(
            teacherId: self::TEACHER_ID,
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:00',
            endTime: '09:00',
        );
        $this->repository->save($existingSlot);

        $newSlot = $this->createSlot(
            teacherId: '550e8400-e29b-41d4-a716-446655440011',
            classId: '550e8400-e29b-41d4-a716-446655440021',
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:30',
            endTime: '09:30',
        );

        $conflicts = $this->detector->detectConflicts($newSlot, $tenantId);

        self::assertEmpty($conflicts);
    }

    #[Test]
    public function excludesCurrentSlotWhenUpdating(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $existingSlot = $this->createSlot(
            teacherId: self::TEACHER_ID,
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:00',
            endTime: '09:00',
        );
        $this->repository->save($existingSlot);

        $conflicts = $this->detector->detectConflicts($existingSlot, $tenantId, $existingSlot->id);

        self::assertEmpty($conflicts);
    }

    #[Test]
    public function detectsBothTeacherAndRoomConflicts(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $existingSlot = $this->createSlot(
            teacherId: self::TEACHER_ID,
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:00',
            endTime: '09:00',
            room: 'Salle 101',
        );
        $this->repository->save($existingSlot);

        $newSlot = $this->createSlot(
            teacherId: self::TEACHER_ID,
            classId: '550e8400-e29b-41d4-a716-446655440021',
            dayOfWeek: DayOfWeek::MONDAY,
            startTime: '08:30',
            endTime: '09:30',
            room: 'Salle 101',
        );

        $conflicts = $this->detector->detectConflicts($newSlot, $tenantId);

        $types = array_map(static fn ($c) => $c->type, $conflicts);
        self::assertContains('teacher', $types);
        self::assertContains('room', $types);
    }

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