<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Application\Query\IsSchoolDay;

use App\Administration\Application\Query\IsSchoolDay\IsSchoolDayHandler;
use App\Administration\Application\Query\IsSchoolDay\IsSchoolDayQuery;
use App\Administration\Domain\Model\SchoolCalendar\CalendarEntry;
use App\Administration\Domain\Model\SchoolCalendar\CalendarEntryId;
use App\Administration\Domain\Model\SchoolCalendar\CalendarEntryType;
use App\Administration\Domain\Model\SchoolCalendar\SchoolCalendar;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Infrastructure\Persistence\InMemory\InMemorySchoolCalendarRepository;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class IsSchoolDayHandlerTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string ACADEMIC_YEAR_ID = '550e8400-e29b-41d4-a716-446655440010';

    private InMemorySchoolCalendarRepository $repository;
    private IsSchoolDayHandler $handler;

    protected function setUp(): void
    {
        $this->repository = new InMemorySchoolCalendarRepository();
        $this->handler = new IsSchoolDayHandler(
            calendarRepository: $this->repository,
        );
    }

    #[Test]
    public function weekdayWithNoCalendarIsSchoolDay(): void
    {
        $query = new IsSchoolDayQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: '2025-03-10', // Lundi
        );

        self::assertTrue(($this->handler)($query));
    }

    #[Test]
    public function saturdayIsNotSchoolDay(): void
    {
        $query = new IsSchoolDayQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: '2025-03-15', // Samedi
        );

        self::assertFalse(($this->handler)($query));
    }

    #[Test]
    public function sundayIsNotSchoolDay(): void
    {
        $query = new IsSchoolDayQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: '2025-03-16', // Dimanche
        );

        self::assertFalse(($this->handler)($query));
    }

    #[Test]
    public function holidayIsNotSchoolDay(): void
    {
        $this->seedCalendarWithHoliday('2025-05-01', 'Fête du travail');

        $query = new IsSchoolDayQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: '2025-05-01', // Jeudi férié
        );

        self::assertFalse(($this->handler)($query));
    }

    #[Test]
    public function vacationDayIsNotSchoolDay(): void
    {
        $this->seedCalendarWithVacation('2025-02-08', '2025-02-23', 'Vacances hiver');

        $query = new IsSchoolDayQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: '2025-02-10', // Lundi en vacances
        );

        self::assertFalse(($this->handler)($query));
    }

    #[Test]
    public function regularWeekdayWithCalendarIsSchoolDay(): void
    {
        $this->seedCalendarWithHoliday('2025-05-01', 'Fête du travail');

        $query = new IsSchoolDayQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: '2025-05-02', // Vendredi normal
        );

        self::assertTrue(($this->handler)($query));
    }

    #[Test]
    public function itRejectsMalformedDate(): void
    {
        $query = new IsSchoolDayQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: 'invalid-date',
        );

        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('La date doit être au format YYYY-MM-DD.');

        ($this->handler)($query);
    }

    #[Test]
    public function itRejectsImpossibleCalendarDate(): void
    {
        $query = new IsSchoolDayQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: '2025-02-30',
        );

        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage('La date n\'existe pas dans le calendrier.');

        ($this->handler)($query);
    }

    private function seedCalendarWithHoliday(string $date, string $label): void
    {
        $calendar = SchoolCalendar::initialiser(
            tenantId: TenantId::fromString(self::TENANT_ID),
            academicYearId: AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
        );
        $calendar->ajouterEntree(new CalendarEntry(
            id: CalendarEntryId::generate(),
            type: CalendarEntryType::HOLIDAY,
            startDate: new DateTimeImmutable($date),
            endDate: new DateTimeImmutable($date),
            label: $label,
        ));
        $this->repository->save($calendar);
    }

    private function seedCalendarWithVacation(string $start, string $end, string $label): void
    {
        $calendar = SchoolCalendar::initialiser(
            tenantId: TenantId::fromString(self::TENANT_ID),
            academicYearId: AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
        );
        $calendar->ajouterEntree(new CalendarEntry(
            id: CalendarEntryId::generate(),
            type: CalendarEntryType::VACATION,
            startDate: new DateTimeImmutable($start),
            endDate: new DateTimeImmutable($end),
            label: $label,
        ));
        $this->repository->save($calendar);
    }
}
