<?php

declare(strict_types=1);

namespace App\Tests\Module\Transport\Api;

use App\Module\Core\Infrastructure\DataFixtures\RbacDemoFixtures;
use App\Module\Transport\Domain\Entity\Carrier;
use App\Module\Transport\Domain\Entity\CarrierAddress;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;

/**
 * Sous-ressource Adresse d'un transporteur (spec-back M4 § 4.5, ERP-159).
 * POST /api/carriers/{id}/addresses, PATCH/DELETE /api/carrier_addresses/{id}.
 *
 * Contrat verifie :
 *  - RG-4.06 : code postal hors ^[0-9]{4,5}$ -> 422 ;
 *  - RG-4.05 : transporteur affrete + adresse incomplete -> 422 (par champ) ;
 *  - RG-4.05 : transporteur affrete + adresse complete -> 201 ;
 *  - PATCH / DELETE OK avec transport.carriers.manage, 403 sans (view seul).
 *
 * @internal
 */
final class CarrierAddressApiTest extends AbstractCarrierApiTestCase
{
    private const string PWD = RbacDemoFixtures::DEMO_PASSWORD;

    protected function setUp(): void
    {
        parent::setUp();

        // Seed idempotent des roles + matrice § 5.2 + comptes demo (meme chemin
        // qu'en recette), requis pour les tests de permission (bureau/commerciale).
        self::bootKernel();
        $application = new Application(self::$kernel);
        $application->setAutoExit(false);
        $exit = $application->run(
            new ArrayInput([
                'command'           => 'app:seed-rbac',
                '--with-demo-users' => true,
                '--password'        => self::PWD,
            ]),
            new NullOutput(),
        );
        self::assertSame(0, $exit, 'app:seed-rbac a echoue (permissions transport.carriers.* synchronisees ?).');

        self::ensureKernelShutdown();
    }

