<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Infrastructure\Service;

use App\Administration\Domain\Model\SchoolCalendar\CalendarEntryType;
use App\Administration\Domain\Model\SchoolCalendar\SchoolZone;
use App\Administration\Infrastructure\Service\FrenchPublicHolidaysCalculator;
use App\Administration\Infrastructure\Service\JsonOfficialCalendarProvider;

use function count;

use InvalidArgumentException;

use function json_encode;

use const JSON_THROW_ON_ERROR;

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

use function sys_get_temp_dir;

final class JsonOfficialCalendarProviderTest extends TestCase
{
    private string $tempDir;
    private JsonOfficialCalendarProvider $provider;

    protected function setUp(): void
    {
        $this->tempDir = sys_get_temp_dir() . '/classeo-calendar-test-' . uniqid();

        $this->provider = new JsonOfficialCalendarProvider(
            dataDirectory: $this->tempDir,
            httpClient: new MockHttpClient($this->mockApiResponse()),
            holidaysCalculator: new FrenchPublicHolidaysCalculator(),
            logger: new NullLogger(),
        );
    }

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

    #[Test]
    public function joursFeiesRetourneLesFeriesOfficiels(): void
    {
        $holidays = $this->provider->joursFeries('2024-2025');

        self::assertNotEmpty($holidays);

        foreach ($holidays as $holiday) {
            self::assertSame(CalendarEntryType::HOLIDAY, $holiday->type);
        }
    }

    #[Test]
    public function joursFeiesContientToussaint(): void
    {
        $holidays = $this->provider->joursFeries('2024-2025');

        $labels = array_map(static fn ($h) => $h->label, $holidays);

        self::assertContains('Toussaint', $labels);
    }

    #[Test]
    public function joursFeiesContientFeteNationale(): void
    {
        $holidays = $this->provider->joursFeries('2024-2025');

        $labels = array_map(static fn ($h) => $h->label, $holidays);

        self::assertContains('Fête nationale', $labels);
    }

    #[Test]
    public function joursFeiesContientAssomption(): void
    {
        $holidays = $this->provider->joursFeries('2024-2025');

        $labels = array_map(static fn ($h) => $h->label, $holidays);

        self::assertContains('Assomption', $labels);
    }

    #[Test]
    public function vacancesParZoneRetourneLesVacancesDeZoneA(): void
    {
        $vacations = $this->provider->vacancesParZone(SchoolZone::A, '2024-2025');

        self::assertNotEmpty($vacations);

        foreach ($vacations as $vacation) {
            self::assertSame(CalendarEntryType::VACATION, $vacation->type);
        }
    }

    #[Test]
    public function vacancesParZoneRetourneLesVacancesDeZoneB(): void
    {
        $vacations = $this->provider->vacancesParZone(SchoolZone::B, '2024-2025');

        self::assertNotEmpty($vacations);
    }

    #[Test]
    public function vacancesParZoneRetourneLesVacancesDeZoneC(): void
    {
        $vacations = $this->provider->vacancesParZone(SchoolZone::C, '2024-2025');

        self::assertNotEmpty($vacations);
    }

    #[Test]
    public function vacancesContiennentToussaintEtNoel(): void
    {
        $vacations = $this->provider->vacancesParZone(SchoolZone::A, '2024-2025');

        $labels = array_map(static fn ($v) => $v->label, $vacations);

        self::assertContains('Vacances de la Toussaint', $labels);
        self::assertContains('Vacances de Noël', $labels);
    }

