<?php

declare(strict_types=1);

namespace App\Tests\Module\Commercial\Api;

use App\Module\Sites\Domain\Entity\Site;

/**
 * Tests fonctionnels de l'onglet Adresse.
 *
 * RG-1.09 (code postal) et RG-1.10 (>= 1 site) sont DEJA couverts par
 * ClientSubResourceApiTest (ERP-57) et ne sont pas reduplique ici. Ce fichier
 * cible :
 *  - RG-1.06 / RG-1.07 / RG-1.08 : exclusivite is_prospect vs
 *    is_delivery / is_billing ;
 *  - RG-1.11 : billing_email obligatoire ssi is_billing ;
 *  - RG-1.29 (ERP-78) : les categories de code DISTRIBUTEUR / COURTIER sont
 *    interdites sur une adresse (-> 422) ; toute autre categorie est acceptee.
 *
 * Depuis ERP-76, ces regles sont portees par des Assert\Callback sur l'entite
 * ClientAddress (mirror applicatif des CHECK Postgres) : la combinaison invalide
 * est donc rejetee en 422 AVANT la base, et non plus par une violation CHECK
 * remontant en 500. Les CHECK BDD restent en filet de securite (non testes ici,
 * inatteignables tant que les validators applicatifs passent en premier).
 *
 * @internal
 */
final class ClientAddressTest extends AbstractCommercialApiTestCase
{
    private const string LD = 'application/ld+json';

    /**
     * RG-1.06 / RG-1.07 : une adresse de prospection ne peut pas etre une
     * adresse de livraison -> 422 (Assert\Callback, mirror du CHECK
     * chk_client_address_prospect_exclusive).
     */
    public function testProspectAddressCannotBeDelivery(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client = $this->createAdminClient();
        $seed   = $this->seedClient('Prospect Delivery');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isProspect' => true,
                'isDelivery' => true,
                'postalCode' => '86100',
                'city'       => 'Châtellerault',
                'street'     => '1 rue du Test',
                'sites'      => [$this->firstSiteIri()],
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    /**
     * RG-1.06 / RG-1.08 : une adresse de prospection ne peut pas etre une
     * adresse de facturation -> 422. On fournit billingEmail pour que la seule
     * violation possible soit l'exclusivite prospect/billing.
     */
    public function testProspectAddressCannotBeBilling(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client = $this->createAdminClient();
        $seed   = $this->seedClient('Prospect Billing');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isProspect'   => true,
                'isBilling'    => true,
                'billingEmail' => 'facturation@test.fr',
                'postalCode'   => '86100',
                'city'         => 'Châtellerault',
                'street'       => '1 rue du Test',
                'sites'        => [$this->firstSiteIri()],
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    /**
     * RG-1.11 : une adresse de facturation exige un billingEmail -> 422.
     */
    public function testBillingAddressRequiresBillingEmail(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client = $this->createAdminClient();
        $seed   = $this->seedClient('Billing No Email');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isBilling'  => true,
                'postalCode' => '86100',
                'city'       => 'Châtellerault',
                'street'     => '1 rue du Test',
                'sites'      => [$this->firstSiteIri()],
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    /**
     * RG-1.11 (cas chaine vide) : une adresse de facturation avec un billingEmail
     * vide ("") doit etre rejetee en 422, et NON passer la validation pour finir
     * en 500 sur le CHECK BDD. Le ClientAddressProcessor normalise "" -> null
     * APRES la validation : le callback doit donc traiter "" comme « absent ».
     */
    public function testBillingAddressRejectsEmptyBillingEmail(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client = $this->createAdminClient();
        $seed   = $this->seedClient('Billing Empty Email');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isBilling'    => true,
                'billingEmail' => '',
                'postalCode'   => '86100',
                'city'         => 'Châtellerault',
                'street'       => '1 rue du Test',
                'sites'        => [$this->firstSiteIri()],
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    /**
     * RG-1.11 (sens inverse) : une adresse NON facturable ne peut pas porter un
     * billingEmail -> 422.
     */
    public function testNonBillingAddressRejectsBillingEmail(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client = $this->createAdminClient();
        $seed   = $this->seedClient('Non Billing With Email');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isDelivery'   => true,
                'isBilling'    => false,
                'billingEmail' => 'parasite@test.fr',
                'postalCode'   => '86100',
                'city'         => 'Châtellerault',
                'street'       => '1 rue du Test',
                'sites'        => [$this->firstSiteIri()],
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    /**
     * RG-1.11 (sens inverse, cas chaine vide) : une adresse NON facturable avec
     * un billingEmail vide ("") est ACCEPTEE (201). Le "" equivaut a « pas
     * d'email » : il ne doit pas declencher la violation « email interdit hors
     * facturation » (sinon un champ simplement vide serait refuse a tort).
     */
    public function testNonBillingAddressAcceptsEmptyBillingEmail(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client   = $this->createAdminClient();
        $seed     = $this->seedClient('Non Billing Empty Email');
        $category = $this->createCategory('SECTEUR');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isDelivery'   => true,
                'isBilling'    => false,
                'billingEmail' => '',
                'postalCode'   => '86100',
                'city'         => 'Châtellerault',
                'street'       => '1 rue du Test',
                'sites'        => [$this->firstSiteIri()],
                'categories'   => ['/api/categories/'.$category->getId()],
            ],
        ]);

        self::assertResponseStatusCodeSame(201);
    }

    /**
     * ERP-119 : une adresse de facturation accepte un 2e email (optionnel, max 2).
     */
    public function testBillingAddressAcceptsTwoEmails(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client   = $this->createAdminClient();
        $seed     = $this->seedClient('Billing Two Emails');
        $category = $this->createCategory('SECTEUR');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isBilling'             => true,
                'billingEmail'          => 'facturation@test.fr',
                'billingEmailSecondary' => 'compta@test.fr',
                'postalCode'            => '86100',
                'city'                  => 'Châtellerault',
                'street'                => '1 rue du Test',
                'sites'                 => [$this->firstSiteIri()],
                'categories'            => ['/api/categories/'.$category->getId()],
            ],
        ]);

