<?php

declare(strict_types=1);

namespace App\Tests\Module\Technique\Api;

use App\Module\Technique\Domain\Entity\Provider;

/**
 * Tests du gating comptabilite + mode strict par groupe (ProviderProcessor /
 * ProviderReadGroupContextBuilder) — ERP-134.
 *
 * Couvre : gating accounting PAR OMISSION (siren/ribs absents sans accounting.view,
 * bug #4 M1), mode strict RG-3.15 (403 sur tout le payload), gating archive (RG-3.13).
 *
 * Les users sont crees via createUserWithPermissions() (parent) : rattaches a TOUS
 * les sites SANS currentSite -> CurrentSiteProvider::get() = null -> aucun
 * cloisonnement, on isole ainsi le comportement RBAC du comportement site.
 *
 * @internal
 */
final class ProviderRbacGatingTest extends AbstractProviderApiTestCase
{
    public function testAccountingFieldsOmittedWithoutAccountingView(): void
    {
        $provider = $this->seedProvider('Compta Masquee', [self::SITE_86], siren: '123456789');
        $id       = $provider->getId();

        // Profil type Commerciale : view + manage SANS accounting.view.
        $creds  = $this->createUserWithPermissions(['technique.providers.view']);
        $client = $this->authenticatedClient($creds['username'], $creds['password']);

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

        $body = $response->toArray();
        // Gating par omission : scalaires comptables ET ribs totalement absents.
        self::assertArrayNotHasKey('siren', $body);
        self::assertArrayNotHasKey('ribs', $body);
        // isArchived reste expose (bug #3 M1 : la cle ne doit pas etre droppee).
        self::assertArrayHasKey('isArchived', $body);
    }

    public function testAccountingFieldsPresentWithAccountingView(): void
    {
        $provider = $this->seedProvider('Compta Visible', [self::SITE_86], siren: '987654321');
        $id       = $provider->getId();

        $creds = $this->createUserWithPermissions([
            'technique.providers.view',
            'technique.providers.accounting.view',
        ]);
        $client = $this->authenticatedClient($creds['username'], $creds['password']);

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

        $body = $response->toArray();
        self::assertSame('987654321', $body['siren']);
        // La cle ribs apparait (collection vide ici, mais presente).
        self::assertArrayHasKey('ribs', $body);
    }

    public function testStrictModeRejectsMixedGroupsForManageOnlyUser(): void
    {
        $provider = $this->seedProvider('Strict Cible', [self::SITE_86]);
        $id       = $provider->getId();

        // Profil type Bureau : manage SANS accounting.manage.
        $creds = $this->createUserWithPermissions([
            'technique.providers.view',
            'technique.providers.manage',
        ]);
        $client = $this->authenticatedClient($creds['username'], $creds['password']);

        $response = $client->request('PATCH', '/api/providers/'.$id, [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['companyName' => 'Renomme', 'siren' => '111222333'],
        ]);

        // RG-3.15 : payload melangeant main + accounting sans accounting.manage
        // -> 403 sur tout le payload (mode strict, pas de filtrage silencieux).
        self::assertSame(403, $response->getStatusCode());

        // Aucun champ n'a ete persiste (rollback du mode strict).
        $this->getEm()->clear();
        $reloaded = $this->getEm()->getRepository(Provider::class)->find($id);
        self::assertSame('STRICT CIBLE', $reloaded->getCompanyName());
        self::assertNull($reloaded->getSiren());
    }

    public function testAccountingOnlyUserCanPatchAccountingButNotMain(): void
    {
        $provider = $this->seedProvider('Compta Editrice', [self::SITE_86]);
        $id       = $provider->getId();

        // Profil type Compta : accounting.view + accounting.manage SANS manage.
        $creds = $this->createUserWithPermissions([
            'technique.providers.view',
            'technique.providers.accounting.view',
            'technique.providers.accounting.manage',
        ]);
        $client = $this->authenticatedClient($creds['username'], $creds['password']);

        // PATCH accounting -> 200.
        $ok = $client->request('PATCH', '/api/providers/'.$id, [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['siren' => '555666777'],
        ]);
        self::assertSame(200, $ok->getStatusCode());

        // PATCH main (companyName) -> 403 (pas de permission manage).
        $ko = $client->request('PATCH', '/api/providers/'.$id, [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['companyName' => 'Interdit'],
        ]);
        self::assertSame(403, $ko->getStatusCode());
    }

    public function testArchiveRequiresArchivePermission(): void
    {
        $provider = $this->seedProvider('A Archiver', [self::SITE_86]);
        $id       = $provider->getId();

        // Bureau (manage) sans archive -> 403.
        $creds = $this->createUserWithPermissions([
            'technique.providers.view',
            'technique.providers.manage',
        ]);
        $client = $this->authenticatedClient($creds['username'], $creds['password']);

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

        // RG-3.13 : l'archivage exige technique.providers.archive.
        self::assertSame(403, $response->getStatusCode());
    }

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

        $client   = $this->createAdminClient();
        $response = $client->request('PATCH', '/api/providers/'.$id, [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['isArchived' => true],
        ]);

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

        $this->getEm()->clear();
        $reloaded = $this->getEm()->getRepository(Provider::class)->find($id);
        self::assertTrue($reloaded->isArchived());
        self::assertNotNull($reloaded->getArchivedAt());
    }

    public function testRestoreWithNameConflictReturns409(): void
    {
        // Un prestataire archive porte un nom qu'un prestataire ACTIF a repris
        // entre-temps (autorise par l'index partiel : l'archive n'y figure pas).
        $archived = $this->seedProvider('Conflit Co', [self::SITE_86], isArchived: true);
        $this->seedProvider('Conflit Co', [self::SITE_86]); // actif, meme nom normalise

        $client   = $this->createAdminClient();
        $response = $client->request('PATCH', '/api/providers/'.$archived->getId(), [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['isArchived' => false],
        ]);

        // RG-3.14 : restaurer ferait deux actifs homonymes -> 409 (pas de 500 SQL).
        self::assertSame(409, $response->getStatusCode());
    }
}