    #[Test]
    public function hiverDiffereSelonLaZone(): void
    {
        $vacationsA = $this->provider->vacancesParZone(SchoolZone::A, '2024-2025');
        $vacationsC = $this->provider->vacancesParZone(SchoolZone::C, '2024-2025');

        $hiverA = null;
        $hiverC = null;

        foreach ($vacationsA as $v) {
            if (str_contains($v->label, 'hiver')) {
                $hiverA = $v;
            }
        }

        foreach ($vacationsC as $v) {
            if (str_contains($v->label, 'hiver')) {
                $hiverC = $v;
            }
        }

        self::assertNotNull($hiverA);
        self::assertNotNull($hiverC);
        self::assertNotSame(
            $hiverA->startDate->format('Y-m-d'),
            $hiverC->startDate->format('Y-m-d'),
        );
    }

    #[Test]
    public function toutesEntreesOfficiellesCombineJoursFeiesEtVacances(): void
    {
        $all = $this->provider->toutesEntreesOfficielles(SchoolZone::A, '2024-2025');
        $holidays = $this->provider->joursFeries('2024-2025');
        $vacations = $this->provider->vacancesParZone(SchoolZone::A, '2024-2025');

        self::assertCount(count($holidays) + count($vacations), $all);
    }

    #[Test]
    public function anneeScolaireInconnueLeveException(): void
    {
        $provider = new JsonOfficialCalendarProvider(
            dataDirectory: $this->tempDir,
            httpClient: new MockHttpClient(new MockResponse('', ['http_code' => 500])),
            holidaysCalculator: new FrenchPublicHolidaysCalculator(),
            logger: new NullLogger(),
        );

        $this->expectException(RuntimeException::class);

        $provider->joursFeries('2099-2100');
    }

    #[Test]
    public function chaqueEntreeAUnIdUnique(): void
    {
        $all = $this->provider->toutesEntreesOfficielles(SchoolZone::A, '2024-2025');

        $ids = array_map(static fn ($entry) => (string) $entry->id, $all);
        $uniqueIds = array_unique($ids);

        self::assertCount(count($all), $uniqueIds);
    }

    #[Test]
    public function itRejectsPathTraversalInAcademicYear(): void
    {
        $this->expectException(InvalidArgumentException::class);

        $this->provider->joursFeries('../../etc/passwd');
    }

    #[Test]
    public function itRejectsODataInjectionInAcademicYear(): void
    {
        $this->expectException(InvalidArgumentException::class);

        $this->provider->joursFeries('2024-2025"; DROP TABLE');
    }

    #[Test]
    public function cacheFileIsCreatedOnFirstAccess(): void
    {
        $this->provider->joursFeries('2024-2025');

        self::assertFileExists($this->tempDir . '/official-holidays-2024-2025.json');
    }

    #[Test]
    public function apiStartDateIsAdjustedByOneDay(): void
    {
        // The gouv.fr API returns start_date as "la veille à 23h UTC" (e.g., 2024-10-18T23:00:00+00:00
        // for a vacation starting 2024-10-19). The provider adds +1 day to start_date only.
        // end_date represents the last vacation day directly (no shift).
        $vacations = $this->provider->vacancesParZone(SchoolZone::A, '2024-2025');

        $toussaint = null;
        foreach ($vacations as $v) {
            if (str_contains($v->label, 'Toussaint')) {
                $toussaint = $v;
                break;
            }
        }

        self::assertNotNull($toussaint);
        // Mock API has start_date '2024-10-18T23:00:00+00:00' → adjusted to 2024-10-19
        self::assertSame('2024-10-19', $toussaint->startDate->format('Y-m-d'));
        // Mock API has end_date '2024-11-03T23:00:00+00:00' → kept as 2024-11-03 (last vacation day)
        self::assertSame('2024-11-03', $toussaint->endDate->format('Y-m-d'));
    }

    /**
     * Creates a MockResponse simulating the gouv.fr API for 2024-2025 vacations.
     */
    private function mockApiResponse(): MockResponse
    {
        $records = [
            // 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 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'],
            // 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 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'],
            // Zone C
            ['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'],
        ];

        $body = json_encode(['results' => $records], JSON_THROW_ON_ERROR);

        return new MockResponse($body, ['http_code' => 200]);
    }
}
