<?php

declare(strict_types=1);

namespace App\Tests\Module\Commercial\Api;

use App\Module\Commercial\Domain\Entity\Supplier;

/**
 * Tests fonctionnels du formulaire principal fournisseur (M2, spec § 4.3 / § 4.4)
 * sur le CORPS JSON : creation (companyName + categories), normalisation serveur
 * (RG-2.12 UPPERCASE), categorie de type FOURNISSEUR (RG-2.10), unicite du nom
 * (RG-2.11) et archivage nominal (RG-2.14). Jumeau de ClientApiTest (M1).
 *
 * @internal
 */
final class SupplierApiTest extends AbstractSupplierApiTestCase
{
    // === POST formulaire principal ===

    public function testPostMainFormUppercasesCompanyName(): void
    {
        $client = $this->createAdminClient();
        $cat    = $this->supplierCategory('NEGOCIANT');

        $data = $client->request('POST', '/api/suppliers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'companyName' => 'recycla sas',
                'categories'  => ['/api/categories/'.$cat->getId()],
            ],
        ])->toArray();

        self::assertResponseStatusCodeSame(201);
        // RG-2.12 : companyName normalise en MAJUSCULES sur la valeur RENVOYEE.
        self::assertSame('RECYCLA SAS', $data['companyName']);
        // Embed categorie : code/name presents (category:read dans le contexte).
        self::assertSame('NEGOCIANT', $data['categories'][0]['code']);
    }

    public function testPostMainFormHasNoInlineContactFields(): void
    {
        // refonte-contact V0.2 : plus aucun champ de contact inline au POST.
        $client = $this->createAdminClient();
        $cat    = $this->supplierCategory('NEGOCIANT');

        $data = $client->request('POST', '/api/suppliers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'companyName'    => 'No Inline Co',
                // Champs historiques : ignores par le denormaliseur.
                'firstName'      => 'Ignored',
                'lastName'       => 'Ignored',
                'phonePrimary'   => '0612345678',
                'email'          => 'ignored@test.fr',
                'categories'     => ['/api/categories/'.$cat->getId()],
            ],
        ])->toArray();

        self::assertResponseStatusCodeSame(201);
        foreach (['firstName', 'lastName', 'phonePrimary', 'phoneSecondary', 'email'] as $key) {
            self::assertArrayNotHasKey($key, $data);
        }
    }

    // === RG-2.10 : categorie de type FOURNISSEUR ===

    public function testPostWithNonFournisseurCategoryReturns422OnCategoriesPath(): void
    {
        $client = $this->createAdminClient();
        // createCategory() (parent) cree une categorie de type CLIENT -> interdite.
        $clientTypedCategory = $this->createCategory('SECTEUR');

        $response = $client->request('POST', '/api/suppliers', [
            'headers' => ['Content-Type' => self::LD, 'Accept' => self::LD],
            'json'    => [
                'companyName' => 'Wrong Cat Type',
                'categories'  => ['/api/categories/'.$clientTypedCategory->getId()],
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
        $byPath = [];
        foreach ($response->toArray(false)['violations'] ?? [] as $v) {
            $byPath[$v['propertyPath']] = $v['message'];
        }
        // ERP-101 : la violation porte propertyPath=categories (mapping inline front).
        self::assertArrayHasKey('categories', $byPath);
        self::assertSame('Type de catégorie non autorisé (FOURNISSEUR attendu).', $byPath['categories']);
    }

    // === RG-2.11 : unicite du nom de societe ===

    public function testPostDuplicateCompanyNameReturns409(): void
    {
        $client = $this->createAdminClient();
        $this->seedSupplier('Dup Name Co');

        $client->request('POST', '/api/suppliers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => $this->validMainPayload('Dup Name Co'),
        ]);

        // RG-2.11 : doublon parmi les actifs -> 409 (index uq_supplier_company_name_active).
        self::assertResponseStatusCodeSame(409);
    }

    public function testPostSameNameAfterArchivingPreviousReturns201(): void
    {
        $client = $this->createAdminClient();
        // L'homonyme est archive -> hors index partiel : le nom redevient disponible.
        $this->seedSupplier('Reuse After Archive', true);

        $client->request('POST', '/api/suppliers', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => $this->validMainPayload('Reuse After Archive'),
        ]);

        self::assertResponseStatusCodeSame(201);
    }

    // === RG-2.14 : archivage (admin) ===

    public function testAdminArchiveSetsArchivedAt(): void
    {
        $client = $this->createAdminClient();
        $seed   = $this->seedSupplier('Archive Me');

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

        self::assertResponseStatusCodeSame(200);

        $em = $this->getEm();
        $em->clear();
        $reloaded = $em->getRepository(Supplier::class)->find($seed->getId());
        self::assertNotNull($reloaded);
        self::assertTrue($reloaded->isArchived());
        self::assertNotNull($reloaded->getArchivedAt(), 'RG-2.14 : archivedAt doit etre rempli a l\'archivage.');
    }

    public function testArchiveWithOtherFieldReturns422(): void
    {
        $client = $this->createAdminClient();
        $seed   = $this->seedSupplier('Archive Plus Field');

        // RG-2.14 : une requete d'archivage ne modifie aucun autre champ.
        $response = $client->request('PATCH', '/api/suppliers/'.$seed->getId(), [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['isArchived' => true, 'companyName' => 'Renamed While Archiving'],
        ]);

        self::assertResponseStatusCodeSame(422);
        // Le 422 doit etre celui de RG-2.14 (archivage exclusif) et non un 422
        // orthogonal : on verifie le message porte par l'exception.
        self::assertStringContainsString('archivage', $response->getContent(false));
    }

    public function testRestoreSetsArchivedAtNull(): void
    {
        $client = $this->createAdminClient();
        $seed   = $this->seedSupplier('Restore Me', true);

        $client->request('PATCH', '/api/suppliers/'.$seed->getId(), [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['isArchived' => false],
        ]);

        self::assertResponseStatusCodeSame(200);

        $em = $this->getEm();
        $em->clear();
        $reloaded = $em->getRepository(Supplier::class)->find($seed->getId());
        self::assertNotNull($reloaded);
        self::assertFalse($reloaded->isArchived());
        self::assertNull($reloaded->getArchivedAt(), 'RG-2.15 : archivedAt repasse a null a la restauration.');
    }
}