        self::assertResponseStatusCodeSame(201);
    }

    /**
     * ERP-119 : le 2e email de facturation, comme le principal, n'est autorise que
     * sur une adresse de facturation -> 422 avec violation sur billingEmailSecondary.
     */
    public function testSecondaryBillingEmailRejectedOnNonBillingAddress(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client   = $this->createAdminClient();
        $seed     = $this->seedClient('Secondary Email Non Billing');
        $category = $this->createCategory('SECTEUR');

        $body = $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD, 'Accept' => self::LD],
            'json'    => [
                'isDelivery'            => true,
                'billingEmailSecondary' => 'compta@test.fr',
                'postalCode'            => '86100',
                'city'                  => 'Châtellerault',
                'street'                => '1 rue du Test',
                'sites'                 => [$this->firstSiteIri()],
                'categories'            => ['/api/categories/'.$category->getId()],
            ],
        ])->toArray(false);

        self::assertResponseStatusCodeSame(422);
        $byPath = $this->violationsByPath($body);
        self::assertArrayHasKey('billingEmailSecondary', $byPath);
    }

    /**
     * RG-1.29 : poster une categorie de type DISTRIBUTEUR sur une adresse -> 422
     * avec violation sur le champ `categories`.
     */
    public function testAddressRejectsDistributorCategory(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client   = $this->createAdminClient();
        $seed     = $this->seedClient('Address Distributor Cat');
        $category = $this->createCategory('DISTRIBUTEUR');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isDelivery' => true,
                'postalCode' => '86100',
                'city'       => 'Châtellerault',
                'street'     => '1 rue du Test',
                'sites'      => [$this->firstSiteIri()],
                'categories' => ['/api/categories/'.$category->getId()],
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
        self::assertStringContainsString(
            'Type de catégorie non autorisé sur une adresse.',
            (string) $client->getResponse()->getContent(false),
        );
    }

    /**
     * RG-1.29 : poster une categorie de type COURTIER sur une adresse -> 422.
     */
    public function testAddressRejectsBrokerCategory(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client   = $this->createAdminClient();
        $seed     = $this->seedClient('Address Broker Cat');
        $category = $this->createCategory('COURTIER');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isDelivery' => true,
                'postalCode' => '86100',
                'city'       => 'Châtellerault',
                'street'     => '1 rue du Test',
                'sites'      => [$this->firstSiteIri()],
                'categories' => ['/api/categories/'.$category->getId()],
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    /**
     * RG-1.29 : une categorie de type SECTEUR est autorisee sur une adresse.
     */
    public function testAddressAcceptsSectorCategory(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client   = $this->createAdminClient();
        $seed     = $this->seedClient('Address Sector Cat');
        $category = $this->createCategory('SECTEUR');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isDelivery' => true,
                'postalCode' => '86100',
                'city'       => 'Châtellerault',
                'street'     => '1 rue du Test',
                'sites'      => [$this->firstSiteIri()],
                'categories' => ['/api/categories/'.$category->getId()],
            ],
        ]);

        self::assertResponseStatusCodeSame(201);
    }

    /**
     * RG-1.29 : une categorie de type AUTRE est autorisee sur une adresse.
     */
    public function testAddressAcceptsOtherCategory(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client   = $this->createAdminClient();
        $seed     = $this->seedClient('Address Other Cat');
        $category = $this->createCategory('AUTRE');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isDelivery' => true,
                'postalCode' => '86100',
                'city'       => 'Châtellerault',
                'street'     => '1 rue du Test',
                'sites'      => [$this->firstSiteIri()],
                'categories' => ['/api/categories/'.$category->getId()],
            ],
        ]);

        self::assertResponseStatusCodeSame(201);
    }

    /**
     * Spec-front § Adresse : au moins une categorie est obligatoire sur une
     * adresse. POST sans categorie (mais avec site) -> 422.
     */
    public function testAddressRequiresAtLeastOneCategory(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client = $this->createAdminClient();
        $seed   = $this->seedClient('Address No Cat');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isDelivery' => true,
                'postalCode' => '86100',
                'city'       => 'Châtellerault',
                'street'     => '1 rue du Test',
                'sites'      => [$this->firstSiteIri()],
            ],
        ]);

        self::assertResponseStatusCodeSame(422);
    }

    /**
     * RG (ERP-119) : au moins un type d'adresse (Prospection / Livraison /
     * Facturation) est obligatoire. POST sans aucun drapeau de type -> 422, avec
     * une violation portee sur `isProspect` (mappee sous le select « Type
     * d'adresse » cote front via ClientAddressBlock).
     */
    public function testAddressRequiresAtLeastOneType(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client   = $this->createAdminClient();
        $seed     = $this->seedClient('Address No Type');
        $category = $this->createCategory('SECTEUR');

        $body = $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD, 'Accept' => self::LD],
            'json'    => [
                'postalCode' => '86100',
                'city'       => 'Châtellerault',
                'street'     => '1 rue du Test',
                'sites'      => [$this->firstSiteIri()],
                'categories' => ['/api/categories/'.$category->getId()],
            ],
        ])->toArray(false);

        self::assertResponseStatusCodeSame(422);
        $byPath = $this->violationsByPath($body);
        self::assertArrayHasKey('isProspect', $byPath);
        self::assertSame('Le type d\'adresse est obligatoire.', $byPath['isProspect']);
    }

    /**
     * Nouveaux types d'adresse (ERP-119) : Courtier et Distributeur sont acceptes
     * comme types autonomes (avec site + categorie). is_broker / is_distributor.
     */
    public function testBrokerAddressAccepted(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client   = $this->createAdminClient();
        $seed     = $this->seedClient('Address Broker Type');
        $category = $this->createCategory('SECTEUR');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isBroker'   => true,
                'postalCode' => '86100',
                'city'       => 'Châtellerault',
                'street'     => '1 rue du Test',
                'sites'      => [$this->firstSiteIri()],
                'categories' => ['/api/categories/'.$category->getId()],
            ],
        ]);

        self::assertResponseStatusCodeSame(201);
    }

    public function testDistributorAddressAccepted(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client   = $this->createAdminClient();
        $seed     = $this->seedClient('Address Distributor Type');
        $category = $this->createCategory('SECTEUR');

        $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'isDistributor' => true,
                'postalCode'    => '86100',
                'city'          => 'Châtellerault',
                'street'        => '1 rue du Test',
                'sites'         => [$this->firstSiteIri()],
                'categories'    => ['/api/categories/'.$category->getId()],
            ],
        ]);

        self::assertResponseStatusCodeSame(201);
    }

    /**
     * Courtier / Distributeur sont des types AUTONOMES exclusifs : les combiner avec
     * un autre usage (ici Livraison) -> 422, violation sur isProspect (mappee sous le
     * select Type d'adresse). Miroir applicatif du CHECK chk_client_address_broker_exclusive.
     */
    public function testExclusiveAddressTypeRejected(): void
    {
        $this->skipIfSitesModuleDisabled();
        $client   = $this->createAdminClient();
        $seed     = $this->seedClient('Address Broker Mix');
        $category = $this->createCategory('SECTEUR');

        $body = $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD, 'Accept' => self::LD],
            'json'    => [
                'isBroker'   => true,
                'isDelivery' => true,
                'postalCode' => '86100',
                'city'       => 'Châtellerault',
                'street'     => '1 rue du Test',
                'sites'      => [$this->firstSiteIri()],
                'categories' => ['/api/categories/'.$category->getId()],
            ],
        ])->toArray(false);

        self::assertResponseStatusCodeSame(422);
        $byPath = $this->violationsByPath($body);
        self::assertArrayHasKey('isProspect', $byPath);
        self::assertSame('Une adresse Courtier ne peut pas avoir d\'autre type.', $byPath['isProspect']);
    }

    /**
     * Retourne l'IRI du premier site seede (fixtures Sites).
     */
    private function firstSiteIri(): string
    {
        $site = $this->getEm()->getRepository(Site::class)->findOneBy([]);
        self::assertNotNull($site, 'Aucun site seede : impossible de tester les adresses.');

        return '/api/sites/'.$site->getId();
    }
}
