<?php

declare(strict_types=1);

namespace App\Tests\Module\Technique\Api;

/**
 * Tests du cloisonnement par site pilote par l'utilisateur (RG-3.17 / § 2.13) —
 * ERP-134. Couvre la LECTURE (liste filtree avant pagination + totalItems, detail
 * 404 hors perimetre, bypass voit tout) et l'ECRITURE (sites hors user_site -> 422).
 *
 * Cloisonnement pilote par l'USER (pas le role) : on cree des users non-admin SANS
 * `sites.bypass_scope`, rattaches a un site precis avec un currentSite. L'admin
 * (isAdmin -> bypass total) sert de temoin « voit tout ».
 *
 * @internal
 */
final class ProviderSiteScopeTest extends AbstractProviderApiTestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        // Pre-requis : le module Sites doit etre actif (sinon currentSite = null,
        // cloisonnement no-op et ces tests perdent leur sens).
        $this->skipIfSitesModuleDisabled();
    }

    public function testListIsScopedToCurrentSiteForNonBypassUser(): void
    {
        $this->seedProvider('Presta Site 86', [self::SITE_86]);
        $this->seedProvider('Presta Site 17', [self::SITE_17]);
        $this->seedProvider('Presta Site 82', [self::SITE_82]);

        $creds = $this->createScopedUser(
            ['technique.providers.view'],
            sitePostalCodes: [self::SITE_86],
            currentSitePostalCode: self::SITE_86,
        );
        $client = $this->authenticatedClient($creds['username'], $creds['password']);

        $response = $client->request('GET', '/api/providers', ['headers' => ['Accept' => self::LD]]);
        self::assertSame(200, $response->getStatusCode());

        $body = $response->toArray();
        // totalItems reflete le PERIMETRE de l'user (filtre avant pagination).
        self::assertSame(1, $body['totalItems']);
        self::assertSame('PRESTA SITE 86', $body['member'][0]['companyName']);
    }

    public function testDetailOutOfScopeReturns404(): void
    {
        $inScope    = $this->seedProvider('Dans Perimetre', [self::SITE_86]);
        $outOfScope = $this->seedProvider('Hors Perimetre', [self::SITE_17]);

        $creds = $this->createScopedUser(
            ['technique.providers.view'],
            sitePostalCodes: [self::SITE_86],
            currentSitePostalCode: self::SITE_86,
        );
        $client = $this->authenticatedClient($creds['username'], $creds['password']);

        // In-scope -> 200.
        $ok = $client->request('GET', '/api/providers/'.$inScope->getId(), ['headers' => ['Accept' => self::LD]]);
        self::assertSame(200, $ok->getStatusCode());

        // Out-of-scope -> 404 (ne pas reveler l'existence hors perimetre).
        $ko = $client->request('GET', '/api/providers/'.$outOfScope->getId(), ['headers' => ['Accept' => self::LD]]);
        self::assertSame(404, $ko->getStatusCode());
    }

    public function testBypassUserSeesAllSites(): void
    {
        $this->seedProvider('Presta Site 86', [self::SITE_86]);
        $this->seedProvider('Presta Site 17', [self::SITE_17]);
        $this->seedProvider('Presta Site 82', [self::SITE_82]);

        // Admin = bypass total.
        $client   = $this->createAdminClient();
        $response = $client->request('GET', '/api/providers', ['headers' => ['Accept' => self::LD]]);

        self::assertSame(200, $response->getStatusCode());
        self::assertSame(3, $response->toArray()['totalItems']);
    }

    public function testWriteOutOfScopeSiteRejectedAtIriResolution(): void
    {
        // User non-bypass / non-read_ref : la resolution de l'IRI du site hors
        // perimetre echoue en amont (SiteCollectionScopedExtension : item Site
        // « introuvable ») -> 400 anti-enumeration, avant le ProviderProcessor.
        $creds = $this->createScopedUser(
            ['technique.providers.view', 'technique.providers.manage'],
            sitePostalCodes: [self::SITE_86],
            currentSitePostalCode: self::SITE_86,
        );
        $client = $this->authenticatedClient($creds['username'], $creds['password']);

        $response = $client->request('POST', '/api/providers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => $this->validMainPayload('Hors Scope Sas', [self::SITE_17]),
        ]);

        self::assertSame(400, $response->getStatusCode());
    }

    public function testWriteOutOfScopeSiteRejectedByProcessorGuard(): void
    {
        // User `sites.read_ref` : peut RESOUDRE n'importe quel site (referentiel
        // transverse) mais n'opere que sur ses user_site. La garde guardSiteScope
        // du ProviderProcessor est alors l'enforcement autoritaire de RG-3.17
        // -> 422 sur `sites` (mappable inline, ERP-101).
        $creds = $this->createScopedUser(
            ['technique.providers.view', 'technique.providers.manage', 'sites.read_ref'],
            sitePostalCodes: [self::SITE_86],
            currentSitePostalCode: self::SITE_86,
        );
        $client = $this->authenticatedClient($creds['username'], $creds['password']);

        $response = $client->request('POST', '/api/providers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => $this->validMainPayload('Hors Scope Guard', [self::SITE_17]),
        ]);

        self::assertSame(422, $response->getStatusCode());
        self::assertArrayHasKey('sites', $this->violationsByPath($response->toArray(false)));
    }

    public function testWriteAllowsSiteWithinUserScope(): void
    {
        $creds = $this->createScopedUser(
            ['technique.providers.view', 'technique.providers.manage'],
            sitePostalCodes: [self::SITE_86],
            currentSitePostalCode: self::SITE_86,
        );
        $client = $this->authenticatedClient($creds['username'], $creds['password']);

        // Site 86 = un des user_site -> 201.
        $response = $client->request('POST', '/api/providers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => $this->validMainPayload('Dans Scope Sas', [self::SITE_86]),
        ]);

        self::assertSame(201, $response->getStatusCode());
    }

    public function testPatchAddingOutOfScopeSiteIsRejected(): void
    {
        $provider = $this->seedProvider('Patch Sites', [self::SITE_86]);
        $id       = $provider->getId();

        // read_ref pour pouvoir resoudre l'IRI du site 17 (sinon 400 en amont) et
        // exercer la garde guardSiteScope sur le PATCH.
        $creds = $this->createScopedUser(
            ['technique.providers.view', 'technique.providers.manage', 'sites.read_ref'],
            sitePostalCodes: [self::SITE_86],
            currentSitePostalCode: self::SITE_86,
        );
        $client = $this->authenticatedClient($creds['username'], $creds['password']);

        $site86 = $this->site(self::SITE_86)->getId();
        $site17 = $this->site(self::SITE_17)->getId();

        $response = $client->request('PATCH', '/api/providers/'.$id, [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['sites' => ['/api/sites/'.$site86, '/api/sites/'.$site17]],
        ]);

        // RG-3.17 : ajouter un site hors user_site -> 422 (garde Processor).
        self::assertSame(422, $response->getStatusCode());
        self::assertArrayHasKey('sites', $this->violationsByPath($response->toArray(false)));
    }
}
