<?php

declare(strict_types=1);

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

use App\Administration\Application\Command\ConfigureCalendar\ConfigureCalendarCommand;
use App\Administration\Application\Command\ConfigureCalendar\ConfigureCalendarHandler;
use App\Administration\Domain\Model\SchoolCalendar\CalendarEntryType;
use App\Administration\Domain\Model\SchoolCalendar\SchoolZone;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Infrastructure\Persistence\InMemory\InMemorySchoolCalendarRepository;
use App\Administration\Infrastructure\Service\FrenchPublicHolidaysCalculator;
use App\Administration\Infrastructure\Service\JsonOfficialCalendarProvider;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;

use function json_encode;

use const JSON_THROW_ON_ERROR;

use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;

use function sys_get_temp_dir;

final class ConfigureCalendarHandlerTest 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 ConfigureCalendarHandler $handler;
    private string $tempDir;

    protected function setUp(): void
    {
        $this->tempDir = sys_get_temp_dir() . '/classeo-handler-test-' . uniqid();
        $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 ConfigureCalendarHandler(
            calendarRepository: $this->repository,
            calendarProvider: new JsonOfficialCalendarProvider(
                dataDirectory: $this->tempDir,
                httpClient: new MockHttpClient($this->mockApiResponse()),
                holidaysCalculator: new FrenchPublicHolidaysCalculator(),
                logger: new NullLogger(),
            ),
            clock: $clock,
        );
    }

    protected function tearDown(): void
    {
        $files = glob($this->tempDir . '/*');
        if ($files !== false) {
            foreach ($files as $file) {
                unlink($file);
            }
        }
        if (is_dir($this->tempDir)) {
            rmdir($this->tempDir);
        }
    }

    #[Test]
    public function itConfiguresCalendarWithZoneA(): void
    {
        $command = new ConfigureCalendarCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            zone: 'A',
            academicYear: '2024-2025',
        );

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

        self::assertSame(SchoolZone::A, $calendar->zone);
        self::assertNotEmpty($calendar->entries());
    }

    #[Test]
    public function itImportsHolidaysAndVacations(): void
    {
        $command = new ConfigureCalendarCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            zone: 'B',
            academicYear: '2024-2025',
        );

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

        $holidays = array_filter(
            $calendar->entries(),
            static fn ($e) => $e->type === CalendarEntryType::HOLIDAY,
        );
        $vacations = array_filter(
            $calendar->entries(),
            static fn ($e) => $e->type === CalendarEntryType::VACATION,
        );

        self::assertNotEmpty($holidays);
        self::assertNotEmpty($vacations);
    }

    #[Test]
    public function itSavesCalendarToRepository(): void
    {
        $command = new ConfigureCalendarCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            zone: 'C',
            academicYear: '2024-2025',
        );

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

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

        self::assertNotNull($saved);
        self::assertSame(SchoolZone::C, $saved->zone);
    }

    #[Test]
    public function itReconfiguresExistingCalendar(): void
    {
        $command = new ConfigureCalendarCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            zone: 'A',
            academicYear: '2024-2025',
        );

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

        $commandB = new ConfigureCalendarCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            zone: 'B',
            academicYear: '2024-2025',
        );

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

        self::assertSame(SchoolZone::B, $calendar->zone);
    }

    #[Test]
    public function itRecordsCalendrierConfigureEvent(): void
    {
        $command = new ConfigureCalendarCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            zone: 'A',
            academicYear: '2024-2025',
        );

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

        $events = $calendar->pullDomainEvents();
        self::assertNotEmpty($events);
    }

    private function mockApiResponse(): MockResponse
    {
        $records = [
            ['description' => 'Vacances de la Toussaint', 'start_date' => '2024-10-18T23:00:00+00:00', 'end_date' => '2024-11-03T23:00:00+00:00', 'zones' => 'Zone A'],
            ['description' => 'Vacances de Noël', 'start_date' => '2024-12-20T23:00:00+00:00', 'end_date' => '2025-01-05T23:00:00+00:00', 'zones' => 'Zone A'],
            ['description' => 'Vacances d\'hiver', 'start_date' => '2025-02-21T23:00:00+00:00', 'end_date' => '2025-03-09T23:00:00+00:00', 'zones' => 'Zone A'],
            ['description' => 'Vacances de printemps', 'start_date' => '2025-04-18T22:00:00+00:00', 'end_date' => '2025-05-04T22:00:00+00:00', 'zones' => 'Zone A'],
            ['description' => 'Vacances d\'été', 'start_date' => '2025-07-04T22:00:00+00:00', 'end_date' => '2025-08-31T22:00:00+00:00', 'zones' => 'Zone A'],
            ['description' => 'Vacances de la Toussaint', 'start_date' => '2024-10-18T23:00:00+00:00', 'end_date' => '2024-11-03T23:00:00+00:00', 'zones' => 'Zone B'],
            ['description' => 'Vacances de Noël', 'start_date' => '2024-12-20T23:00:00+00:00', 'end_date' => '2025-01-05T23:00:00+00:00', 'zones' => 'Zone B'],
            ['description' => 'Vacances d\'hiver', 'start_date' => '2025-02-07T23:00:00+00:00', 'end_date' => '2025-02-23T23:00:00+00:00', 'zones' => 'Zone B'],
            ['description' => 'Vacances de printemps', 'start_date' => '2025-04-04T22:00:00+00:00', 'end_date' => '2025-04-20T22:00:00+00:00', 'zones' => 'Zone B'],
            ['description' => 'Vacances d\'été', 'start_date' => '2025-07-04T22:00:00+00:00', 'end_date' => '2025-08-31T22:00:00+00:00', 'zones' => 'Zone B'],
            ['description' => 'Vacances de la Toussaint', 'start_date' => '2024-10-18T23:00:00+00:00', 'end_date' => '2024-11-03T23:00:00+00:00', 'zones' => 'Zone C'],
            ['description' => 'Vacances de Noël', 'start_date' => '2024-12-20T23:00:00+00:00', 'end_date' => '2025-01-05T23:00:00+00:00', 'zones' => 'Zone C'],
            ['description' => 'Vacances d\'hiver', 'start_date' => '2025-02-14T23:00:00+00:00', 'end_date' => '2025-03-02T23:00:00+00:00', 'zones' => 'Zone C'],
            ['description' => 'Vacances de printemps', 'start_date' => '2025-04-11T22:00:00+00:00', 'end_date' => '2025-04-27T22:00:00+00:00', 'zones' => 'Zone C'],
            ['description' => 'Vacances d\'été', 'start_date' => '2025-07-04T22:00:00+00:00', 'end_date' => '2025-08-31T22:00:00+00:00', 'zones' => 'Zone C'],
        ];

        return new MockResponse(json_encode(['results' => $records], JSON_THROW_ON_ERROR), ['http_code' => 200]);
    }
}
