<?php

declare(strict_types=1);

namespace App\Tests\Module\Technique\Api;

use ApiPlatform\Symfony\Bundle\Test\Client;

/**
 * Tests du cloisonnement par site des SOUS-RESSOURCES d'un prestataire (Contacts /
 * Adresses / RIB) — § 2.13 / RG-3.17. Complement de ProviderSiteScopeTest (qui ne
 * couvrait que le Provider lui-meme).
 *
 * Sans garde dedie, un user cloisonne pouvait lire / editer / supprimer une
 * sous-ressource d'un prestataire HORS de son site (le detail Provider est garde en
 * 404, mais les sous-ressources passent par le provider Doctrine par defaut, non
 * cloisonne — et SiteScopedQueryExtension ne filtre que les SiteAwareInterface).
 * Le RIB est particulierement sensible (IBAN / BIC).
 *
 * Garde pose par ProviderSubResourceItemProvider (Get/Patch/Delete -> 404 hors
 * perimetre) + ProviderSiteScopeChecker::assertInScope dans les processors (POST
 * sur parent hors perimetre -> 404). Decision de scope partagee (source unique).
 *
 * @internal
 */
final class ProviderSubResourceSiteScopeTest extends AbstractProviderApiTestCase
{
    /** Permissions completes pour exercer view + manage + accounting sur tous les chemins. */
    private const array FULL_PERMS = [
        'technique.providers.view',
        'technique.providers.manage',
        'technique.providers.accounting.view',
        'technique.providers.accounting.manage',
    ];

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

    public function testGetContactOutOfScopeReturns404ButInScope200(): void
    {
        $inScope     = $this->seedProvider('Presta In Scope', [self::SITE_86]);
        $inContactId = $this->addContact($inScope, 'Marie', 'Martin')->getId();

        $outScope     = $this->seedProvider('Presta Out Scope', [self::SITE_17]);
        $outContactId = $this->addContact($outScope, 'Paul', 'Durand')->getId();

        $client = $this->scopedClient();

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

        // Hors perimetre : 404 (ne pas reveler l'existence du contact d'un autre site).
        $ko = $client->request('GET', '/api/provider_contacts/'.$outContactId, ['headers' => ['Accept' => self::LD]]);
        self::assertSame(404, $ko->getStatusCode());
    }

    public function testGetRibOutOfScopeReturns404(): void
    {
        // RIB = donnee bancaire sensible (IBAN/BIC) : le cas le plus critique.
        $outScope = $this->seedProvider('Presta Out Rib', [self::SITE_17]);
        $ribId    = $this->addRib($outScope)->getId();

        $client = $this->scopedClient();

        $response = $client->request('GET', '/api/provider_ribs/'.$ribId, ['headers' => ['Accept' => self::LD]]);
        self::assertSame(404, $response->getStatusCode());
    }

    public function testPatchRibOutOfScopeReturns404(): void
    {
        $outScope = $this->seedProvider('Presta Patch Rib', [self::SITE_17]);
        $ribId    = $this->addRib($outScope)->getId();

        $client = $this->scopedClient();

        $response = $client->request('PATCH', '/api/provider_ribs/'.$ribId, [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['label' => 'Hacked'],
        ]);
        self::assertSame(404, $response->getStatusCode());
    }

    public function testDeleteContactOutOfScopeReturns404(): void
    {
        $outScope  = $this->seedProvider('Presta Del Contact', [self::SITE_17]);
        $contactId = $this->addContact($outScope, 'Paul', 'Durand')->getId();

        $client = $this->scopedClient();

        $response = $client->request('DELETE', '/api/provider_contacts/'.$contactId);
        self::assertSame(404, $response->getStatusCode());
    }

    public function testPostContactOnOutOfScopeProviderReturns404(): void
    {
        $outScope = $this->seedProvider('Presta Post Contact', [self::SITE_17]);
        $id       = $outScope->getId();

        $client = $this->scopedClient();

        $response = $client->request('POST', '/api/providers/'.$id.'/contacts', [
            'headers' => ['Content-Type' => self::LD, 'Accept' => self::LD],
            'json'    => ['firstName' => 'Intrus'],
        ]);
        self::assertSame(404, $response->getStatusCode());
    }

    public function testPostRibOnOutOfScopeProviderReturns404(): void
    {
        $outScope = $this->seedProvider('Presta Post Rib', [self::SITE_17]);
        $id       = $outScope->getId();

        $client = $this->scopedClient();

        $response = $client->request('POST', '/api/providers/'.$id.'/ribs', [
            'headers' => ['Content-Type' => self::LD, 'Accept' => self::LD],
            'json'    => [
                'label' => 'Intrus',
                'iban'  => self::VALID_IBAN,
                'bic'   => self::VALID_BIC,
            ],
        ]);
        self::assertSame(404, $response->getStatusCode());
    }

    public function testBypassUserReachesSubResourceOnAnySite(): void
    {
        // Temoin : l'admin (bypass total) lit bien un contact hors « son » site.
        $outScope  = $this->seedProvider('Presta Admin Reach', [self::SITE_17]);
        $contactId = $this->addContact($outScope, 'Marie', 'Martin')->getId();

        $client   = $this->createAdminClient();
        $response = $client->request('GET', '/api/provider_contacts/'.$contactId, ['headers' => ['Accept' => self::LD]]);
        self::assertSame(200, $response->getStatusCode());
    }

    /**
     * Client authentifie comme un user NON-bypass rattache au seul site 86 (avec
     * currentSite 86) — sujet des tests de cloisonnement des sous-ressources.
     */
    private function scopedClient(): Client
    {
        $creds = $this->createScopedUser(
            self::FULL_PERMS,
            sitePostalCodes: [self::SITE_86],
            currentSitePostalCode: self::SITE_86,
        );

        return $this->authenticatedClient($creds['username'], $creds['password']);
    }
}
