<?php

declare(strict_types=1);

namespace App\Tests\Module\Transport\Api;

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

/**
 * Sous-ressource Contact d'un transporteur (spec-back M4 § 4.5, ERP-160).
 * POST /api/carriers/{id}/contacts, PATCH/DELETE /api/carrier_contacts/{id}.
 *
 * Contrat verifie :
 *  - RG-4.08 : contact totalement vide -> 422 (au moins 1 champ requis) ;
 *  - RG-4.08 : 1 seul champ rempli -> 201 ;
 *  - RG-4.08 : 3 telephones (tableau `phones`) -> 422 (max 2) ;
 *  - mapping `phones[]` -> phonePrimary / phoneSecondary + normalisation (RG-4.13) ;
 *  - PATCH / DELETE OK avec transport.carriers.manage, 403 sans (view seul).
 *
 * @internal
 */
final class CarrierContactApiTest 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 testEmptyContactReturns422(): void
    {
        // RG-4.08 : aucun champ rempli -> 422 (garde Processor, double du CHECK BDD).
        $carrier = $this->seedCarrier('Contact Vide');
        $client  = $this->createAdminClient();

        $response = $client->request('POST', '/api/carriers/'.$carrier->getId().'/contacts', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [],
        ]);
        self::assertResponseStatusCodeSame(422);
        // RG-4.08 : la violation est rattachee a `firstName` (mapping inline ERP-101).
        self::assertViolationOnPath($response, 'firstName');
    }

    public function testSingleFieldContactIsCreated(): void
    {
        // RG-4.08 : un seul champ suffit a valider le bloc.
        $carrier = $this->seedCarrier('Contact Mono');
        $client  = $this->createAdminClient();

        $client->request('POST', '/api/carriers/'.$carrier->getId().'/contacts', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => ['lastName' => 'martin'],
        ]);
        self::assertResponseStatusCodeSame(201);

        // RG-4.13 : nom capitalise serveur.
        self::assertJsonContains(['lastName' => 'Martin']);
    }

    public function testThirdPhoneReturns422(): void
    {
        // RG-4.08 : max 2 telephones. Le contrat d'ecriture accepte un tableau
        // `phones` (liste dynamique cote front « x1, +1 possible, max 2 ») ; un 3e
        // numero -> 422 rattachee au champ `phones`.
        $carrier = $this->seedCarrier('Contact Trois Tel');
        $client  = $this->createAdminClient();

        $response = $client->request('POST', '/api/carriers/'.$carrier->getId().'/contacts', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'firstName' => 'Jean',
                'phones'    => ['0611111111', '0622222222', '0633333333'],
            ],
        ]);
        self::assertResponseStatusCodeSame(422);
        // Le max-2 cible le champ virtuel `phones` (mapping inline ERP-101).
        self::assertViolationOnPath($response, 'phones');
    }

    public function testInvalidEmailReturns422(): void
    {
        // L'email du contact porte un Assert\Email (nouvelle contrainte M4) : une
        // adresse mal formee -> 422 ciblee sur `email`.
        $carrier = $this->seedCarrier('Contact Email Invalide');
        $client  = $this->createAdminClient();

        $response = $client->request('POST', '/api/carriers/'.$carrier->getId().'/contacts', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => ['lastName' => 'Durand', 'email' => 'pas-un-email'],
        ]);
        self::assertResponseStatusCodeSame(422);
        self::assertViolationOnPath($response, 'email');
    }

    public function testPostContactOnUnknownCarrierReturns404(): void
    {
        // Parent introuvable (read:false) -> 404 explicite du processor.
        $client = $this->createAdminClient();

        $client->request('POST', '/api/carriers/999999/contacts', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => ['lastName' => 'Martin'],
        ]);
        self::assertResponseStatusCodeSame(404);
    }

    public function testPhonesAreMappedAndNormalized(): void
    {
        // Mapping `phones[0]` -> phonePrimary, `phones[1]` -> phoneSecondary +
        // normalisation RG-4.13 (chiffres uniquement).
        $carrier = $this->seedCarrier('Contact Deux Tel');
        $client  = $this->createAdminClient();

        $client->request('POST', '/api/carriers/'.$carrier->getId().'/contacts', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => [
                'lastName' => 'Dupont',
                'phones'   => ['06.11.11.11.11', '06 22 22 22 22'],
            ],
        ]);
        self::assertResponseStatusCodeSame(201);
        self::assertJsonContains([
            'phonePrimary'   => '0611111111',
            'phoneSecondary' => '0622222222',
        ]);
    }

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

        // PATCH (manage) -> 200
        $client->request('PATCH', '/api/carrier_contacts/'.$contact->getId(), [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['jobTitle' => 'Directeur'],
        ]);
        self::assertResponseStatusCodeSame(200);

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

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

        $client->request('POST', '/api/carriers/'.$carrier->getId().'/contacts', [
            'headers' => ['Content-Type' => self::LD],
            'json'    => ['lastName' => 'Bernard'],
        ]);
        self::assertResponseStatusCodeSame(403);

        $client->request('PATCH', '/api/carrier_contacts/'.$contact->getId(), [
            'headers' => ['Content-Type' => self::MERGE],
            'json'    => ['jobTitle' => 'Chef'],
        ]);
        self::assertResponseStatusCodeSame(403);

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

    /**
     * Seede un transporteur + un contact rattache (pour les tests PATCH/DELETE).
     */
    private function seedContact(string $name): CarrierContact
    {
        $em      = $this->getEm();
        $carrier = $this->seedCarrier($name);

        $contact = new CarrierContact();
        $contact->setCarrier($carrier);
        $contact->setLastName('Martin');
        $contact->setPhonePrimary('0612345678');
        $carrier->addContact($contact);
        $em->persist($contact);
        $em->flush();

        return $contact;
    }
}
