<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Application\Command\AddPedagogicalDay;

use App\Administration\Application\Command\AddPedagogicalDay\AddPedagogicalDayCommand;
use App\Administration\Application\Command\AddPedagogicalDay\AddPedagogicalDayHandler;
use App\Administration\Domain\Event\JourneePedagogiqueAjoutee;
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\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class AddPedagogicalDayHandlerTest 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 AddPedagogicalDayHandler $handler;

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

        $this->handler = new AddPedagogicalDayHandler(
            calendarRepository: $this->repository,
            clock: $clock,
        );
    }

    #[Test]
    public function itAddsPedagogicalDayToExistingCalendar(): void
    {
        $this->seedCalendar();

        $command = new AddPedagogicalDayCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: '2025-03-14',
            label: 'Formation enseignants',
        );

        $calendar = ($this->handler)($command);

        $entries = $calendar->entries();
        $pedagogicalDays = array_filter(
            $entries,
            static fn ($e) => $e->type === CalendarEntryType::PEDAGOGICAL_DAY,
        );

        self::assertCount(1, $pedagogicalDays);
        $day = array_values($pedagogicalDays)[0];
        self::assertSame('Formation enseignants', $day->label);
        self::assertSame('2025-03-14', $day->startDate->format('Y-m-d'));
    }

    #[Test]
    public function itCreatesNewCalendarIfNoneExists(): void
    {
        $command = new AddPedagogicalDayCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: '2025-03-14',
            label: 'Formation enseignants',
        );

        $calendar = ($this->handler)($command);

        self::assertCount(1, $calendar->entries());
    }

    #[Test]
    public function itRecordsJourneePedagogiqueAjouteeEvent(): void
    {
        $command = new AddPedagogicalDayCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: '2025-03-14',
            label: 'Formation enseignants',
        );

        $calendar = ($this->handler)($command);

        $events = $calendar->pullDomainEvents();
        self::assertCount(1, $events);
        self::assertInstanceOf(JourneePedagogiqueAjoutee::class, $events[0]);
        self::assertSame('Formation enseignants', $events[0]->label);
    }

    #[Test]
    public function itSavesCalendarWithPedagogicalDay(): void
    {
        $command = new AddPedagogicalDayCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: '2025-03-14',
            label: 'Formation',
            description: 'Journée de formation continue',
        );

        ($this->handler)($command);

        $saved = $this->repository->getByTenantAndYear(
            TenantId::fromString(self::TENANT_ID),
            AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
        );

        self::assertCount(1, $saved->entries());
        self::assertSame('Journée de formation continue', $saved->entries()[0]->description);
    }

    #[Test]
    public function itRejectsMalformedDate(): void
    {
        $command = new AddPedagogicalDayCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: 'not-a-date',
            label: 'Formation',
        );

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

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

    #[Test]
    public function itRejectsImpossibleCalendarDate(): void
    {
        $command = new AddPedagogicalDayCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            date: '2025-06-31',
            label: 'Formation',
        );

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

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

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

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