    public function testInvalidPostalCodeReturns422(): void
    {
        // Transporteur NON affrete : RG-4.05 ne s'applique pas, seule RG-4.06 joue.
        $carrier = $this->seedCarrierWithChartered('Cp Invalide', false);
        $client  = $this->createAdminClient();

        $response = $client->request('POST', '/api/carriers/'.$carrier->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => ['postalCode' => '123'], // 3 chiffres -> Regex KO
        ]);
        self::assertResponseStatusCodeSame(422);
        // La 422 doit cibler le champ fautif (mapping inline ERP-101), pas juste le code HTTP.
        self::assertViolationOnPath($response, 'postalCode');
    }

    public function testInconsistentPostalCodeAndCityIsAccepted(): void
    {
        // RG-4.06 : la validation serveur borne le FORMAT du code postal
        // (^[0-9]{4,5}$) mais ne controle PAS la coherence CP <-> ville (deleguee
        // a l'autocomplete BAN cote front). Un CP valide avec une ville qui ne lui
        // correspond pas est donc accepte (201).
        $carrier = $this->seedCarrierWithChartered('Cp Ville Incoherents', false);
        $client  = $this->createAdminClient();

        $client->request('POST', '/api/carriers/'.$carrier->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'postalCode' => '86000', // Poitiers
                'city'       => 'Marseille', // incoherent, mais non controle
                'street'     => '1 rue de la Coherence',
            ],
        ]);
        self::assertResponseStatusCodeSame(201);
    }

    public function testCharteredCarrierIncompleteAddressReturns422(): void
    {
        // Transporteur affrete : RG-4.05 exige Pays/CP/Ville/Adresse. CP valide mais
        // ville + rue manquantes -> 422 conditionnelle (CarrierAddressProcessor).
        $carrier = $this->seedCarrierWithChartered('Affrete Incomplet', true);
        $client  = $this->createAdminClient();

        $response = $client->request('POST', '/api/carriers/'.$carrier->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => ['postalCode' => '86000'],
        ]);
        self::assertResponseStatusCodeSame(422);
        // RG-4.05 mappe une violation PAR champ manquant (ville + rue ici) -> chaque
        // erreur s'affiche inline sous son champ (ERP-101).
        self::assertViolationOnPath($response, 'city');
        self::assertViolationOnPath($response, 'street');
    }

    public function testCharteredCarrierCompleteAddressIsCreated(): void
    {
        $carrier = $this->seedCarrierWithChartered('Affrete Complet', true);
        $client  = $this->createAdminClient();

        $client->request('POST', '/api/carriers/'.$carrier->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'country'    => 'France',
                'postalCode' => '86000',
                'city'       => 'Poitiers',
                'street'     => '12 rue des Acacias',
            ],
        ]);
        self::assertResponseStatusCodeSame(201);
    }

    public function testPostAddressOnUnknownCarrierReturns404(): void
    {
        // Sous-ressource en read:false : le parent introuvable n'est plus intercepte
        // en amont -> le processor doit lever un 404 explicite (sinon 500 au persist).
        $client = $this->createAdminClient();

        $client->request('POST', '/api/carriers/999999/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => ['postalCode' => '86000', 'city' => 'Poitiers', 'street' => '1 rue X'],
        ]);
        self::assertResponseStatusCodeSame(404);
    }

    public function testPatchAndDeleteSucceedWithManage(): void
    {
        $address = $this->seedAddress('Patch Delete', false);
        $client  = $this->authenticatedClient('bureau', self::PWD); // manage (matrice § 5.2)

        // PATCH (manage) -> 200
        $client->request('PATCH', '/api/carrier_addresses/'.$address->getId(), [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['city' => 'Lyon'],
        ]);
        self::assertResponseStatusCodeSame(200);

        // DELETE (manage) -> 204
        $client->request('DELETE', '/api/carrier_addresses/'.$address->getId());
        self::assertResponseStatusCodeSame(204);
    }

    public function testWriteForbiddenWithoutManage(): void
    {
        $address = $this->seedAddress('Forbidden', false);
        $carrier = $address->getCarrier();
        self::assertNotNull($carrier);
        $client = $this->authenticatedClient('commerciale', self::PWD); // view seul

        $client->request('POST', '/api/carriers/'.$carrier->getId().'/addresses', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => ['postalCode' => '86000', 'city' => 'Poitiers', 'street' => '1 rue X'],
        ]);
        self::assertResponseStatusCodeSame(403);

        $client->request('PATCH', '/api/carrier_addresses/'.$address->getId(), [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['city' => 'Lyon'],
        ]);
        self::assertResponseStatusCodeSame(403);

        $client->request('DELETE', '/api/carrier_addresses/'.$address->getId());
        self::assertResponseStatusCodeSame(403);
    }

    /**
     * Seede un transporteur minimal en controlant le flag affrete (RG-4.05).
     */
    private function seedCarrierWithChartered(string $name, bool $isChartered): Carrier
    {
        $em      = $this->getEm();
        $carrier = new Carrier();
        $carrier->setName(mb_strtoupper($name, 'UTF-8'));
        $carrier->setCertificationType('GMP_PLUS');
        $carrier->setIsChartered($isChartered);
        $em->persist($carrier);
        $em->flush();

        return $carrier;
    }

    /**
     * Seede un transporteur + une adresse rattachee (pour les tests PATCH/DELETE).
     */
    private function seedAddress(string $name, bool $isChartered): CarrierAddress
    {
        $em      = $this->getEm();
        $carrier = $this->seedCarrierWithChartered($name, $isChartered);

        $address = new CarrierAddress();
        $address->setCarrier($carrier);
        $address->setPostalCode('86000');
        $address->setCity('Poitiers');
        $address->setStreet('12 rue des Acacias');
        $carrier->addAddress($address);
        $em->persist($address);
        $em->flush();

        return $address;
    }
}
