<?php

declare(strict_types=1);

namespace App\Tests\Functional\Administration\Api;

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
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\SchoolCalendar\SchoolCalendarRepository;
use App\Administration\Domain\Model\SchoolCalendar\SchoolZone;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Infrastructure\Security\SecurityUser;
use App\Shared\Domain\Tenant\TenantId;

use function count;

use DateTimeImmutable;
use Doctrine\DBAL\Connection;
use PHPUnit\Framework\Attributes\Test;

/**
 * Tests for school calendar API endpoints.
 *
 * @see Story 2.11 - Configuration Calendrier Scolaire
 */
final class CalendarEndpointsTest extends ApiTestCase
{
    protected static ?bool $alwaysBootKernel = true;

    private const string TENANT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
    private const string ACADEMIC_YEAR_ID = '11111111-1111-1111-1111-111111111111';
    private const string USER_ID = '550e8400-e29b-41d4-a716-446655440000';
    private const string BASE_URL = 'http://ecole-alpha.classeo.local/api/academic-years/11111111-1111-1111-1111-111111111111';

    /** URL with symbolic 'current' ID — required for configure/import (year resolution). */
    private const string CONFIGURE_URL = 'http://ecole-alpha.classeo.local/api/academic-years/current';

    protected function tearDown(): void
    {
        $container = static::getContainer();
        /** @var Connection $connection */
        $connection = $container->get(Connection::class);
        $connection->executeStatement(
            'DELETE FROM school_calendar_entries WHERE tenant_id = :tenant_id',
            ['tenant_id' => self::TENANT_ID],
        );

        parent::tearDown();
    }

    // =========================================================================
    // Security - Without tenant
    // =========================================================================

    #[Test]
    public function getCalendarReturns404WithoutTenant(): void
    {
        $client = static::createClient();

        $client->request('GET', '/api/academic-years/current/calendar', [
            'headers' => [
                'Host' => 'localhost',
                'Accept' => 'application/json',
            ],
        ]);

        self::assertResponseStatusCodeSame(404);
    }

    #[Test]
    public function configureCalendarReturns404WithoutTenant(): void
    {
        $client = static::createClient();

        $client->request('PUT', '/api/academic-years/current/calendar', [
            'headers' => [
                'Host' => 'localhost',
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['zone' => 'A'],
        ]);

        self::assertResponseStatusCodeSame(404);
    }

    #[Test]
    public function importOfficialHolidaysReturns404WithoutTenant(): void
    {
        $client = static::createClient();

        $client->request('POST', '/api/academic-years/current/calendar/import-official', [
            'headers' => [
                'Host' => 'localhost',
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['importZone' => 'A'],
        ]);

        self::assertResponseStatusCodeSame(404);
    }

    #[Test]
    public function addPedagogicalDayReturns404WithoutTenant(): void
    {
        $client = static::createClient();

        $client->request('POST', '/api/academic-years/current/calendar/pedagogical-day', [
            'headers' => [
                'Host' => 'localhost',
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['date' => '2025-03-14', 'label' => 'Formation', 'description' => 'Formation continue'],
        ]);

        self::assertResponseStatusCodeSame(404);
    }

    #[Test]
    public function isSchoolDayReturns404WithoutTenant(): void
    {
        $client = static::createClient();

        $client->request('GET', '/api/academic-years/current/calendar/is-school-day/2025-03-14', [
            'headers' => [
                'Host' => 'localhost',
                'Accept' => 'application/json',
            ],
        ]);

        self::assertResponseStatusCodeSame(404);
    }

    // =========================================================================
    // Security - Without authentication (with tenant)
    // =========================================================================

    #[Test]
    public function getCalendarReturns401WithoutAuthentication(): void
    {
        $client = static::createClient();

        $client->request('GET', 'http://ecole-alpha.classeo.local/api/academic-years/current/calendar', [
            'headers' => [
                'Accept' => 'application/json',
            ],
        ]);

        self::assertResponseStatusCodeSame(401);
    }

    #[Test]
    public function configureCalendarReturns401WithoutAuthentication(): void
    {
        $client = static::createClient();

        $client->request('PUT', 'http://ecole-alpha.classeo.local/api/academic-years/current/calendar', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['zone' => 'A'],
        ]);

        self::assertResponseStatusCodeSame(401);
    }

    #[Test]
    public function importOfficialHolidaysReturns401WithoutAuthentication(): void
    {
        $client = static::createClient();

        $client->request('POST', 'http://ecole-alpha.classeo.local/api/academic-years/current/calendar/import-official', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['importZone' => 'A'],
        ]);

        self::assertResponseStatusCodeSame(401);
    }

    #[Test]
    public function addPedagogicalDayReturns401WithoutAuthentication(): void
    {
        $client = static::createClient();

        $client->request('POST', 'http://ecole-alpha.classeo.local/api/academic-years/current/calendar/pedagogical-day', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['date' => '2025-03-14', 'label' => 'Formation', 'description' => 'Formation continue'],
        ]);

        self::assertResponseStatusCodeSame(401);
    }

