<?php

declare(strict_types=1);

namespace App\Tests\Integration\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 PHPUnit\Framework\Attributes\Large;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;

use function sprintf;

use Symfony\Component\HttpClient\HttpClient;

use function sys_get_temp_dir;
use function unlink;

/**
 * Vérifie que l'API data.education.gouv.fr répond toujours correctement
 * et que le format de réponse n'a pas changé.
 *
 * Ce test fait un appel réseau réel. Il échouera si :
 * - l'URL de l'API change
 * - le format de réponse (champs description, start_date, end_date, zones) change
 * - l'API est indisponible
 */
#[Large]
final class GouvFrCalendarApiTest extends TestCase
{
    private const string ACADEMIC_YEAR = '2024-2025';

    private string $tempDir;
    private JsonOfficialCalendarProvider $provider;

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

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

    protected function tearDown(): void
    {
        // Supprimer les fichiers générés
        $files = glob($this->tempDir . '/*.json');
        foreach ($files as $file) {
            unlink($file);
        }
        rmdir($this->tempDir);
    }

    #[Test]
    public function apiRetourneDesVacancesPourChaqueZone(): void
    {
        $vacationsA = $this->provider->vacancesParZone(SchoolZone::A, self::ACADEMIC_YEAR);
        $vacationsB = $this->provider->vacancesParZone(SchoolZone::B, self::ACADEMIC_YEAR);
        $vacationsC = $this->provider->vacancesParZone(SchoolZone::C, self::ACADEMIC_YEAR);

        self::assertNotEmpty($vacationsA, 'L\'API doit retourner des vacances pour la zone A');
        self::assertNotEmpty($vacationsB, 'L\'API doit retourner des vacances pour la zone B');
        self::assertNotEmpty($vacationsC, 'L\'API doit retourner des vacances pour la zone C');
    }

    #[Test]
    public function chaqueEntreeALesBonsChamps(): void
    {
        $entries = $this->provider->toutesEntreesOfficielles(SchoolZone::A, self::ACADEMIC_YEAR);

        foreach ($entries as $entry) {
            self::assertNotEmpty((string) $entry->id, 'Chaque entrée doit avoir un id');
            self::assertInstanceOf(CalendarEntryType::class, $entry->type);
            self::assertNotNull($entry->startDate, 'startDate ne doit pas être null');
            self::assertNotNull($entry->endDate, 'endDate ne doit pas être null');
            self::assertNotEmpty($entry->label, 'label ne doit pas être vide');
        }
    }

    #[Test]
    public function lesDatesDeVacancesSontCoherentes(): void
    {
        $vacations = $this->provider->vacancesParZone(SchoolZone::A, self::ACADEMIC_YEAR);

        foreach ($vacations as $vacation) {
            self::assertSame(CalendarEntryType::VACATION, $vacation->type);
            self::assertGreaterThanOrEqual(
                $vacation->startDate->format('Y-m-d'),
                $vacation->endDate->format('Y-m-d'),
                sprintf('La fin (%s) doit être >= au début (%s) pour "%s"',
                    $vacation->endDate->format('Y-m-d'),
                    $vacation->startDate->format('Y-m-d'),
                    $vacation->label,
                ),
            );
        }
    }

    #[Test]
    public function leFichierJsonEstCreeEnCache(): void
    {
        $expectedFile = $this->tempDir . '/official-holidays-' . self::ACADEMIC_YEAR . '.json';

        self::assertFileDoesNotExist($expectedFile);

        $this->provider->toutesEntreesOfficielles(SchoolZone::A, self::ACADEMIC_YEAR);

        self::assertFileExists($expectedFile);

        $content = json_decode(file_get_contents($expectedFile), true);
        self::assertArrayHasKey('holidays', $content);
        self::assertArrayHasKey('vacations', $content);
        self::assertArrayHasKey('A', $content['vacations']);
        self::assertArrayHasKey('B', $content['vacations']);
        self::assertArrayHasKey('C', $content['vacations']);
    }

    #[Test]
    public function leDeuxiemeAppelUtiliseLeCacheSansToucherLApi(): void
    {
        // Premier appel : fetch API + sauvegarde
        $first = $this->provider->toutesEntreesOfficielles(SchoolZone::A, self::ACADEMIC_YEAR);

        // Recréer un provider avec un HttpClient qui échoue systématiquement
        // Si le cache fonctionne, il ne touchera pas au HttpClient
        $cachedProvider = new JsonOfficialCalendarProvider(
            dataDirectory: $this->tempDir,
            httpClient: HttpClient::create(), // pas utilisé si le fichier existe
            holidaysCalculator: new FrenchPublicHolidaysCalculator(),
            logger: new NullLogger(),
        );

        $second = $cachedProvider->toutesEntreesOfficielles(SchoolZone::A, self::ACADEMIC_YEAR);

        self::assertCount(count($first), $second);
    }

    #[Test]
    public function vacancesToussaintEtNoelPresentes(): void
    {
        $vacations = $this->provider->vacancesParZone(SchoolZone::A, self::ACADEMIC_YEAR);
        $labels = array_map(static fn ($v) => $v->label, $vacations);

        self::assertNotEmpty(
            array_filter($labels, static fn (string $l) => str_contains(strtolower($l), 'toussaint')),
            'Les vacances de la Toussaint doivent être présentes',
        );
        self::assertNotEmpty(
            array_filter($labels, static fn (string $l) => str_contains(strtolower($l), 'noël') || str_contains(strtolower($l), 'noel')),
            'Les vacances de Noël doivent être présentes',
        );
    }
}
