<?php

declare(strict_types=1);

namespace App\Tests\Module\Transport\Api;

use ApiPlatform\Symfony\Bundle\Test\Client;
use App\Module\Commercial\Domain\Entity\Client as ClientEntity;
use App\Module\Commercial\Domain\Entity\ClientAddress;
use App\Module\Commercial\Domain\Entity\Supplier;
use App\Module\Commercial\Domain\Entity\SupplierAddress;
use App\Module\Sites\Domain\Entity\Site;
use App\Module\Transport\Domain\Entity\Carrier;
use App\Module\Transport\Domain\Entity\CarrierAddress;
use App\Module\Transport\Domain\Entity\CarrierContact;
use App\Module\Transport\Domain\Entity\CarrierPrice;
use App\Module\Transport\Domain\Entity\QualimatCarrier;
use App\Tests\Module\Core\Api\AbstractApiTestCase;
use DateTimeImmutable;
use Symfony\Contracts\HttpClient\ResponseInterface;

/**
 * Base des tests fonctionnels du repertoire transporteurs (M4). Apporte les
 * factories de seed direct (sans passer par l'API : le flux d'ecriture arrive
 * au WT4) pour les tests de lecture / serialisation / contrat (DoD § 4.0.bis).
 *
 * Donnees (RETEX M1) : chaque test seede ses transporteurs ; le tearDown les
 * purge (cascade BDD sur les sous-collections) ainsi que les lignes
 * qualimat_carrier de test (prefixe SIRET dedie).
 *
 * @internal
 */
abstract class AbstractCarrierApiTestCase extends AbstractApiTestCase
{
    protected const string LD    = 'application/ld+json';
    protected const string MERGE = 'application/merge-patch+json';

    /** Prefixe SIRET des lignes qualimat_carrier seedees par les tests (purge ciblee). */
    private const string TEST_SIRET_PREFIX = 'TESTQ';

    /** Prefixe des Client/Supplier de test (cross-module Prix) — purge ciblee. */
    private const string TEST_REF_PREFIX = 'TESTCARRIERREF';

    protected function tearDown(): void
    {
        $em = $this->getEm();
        // Carrier d'abord : ON DELETE CASCADE purge carrier_price (FK RESTRICT vers
        // client/supplier), liberant les Client/Supplier de test pour leur purge.
        $em->createQuery('DELETE FROM '.Carrier::class)->execute();
        $em->createQuery('DELETE FROM '.ClientEntity::class.' c WHERE c.companyName LIKE :p')
            ->setParameter('p', self::TEST_REF_PREFIX.'%')->execute()
        ;
        $em->createQuery('DELETE FROM '.Supplier::class.' s WHERE s.companyName LIKE :p')
            ->setParameter('p', self::TEST_REF_PREFIX.'%')->execute()
        ;
        // qualimat_carrier : insere en DBAL brut (entite lecture seule) -> purge DBAL.
        $em->getConnection()->executeStatement(
            'DELETE FROM qualimat_carrier WHERE siret LIKE :p',
            ['p' => self::TEST_SIRET_PREFIX.'%'],
        );

        parent::tearDown();
    }

    protected function createAdminClient(): Client
    {
        return $this->authenticatedClient('admin', 'admin');
    }

    /**
     * Garde-fou ERP-101 : verifie qu'une reponse 422 porte une violation sur le
     * `propertyPath` attendu (et pas seulement le bon code HTTP). Sans cette
     * assertion, une 422 venue d'une AUTRE cause (autre champ manquant, IRI 404)
     * ferait passer le test au vert sans prouver le mapping inline par champ.
     *
     * Mutualise dans la base (au lieu d'un duplicata par fichier) pour que toute
     * la stack d'ecriture (formulaire principal + sous-ressources) l'utilise.
     */
    protected static function assertViolationOnPath(object $response, string $path): void
    {
        /** @var ResponseInterface $response */
        $paths = array_column($response->toArray(false)['violations'] ?? [], 'propertyPath');

        self::assertContains(
            $path,
            $paths,
            sprintf('Aucune violation sur "%s" (paths: %s).', $path, implode(', ', $paths)),
        );
    }

    /**
     * Payload minimal valide du formulaire principal (transporteur non-QUALIMAT,
     * non affrete) : nom + certification GMP_PLUS. Sert de base aux tests
     * d'ecriture / RBAC.
     *
     * @return array<string, mixed>
     */
    protected function validMainPayload(string $name): array
    {
        return [
            'name'              => $name,
            'certificationType' => 'GMP_PLUS',
            'isChartered'       => false,
        ];
    }

    /**
     * Seede un transporteur minimal (nom en MAJUSCULES, comme le ferait le
     * futur Processor). Sert aux tests de liste / archivage.
     */
    protected function seedCarrier(string $name, bool $isArchived = false): Carrier
    {
        $em      = $this->getEm();
        $carrier = new Carrier();
        $carrier->setName(mb_strtoupper($name, 'UTF-8'));
        $carrier->setCertificationType('GMP_PLUS');
        $carrier->setIsArchived($isArchived);
        if ($isArchived) {
            $carrier->setArchivedAt(new DateTimeImmutable());
        }
        $em->persist($carrier);
        $em->flush();

        return $carrier;
    }

