<?php

declare(strict_types=1);

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

use App\Administration\Application\Query\ValidateHomeworkDueDate\ValidateHomeworkDueDateHandler;
use App\Administration\Application\Query\ValidateHomeworkDueDate\ValidateHomeworkDueDateQuery;
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 PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class ValidateHomeworkDueDateHandlerTest 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 ValidateHomeworkDueDateHandler $handler;

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

    #[Test]
    public function weekdayWithNoCalendarIsValid(): void
    {
        $result = ($this->handler)(new ValidateHomeworkDueDateQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            dueDate: '2025-03-10', // Lundi
        ));

        self::assertTrue($result->valid);
        self::assertNull($result->reason);
        self::assertSame([], $result->warnings);
    }

    #[Test]
    public function weekendIsInvalid(): void
    {
        $result = ($this->handler)(new ValidateHomeworkDueDateQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            dueDate: '2025-03-15', // Samedi
        ));

        self::assertFalse($result->valid);
        self::assertStringContainsString('weekend', $result->reason);
    }

    #[Test]
    public function holidayIsInvalid(): void
    {
        $this->seedCalendarWithEntries(
            new CalendarEntry(
                id: CalendarEntryId::generate(),
                type: CalendarEntryType::HOLIDAY,
                startDate: new DateTimeImmutable('2025-05-01'),
                endDate: new DateTimeImmutable('2025-05-01'),
                label: 'Fête du travail',
            ),
        );

        $result = ($this->handler)(new ValidateHomeworkDueDateQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            dueDate: '2025-05-01',
        ));

        self::assertFalse($result->valid);
        self::assertStringContainsString('férié', $result->reason);
    }

    #[Test]
    public function vacationDayIsInvalid(): void
    {
        $this->seedCalendarWithEntries(
            new CalendarEntry(
                id: CalendarEntryId::generate(),
                type: CalendarEntryType::VACATION,
                startDate: new DateTimeImmutable('2025-02-08'),
                endDate: new DateTimeImmutable('2025-02-23'),
                label: 'Vacances hiver',
            ),
        );

        $result = ($this->handler)(new ValidateHomeworkDueDateQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            dueDate: '2025-02-10', // Lundi en vacances
        ));

        self::assertFalse($result->valid);
        self::assertStringContainsString('vacances', $result->reason);
    }

    #[Test]
    public function returnDayFromVacationIsValidWithWarning(): void
    {
        $this->seedCalendarWithEntries(
            new CalendarEntry(
                id: CalendarEntryId::generate(),
                type: CalendarEntryType::VACATION,
                startDate: new DateTimeImmutable('2025-02-08'),
                endDate: new DateTimeImmutable('2025-02-23'),
                label: 'Vacances hiver',
            ),
        );

        $result = ($this->handler)(new ValidateHomeworkDueDateQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            dueDate: '2025-02-24', // Lundi retour de vacances
        ));

        self::assertTrue($result->valid);
        self::assertCount(1, $result->warnings);
        self::assertStringContainsString('retour de vacances', $result->warnings[0]);
    }

    #[Test]
    public function normalSchoolDayIsValid(): void
    {
        $this->seedCalendarWithEntries(
            new CalendarEntry(
                id: CalendarEntryId::generate(),
                type: CalendarEntryType::HOLIDAY,
                startDate: new DateTimeImmutable('2025-05-01'),
                endDate: new DateTimeImmutable('2025-05-01'),
                label: 'Fête du travail',
            ),
        );

        $result = ($this->handler)(new ValidateHomeworkDueDateQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            dueDate: '2025-05-02', // Vendredi normal
        ));

        self::assertTrue($result->valid);
        self::assertSame([], $result->warnings);
    }

    #[Test]
    public function malformedDateIsInvalid(): void
    {
        $result = ($this->handler)(new ValidateHomeworkDueDateQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            dueDate: 'not-a-date',
        ));

        self::assertFalse($result->valid);
        self::assertStringContainsString('YYYY-MM-DD', $result->reason);
    }

    #[Test]
    public function impossibleCalendarDateIsInvalid(): void
    {
        $result = ($this->handler)(new ValidateHomeworkDueDateQuery(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            dueDate: '2025-02-30',
        ));

        self::assertFalse($result->valid);
        self::assertStringContainsString('n\'existe pas', $result->reason);
    }

    private function seedCalendarWithEntries(CalendarEntry ...$entries): void
    {
        $calendar = SchoolCalendar::initialiser(
            tenantId: TenantId::fromString(self::TENANT_ID),
            academicYearId: AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
        );

        foreach ($entries as $entry) {
            $calendar->ajouterEntree($entry);
        }

        $this->repository->save($calendar);
    }
}