    #[Test]
    public function isSchoolDayReturns401WithoutAuthentication(): void
    {
        $client = static::createClient();

        $client->request('GET', 'http://ecole-alpha.classeo.local/api/academic-years/current/calendar/is-school-day/2025-03-14', [
            'headers' => [
                'Accept' => 'application/json',
            ],
        ]);

        self::assertResponseStatusCodeSame(401);
    }

    // =========================================================================
    // Special identifiers - 'current'
    // =========================================================================

    #[Test]
    public function getCalendarAcceptsCurrentIdentifier(): void
    {
        $client = static::createClient();

        $client->request('GET', 'http://ecole-alpha.classeo.local/api/academic-years/current/calendar', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        // 401 (no auth) not 404 (invalid id) — proves 'current' is accepted
        self::assertResponseStatusCodeSame(401);
    }

    #[Test]
    public function configureCalendarAcceptsCurrentIdentifier(): void
    {
        $client = static::createClient();

        $client->request('PUT', 'http://ecole-alpha.classeo.local/api/academic-years/current/calendar', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['zone' => 'A'],
        ]);

        // 401 (no auth) not 404 (invalid id) — proves 'current' is accepted
        self::assertResponseStatusCodeSame(401);
    }

    #[Test]
    public function importOfficialHolidaysAcceptsCurrentIdentifier(): void
    {
        $client = static::createClient();

        $client->request('POST', 'http://ecole-alpha.classeo.local/api/academic-years/current/calendar/import-official', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['importZone' => 'A'],
        ]);

