<?php

declare(strict_types=1);

namespace App\Tests\Module\Sites\Api;

use App\Module\Sites\Domain\Entity\Site;
use App\Tests\Module\Core\Api\AbstractApiTestCase;

/**
 * Tests fonctionnels CRUD /api/sites avec matrices RBAC.
 *
 * Strategie : les 3 sites fixtures (Chatellerault, Saint-Jean, Pommevic)
 * sont presents a chaque test. On nettoie les sites crees par les tests
 * via un prefixe `Test-` en setUp + tearDown.
 *
 * @internal
 */
final class SiteApiTest extends AbstractApiTestCase
{
    private const TEST_NAME_PREFIX = 'Test-';

    protected function setUp(): void
    {
        parent::setUp();
        $this->cleanupTestSites();
    }

    protected function tearDown(): void
    {
        $this->cleanupTestSites();
        parent::tearDown();
    }

    public function testAdminCanListSites(): void
    {
        $client   = $this->authenticatedClient('admin', 'admin');
        $response = $client->request('GET', '/api/sites');

        self::assertResponseIsSuccessful();
        $data = $response->toArray();
        self::assertGreaterThanOrEqual(3, $data['totalItems']);
    }

    public function testUserWithSitesViewCanListSites(): void
    {
        $this->skipIfSitesModuleDisabled();
        $credentials = $this->createUserWithPermission('sites.view');
        $client      = $this->authenticatedClient($credentials['username'], $credentials['password']);
        $client->request('GET', '/api/sites');

        self::assertResponseIsSuccessful();
    }

    public function testUserWithoutPermissionGetsForbidden(): void
    {
        // alice a la permission via son role "user" ? Non : le role user par
        // defaut n'a aucune permission. Elle ne peut donc pas lister.
        $client = $this->authenticatedClient('alice', 'alice');
        $client->request('GET', '/api/sites');

        self::assertResponseStatusCodeSame(403);
    }

    public function testUnauthenticatedGetCollectionReturns401(): void
    {
        $client = self::createClient();
        $client->request('GET', '/api/sites');

        self::assertResponseStatusCodeSame(401);
    }

    public function testAdminCanCreateSite(): void
    {
        $client   = $this->authenticatedClient('admin', 'admin');
        $response = $client->request('POST', '/api/sites', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'       => 'Test-New-Site',
                'street'     => '1 rue du Test',
                'complement' => null,
                'postalCode' => '86000',
                'city'       => 'Poitiers',
                'color'      => '#AABBCC',
            ],
        ]);

        self::assertResponseStatusCodeSame(201);
        $data = $response->toArray();
        self::assertSame('Test-New-Site', $data['name']);
        self::assertSame('#AABBCC', $data['color']);
    }

    public function testAdminCanPatchSite(): void
    {
        $em   = $this->getEm();
        $site = new Site('Test-Patch-Site', '1 rue Test', null, '86000', 'Poitiers', '#000000');
        $em->persist($site);
        $em->flush();

        $client   = $this->authenticatedClient('admin', 'admin');
        $response = $client->request('PATCH', '/api/sites/'.$site->getId(), [
            'headers' => ['Content-Type' => 'application/merge-patch+json'],
            'json'    => ['color' => '#FF0000'],
        ]);

        self::assertResponseIsSuccessful();
        $data = $response->toArray();
        self::assertSame('#FF0000', $data['color']);
    }

    public function testAdminCanDeleteSite(): void
    {
        $em   = $this->getEm();
        $site = new Site('Test-Delete-Site', '1 rue Test', null, '86000', 'Poitiers', '#000000');
        $em->persist($site);
        $em->flush();
        $siteId = $site->getId();

        $client = $this->authenticatedClient('admin', 'admin');
        $client->request('DELETE', '/api/sites/'.$siteId);

        self::assertResponseStatusCodeSame(204);

        $em->clear();
        self::assertNull($em->getRepository(Site::class)->find($siteId));
    }

    public function testUserWithViewButNotManageCannotDelete(): void
    {
        $em   = $this->getEm();
        $site = new Site('Test-Protected', '1 rue Test', null, '86000', 'Poitiers', '#000000');
        $em->persist($site);
        $em->flush();

        $this->skipIfSitesModuleDisabled();
        $credentials = $this->createUserWithPermission('sites.view');
        $client      = $this->authenticatedClient($credentials['username'], $credentials['password']);
        $client->request('DELETE', '/api/sites/'.$site->getId());

        self::assertResponseStatusCodeSame(403);
    }

    public function testCreateSiteWithDuplicateNameReturns422(): void
    {
        $client = $this->authenticatedClient('admin', 'admin');
        $client->request('POST', '/api/sites', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'       => 'Chatellerault',
                'street'     => 'Autre rue',
                'postalCode' => '75001',
                'city'       => 'Autre ville',
                'color'      => '#FF0000',
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    public function testCreateSiteWithInvalidColorReturns422(): void
    {
        $client = $this->authenticatedClient('admin', 'admin');
        $client->request('POST', '/api/sites', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'       => 'Test-Invalid-Color',
                'street'     => '1 rue Test',
                'postalCode' => '86000',
                'city'       => 'Poitiers',
                'color'      => 'red',
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    public function testCreateSiteIgnoresFullAddressInPayload(): void
    {
        // Garde structurelle : `fullAddress` est un getter computed cote
        // backend (Site::getFullAddress, groupe site:read uniquement). Si un
        // client envoie ce champ en POST, API Platform doit l'ignorer
        // silencieusement car il n'est pas dans le groupe site:write. On
        // grave ce comportement pour qu'un futur dev qui ajouterait un
        // setter casse ce test au lieu de casser l'invariant en silence.
        $client   = $this->authenticatedClient('admin', 'admin');
        $response = $client->request('POST', '/api/sites', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'        => 'Test-FullAddress-Ignored',
                'street'      => '1 rue Test',
                'postalCode'  => '86000',
                'city'        => 'Poitiers',
                'color'       => '#000000',
                'fullAddress' => 'Adresse arbitraire envoyee par le client',
            ],
        ]);

        self::assertResponseStatusCodeSame(201);
        $data = $response->toArray();
        // Le getter computed prevaut sur ce qu'envoie le client : street
        // determine la 1re ligne, jamais la valeur "Adresse arbitraire...".
        self::assertSame("1 rue Test\n86000 Poitiers", $data['fullAddress']);
    }

    public function testCreateSiteWithInvalidPostalCodeReturns422(): void
    {
        $client = $this->authenticatedClient('admin', 'admin');
        $client->request('POST', '/api/sites', [
            'headers' => ['Content-Type' => 'application/ld+json'],
            'json'    => [
                'name'       => 'Test-Invalid-CP',
                'street'     => '1 rue Test',
                'postalCode' => '123',
                'city'       => 'Poitiers',
                'color'      => '#000000',
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    private function cleanupTestSites(): void
    {
        if (!self::$kernel) {
            self::bootKernel();
        }
        $em = $this->getEm();
        $em->createQuery('DELETE FROM '.Site::class.' s WHERE s.name LIKE :prefix')
            ->setParameter('prefix', self::TEST_NAME_PREFIX.'%')
            ->execute()
        ;
        $em->clear();
    }
}