    /**
     * Seede un transporteur COMPLET (sans passer par l'API) : lien QUALIMAT,
     * 1 adresse, 1 contact, et 2 prix couvrant les deux branches (CLIENT avec
     * client + adresse de livraison + site de depart ; FOURNISSEUR avec
     * fournisseur + adresse d'appro + site de livraison). Socle du contrat de
     * serialisation et de la DoD (§ 4.0.bis).
     */
    protected function seedCompleteCarrier(string $name): Carrier
    {
        $em     = $this->getEm();
        $suffix = substr(bin2hex(random_bytes(3)), 0, 6);

        $qualimat = $this->seedQualimatCarrier($name);

        $carrier = new Carrier();
        $carrier->setName(mb_strtoupper($name.' '.$suffix, 'UTF-8'));
        $carrier->setQualimatCarrier($qualimat);
        $carrier->setCertificationType('QUALIMAT');
        $em->persist($carrier);

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

        $contact = new CarrierContact();
        $contact->setCarrier($carrier);
        $contact->setFirstName('Marie');
        $contact->setLastName('Martin');
        $contact->setPhonePrimary('0612345678');
        $contact->setEmail('marie.martin@seed.test');
        $carrier->addContact($contact);
        $em->persist($contact);

        // Refs cross-module : seedees localement (en test, les fixtures M1/M2 ne
        // sont pas chargees — seuls les sites le sont). Prouve l'embed via les
        // contrats Shared + resolve_target_entities (regle n°1).
        $site = $em->getRepository(Site::class)->findOneBy([]);
        self::assertNotNull($site, 'Un site fixture est requis (SitesFixtures).');

        $clientAddress   = $this->seedClientWithAddress($name.' '.$suffix);
        $supplierAddress = $this->seedSupplierWithAddress($name.' '.$suffix);

        // Branche CLIENT (RG-4.10).
        $clientPrice = new CarrierPrice();
        $clientPrice->setCarrier($carrier);
        $clientPrice->setDirection('CLIENT');
        $clientPrice->setClient($clientAddress->getClient());
        $clientPrice->setClientDeliveryAddress($clientAddress);
        $clientPrice->setDepartureSite($site);
        $clientPrice->setContainerType('BENNE');
        $clientPrice->setPricingUnit('TONNE');
        $clientPrice->setPrice('42.50');
        $clientPrice->setPriceState('VALIDE');
        $carrier->addPrice($clientPrice);
        $em->persist($clientPrice);

        // Branche FOURNISSEUR (RG-4.11).
        $supplierPrice = new CarrierPrice();
        $supplierPrice->setCarrier($carrier);
        $supplierPrice->setDirection('FOURNISSEUR');
        $supplierPrice->setSupplier($supplierAddress->getSupplier());
        $supplierPrice->setSupplierSupplyAddress($supplierAddress);
        $supplierPrice->setDeliverySite($site);
        $supplierPrice->setContainerType('FOND_MOUVANT');
        $supplierPrice->setPricingUnit('FORFAIT');
        $supplierPrice->setPrice('320.00');
        $supplierPrice->setPriceState('EN_COURS');
        $carrier->addPrice($supplierPrice);
        $em->persist($supplierPrice);

        $em->flush();

        return $carrier;
    }

    /**
     * Seede un Client minimal (companyName prefixe pour la purge) + une adresse
     * de livraison valide (CHECKs client_address respectes). Retourne l'adresse.
     */
    protected function seedClientWithAddress(string $label): ClientAddress
    {
        $em     = $this->getEm();
        $suffix = substr(bin2hex(random_bytes(3)), 0, 6);

        $client = new ClientEntity();
        $client->setCompanyName(mb_strtoupper(self::TEST_REF_PREFIX.' CLI '.$label.' '.$suffix, 'UTF-8'));
        $em->persist($client);

        $address = new ClientAddress();
        $address->setClient($client);
        // Adresse de livraison : is_delivery=true, is_prospect=false, is_billing=false
        // -> satisfait chk_client_address_prospect_exclusive + chk_client_address_billing_email.
        $address->setIsDelivery(true);
        $address->setPostalCode('86000');
        $address->setCity('Poitiers');
        $address->setStreet('1 rue de la Livraison');
        $em->persist($address);

        return $address;
    }

    /**
     * Seede un Supplier minimal (companyName prefixe pour la purge) + une adresse
     * d'approvisionnement valide (address_type DEPART). Retourne l'adresse.
     */
    protected function seedSupplierWithAddress(string $label): SupplierAddress
    {
        $em     = $this->getEm();
        $suffix = substr(bin2hex(random_bytes(3)), 0, 6);

        $supplier = new Supplier();
        $supplier->setCompanyName(mb_strtoupper(self::TEST_REF_PREFIX.' FRN '.$label.' '.$suffix, 'UTF-8'));
        $em->persist($supplier);

        $address = new SupplierAddress();
        $address->setSupplier($supplier);
        $address->setAddressType('DEPART');
        $address->setPostalCode('17000');
        $address->setCity('La Rochelle');
        $address->setStreet('2 quai de l Appro');
        $em->persist($address);

        return $address;
    }

    /**
     * Insere une ligne qualimat_carrier de test en DBAL brut (l'entite mappee est
     * en lecture seule) et retourne l'entite rechargee. SIRET prefixe pour la purge.
     */
    protected function seedQualimatCarrier(string $name): QualimatCarrier
    {
        $em    = $this->getEm();
        $siret = self::TEST_SIRET_PREFIX.substr(bin2hex(random_bytes(6)), 0, 9);

        $em->getConnection()->insert('qualimat_carrier', [
            'siret'          => $siret,
            'name'           => mb_strtoupper($name, 'UTF-8'),
            'address'        => '12 rue des Acacias',
            'postal_code'    => '86000',
            'city'           => 'Poitiers',
            'status'         => 'Valide',
            'validity_date'  => '2027-12-31',
            'is_active'      => 'true',
            'last_synced_at' => new DateTimeImmutable()->format('Y-m-d H:i:s'),
        ]);

        $qualimat = $em->getRepository(QualimatCarrier::class)->findOneBy(['siret' => $siret]);
        self::assertNotNull($qualimat, 'La ligne qualimat_carrier de test doit etre rechargeable.');

        return $qualimat;
    }
}