        // 401 (no auth) not 404 (invalid id) — proves 'current' is accepted
        self::assertResponseStatusCodeSame(401);
    }

    #[Test]
    public function addPedagogicalDayAcceptsCurrentIdentifier(): void
    {
        $client = static::createClient();

        $client->request('POST', 'http://ecole-alpha.classeo.local/api/academic-years/current/calendar/pedagogical-day', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['date' => '2025-03-14', 'label' => 'Formation', 'description' => 'Formation continue'],
        ]);

        // 401 (no auth) not 404 (invalid id) — proves 'current' is accepted
        self::assertResponseStatusCodeSame(401);
    }

    #[Test]
    public function isSchoolDayAcceptsCurrentIdentifier(): void
    {
        $client = static::createClient();

        $client->request('GET', 'http://ecole-alpha.classeo.local/api/academic-years/current/calendar/is-school-day/2025-03-14', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        // 401 (no auth) not 404 (invalid id) — proves 'current' is accepted
        self::assertResponseStatusCodeSame(401);
    }

    // =========================================================================
    // AC3 (P0) - is-school-day with data
    // =========================================================================

    #[Test]
    public function isSchoolDayReturnsFalseForHoliday(): void
    {
        $this->persistCalendar([
            new CalendarEntry(
                id: CalendarEntryId::generate(),
                type: CalendarEntryType::HOLIDAY,
                startDate: new DateTimeImmutable('2025-12-25'),
                endDate: new DateTimeImmutable('2025-12-25'),
                label: 'Noël',
            ),
        ]);

        $client = $this->createAuthenticatedClient(['ROLE_ADMIN']);
        $response = $client->request('GET', self::BASE_URL . '/calendar/is-school-day/2025-12-25', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();
        $data = $response->toArray();
        self::assertFalse($data['isSchoolDay']);
    }

    #[Test]
    public function isSchoolDayReturnsFalseForVacationDay(): void
    {
        $this->persistCalendar([
            new CalendarEntry(
                id: CalendarEntryId::generate(),
                type: CalendarEntryType::VACATION,
                startDate: new DateTimeImmutable('2025-02-15'),
                endDate: new DateTimeImmutable('2025-03-02'),
                label: 'Vacances d\'hiver',
            ),
        ]);

        $client = $this->createAuthenticatedClient(['ROLE_ADMIN']);
        $response = $client->request('GET', self::BASE_URL . '/calendar/is-school-day/2025-02-20', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();
        $data = $response->toArray();
        self::assertFalse($data['isSchoolDay']);
    }

    #[Test]
    public function isSchoolDayReturnsFalseForPedagogicalDay(): void
    {
        $this->persistCalendar([
            new CalendarEntry(
                id: CalendarEntryId::generate(),
                type: CalendarEntryType::PEDAGOGICAL_DAY,
                startDate: new DateTimeImmutable('2025-03-14'),
                endDate: new DateTimeImmutable('2025-03-14'),
                label: 'Formation continue',
            ),
        ]);

        $client = $this->createAuthenticatedClient(['ROLE_ADMIN']);
        $response = $client->request('GET', self::BASE_URL . '/calendar/is-school-day/2025-03-14', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();
        $data = $response->toArray();
        self::assertFalse($data['isSchoolDay']);
    }

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

        $client = $this->createAuthenticatedClient(['ROLE_ADMIN']);
        // 2025-03-10 is a Monday
        $response = $client->request('GET', self::BASE_URL . '/calendar/is-school-day/2025-03-10', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();
        $data = $response->toArray();
        self::assertTrue($data['isSchoolDay']);
    }

    #[Test]
    public function isSchoolDayReturns403ForParent(): void
    {
        $client = $this->createAuthenticatedClient(['ROLE_PARENT']);

        $client->request('GET', self::BASE_URL . '/calendar/is-school-day/2025-03-10', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseStatusCodeSame(403);
    }

    // =========================================================================
    // AC1 - GET calendar with data
    // =========================================================================

    #[Test]
    public function getCalendarReturnsDataForAdmin(): void
    {
        $this->persistCalendar(
            [
                new CalendarEntry(
                    id: CalendarEntryId::generate(),
                    type: CalendarEntryType::HOLIDAY,
                    startDate: new DateTimeImmutable('2025-12-25'),
                    endDate: new DateTimeImmutable('2025-12-25'),
                    label: 'Noël',
                ),
            ],
            SchoolZone::A,
        );

        $client = $this->createAuthenticatedClient(['ROLE_ADMIN']);
        $response = $client->request('GET', self::BASE_URL . '/calendar', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();
        $data = $response->toArray();
        self::assertSame('A', $data['zone']);
        self::assertNotEmpty($data['entries']);
    }

    #[Test]
    public function getCalendarReturns200ForProf(): void
    {
        $this->persistCalendar(
            [
                new CalendarEntry(
                    id: CalendarEntryId::generate(),
                    type: CalendarEntryType::HOLIDAY,
                    startDate: new DateTimeImmutable('2025-12-25'),
                    endDate: new DateTimeImmutable('2025-12-25'),
                    label: 'Noël',
                ),
            ],
            SchoolZone::A,
        );

        $client = $this->createAuthenticatedClient(['ROLE_PROF']);

        $client->request('GET', self::BASE_URL . '/calendar', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseIsSuccessful();
    }

    #[Test]
    public function getCalendarReturns403ForParent(): void
    {
        $client = $this->createAuthenticatedClient(['ROLE_PARENT']);

        $client->request('GET', self::BASE_URL . '/calendar', [
            'headers' => ['Accept' => 'application/json'],
        ]);

        self::assertResponseStatusCodeSame(403);
    }

    // =========================================================================
    // AC2 - Configure + Import
    // =========================================================================

    #[Test]
    public function configureCalendarReturns200ForAdmin(): void
    {
        $client = $this->createAuthenticatedClient(['ROLE_ADMIN']);
        $response = $client->request('PUT', self::CONFIGURE_URL . '/calendar', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['zone' => 'A'],
        ]);

        self::assertResponseIsSuccessful();
        $data = $response->toArray();
        self::assertSame('A', $data['zone']);
    }

    #[Test]
    public function configureCalendarReturns403ForProf(): void
    {
        $client = $this->createAuthenticatedClient(['ROLE_PROF']);

        $client->request('PUT', self::BASE_URL . '/calendar', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['zone' => 'A'],
        ]);

        self::assertResponseStatusCodeSame(403);
    }

    #[Test]
    public function importOfficialHolidaysReturns200ForAdmin(): void
    {
        $client = $this->createAuthenticatedClient(['ROLE_ADMIN']);
        $response = $client->request('POST', self::CONFIGURE_URL . '/calendar/import-official', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['importZone' => 'A'],
        ]);

        self::assertResponseIsSuccessful();
        $data = $response->toArray();
        self::assertNotEmpty($data['entries']);
    }

    #[Test]
    public function importOfficialHolidaysReturns403ForProf(): void
    {
        $client = $this->createAuthenticatedClient(['ROLE_PROF']);

        $client->request('POST', self::BASE_URL . '/calendar/import-official', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['importZone' => 'A'],
        ]);

        self::assertResponseStatusCodeSame(403);
    }

    // =========================================================================
    // AC2 - Ajustement post-import
    // =========================================================================

    #[Test]
    public function reconfigureCalendarChangesZoneAndEntries(): void
    {
        // Configure zone A
        $clientA = $this->createAuthenticatedClient(['ROLE_ADMIN']);
        $responseA = $clientA->request('PUT', self::CONFIGURE_URL . '/calendar', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['zone' => 'A'],
        ]);

        self::assertResponseIsSuccessful();
        $dataA = $responseA->toArray();
        self::assertSame('A', $dataA['zone']);
        self::assertNotEmpty($dataA['entries']);

        // Reconfigure zone B (new client — kernel reboots between requests)
        $clientB = $this->createAuthenticatedClient(['ROLE_ADMIN']);
        $responseB = $clientB->request('PUT', self::CONFIGURE_URL . '/calendar', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['zone' => 'B'],
        ]);

        self::assertResponseIsSuccessful();
        $dataB = $responseB->toArray();
        self::assertSame('B', $dataB['zone']);
        self::assertNotEmpty($dataB['entries']);
    }

    #[Test]
    public function addPedagogicalDayPreservesImportedEntries(): void
    {
        // Import zone A (uses 'current' — required for year resolution)
        $clientImport = $this->createAuthenticatedClient(['ROLE_ADMIN']);
        $responseImport = $clientImport->request('PUT', self::CONFIGURE_URL . '/calendar', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => ['zone' => 'A'],
        ]);

        self::assertResponseIsSuccessful();
        $importedCount = count($responseImport->toArray()['entries']);

        // Add pedagogical day on top (same 'current' year — new client, kernel reboots)
        $clientAdd = $this->createAuthenticatedClient(['ROLE_ADMIN']);
        $responseAdd = $clientAdd->request('POST', self::CONFIGURE_URL . '/calendar/pedagogical-day', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => [
                'date' => '2025-03-14',
                'label' => 'Formation continue',
                'description' => 'Formation pédagogies actives',
            ],
        ]);

        self::assertResponseIsSuccessful();
        $dataAfterAdd = $responseAdd->toArray();

        // Imported entries preserved + new pedagogical day added
        self::assertCount($importedCount + 1, $dataAfterAdd['entries']);

        $types = array_column($dataAfterAdd['entries'], 'type');
        self::assertContains('pedagogical', $types);
    }

    // =========================================================================
    // AC5 - Journée pédagogique
    // =========================================================================

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

        $client = $this->createAuthenticatedClient(['ROLE_ADMIN']);
        $response = $client->request('POST', self::BASE_URL . '/calendar/pedagogical-day', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => [
                'date' => '2025-03-14',
                'label' => 'Formation continue',
                'description' => 'Formation sur les nouvelles pédagogies',
            ],
        ]);

        self::assertResponseIsSuccessful();
        $data = $response->toArray();
        $types = array_column($data['entries'], 'type');
        self::assertContains('pedagogical', $types);
    }

    #[Test]
    public function addPedagogicalDayReturns403ForProf(): void
    {
        $client = $this->createAuthenticatedClient(['ROLE_PROF']);

        $client->request('POST', self::BASE_URL . '/calendar/pedagogical-day', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => [
                'date' => '2025-03-14',
                'label' => 'Formation continue',
            ],
        ]);

        self::assertResponseStatusCodeSame(403);
    }

    #[Test]
    public function addPedagogicalDayReturns403ForEleve(): void
    {
        $client = $this->createAuthenticatedClient(['ROLE_ELEVE']);

        $client->request('POST', self::BASE_URL . '/calendar/pedagogical-day', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => [
                'date' => '2025-03-14',
                'label' => 'Formation continue',
            ],
        ]);

        self::assertResponseStatusCodeSame(403);
    }

    // =========================================================================
    // Validation - Bad Request
    // =========================================================================

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

        $client = $this->createAuthenticatedClient(['ROLE_ADMIN']);

        $client->request('POST', self::BASE_URL . '/calendar/pedagogical-day', [
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
            ],
            'json' => [
                'date' => '2025-03-14',
                'label' => '   ',
                'description' => 'Label is only whitespace',
            ],
        ]);

        self::assertResponseStatusCodeSame(400);
    }

    // =========================================================================
    // Helpers
    // =========================================================================

    private function createAuthenticatedClient(array $roles): \ApiPlatform\Symfony\Bundle\Test\Client
    {
        $client = static::createClient();

        $user = new SecurityUser(
            userId: UserId::fromString(self::USER_ID),
            email: 'test@classeo.local',
            hashedPassword: '',
            tenantId: TenantId::fromString(self::TENANT_ID),
            roles: $roles,
        );

        $client->loginUser($user, 'api');

        return $client;
    }

    private function persistCalendar(array $entries = [], ?SchoolZone $zone = null): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $academicYearId = AcademicYearId::fromString(self::ACADEMIC_YEAR_ID);

        $calendar = SchoolCalendar::initialiser($tenantId, $academicYearId);

        if ($zone !== null) {
            $calendar->configurerZone($zone);
        }

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

        /** @var SchoolCalendarRepository $repository */
        $repository = static::getContainer()->get(SchoolCalendarRepository::class);
        $repository->save($calendar);
    }
}
