<?php

declare(strict_types=1);

namespace App\Module\Transport\Infrastructure\DataFixtures;

use App\Module\Commercial\Infrastructure\DataFixtures\ClientFixtures;
use App\Module\Commercial\Infrastructure\DataFixtures\SupplierFixtures;
use App\Module\Sites\Infrastructure\DataFixtures\SitesFixtures;
use App\Module\Transport\Application\Service\CarrierFieldNormalizer;
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\Shared\Domain\Contract\ClientAddressInterface;
use App\Shared\Domain\Contract\SiteInterface;
use App\Shared\Domain\Contract\SiteProviderInterface;
use App\Shared\Domain\Contract\SupplierAddressInterface;
use App\Shared\Domain\Entity\UploadedDocument;
use DateTimeImmutable;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ObjectManager;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

/**
 * Fixtures dev/demo du repertoire transporteurs (M4) couvrant l'ensemble des cas
 * metier RG-4.xx, jumelles des fixtures fournisseurs (M2). C'est ICI que vivent
 * les fixtures COMPLETES (les maillons WT precedents s'etaient limites a un stub
 * de lecture). Cas pivots seedes (§ 8.4) :
 *  - 1 transporteur QUALIMAT (lien `qualimat_carrier` + adresse copiee +
 *    validityDate PASSEE pour exercer le fond rouge RG-4.04) ;
 *  - 1 transporteur AUTRE + Decharge (UploadedDocument, RG-4.02) ;
 *  - 1 transporteur affrete (indexation + benne + volume obligatoires, RG-4.03) ;
 *  - 1 transporteur LIOT (immatriculations, certification non requise, RG-4.01) ;
 *  - 1 transporteur COMPLET : contacts + adresses + prix CLIENT et FOURNISSEUR ;
 *  - 1 transporteur archive (exclusion liste + restauration, RG-4.14).
 *
 * Resolution inter-modules conforme a la regle n°1 (pas d'import de logique) :
 *  - sites resolus via le contrat Shared SiteProviderInterface ;
 *  - client/adresse et fournisseur/adresse des prix resolus via les contrats
 *    Shared ClientAddressInterface / SupplierAddressInterface (relations ORM
 *    partagees, RG-4.10/4.11). Si la demo Commercial/Sites n'est pas chargee, les
 *    prix sont simplement omis (le reste de la fiche reste seede).
 *
 * Normalisation : valeurs fournies BRUTES puis normalisees par
 * CarrierFieldNormalizer avant persist, comme le ferait le CarrierProcessor via
 * l'API (name UPPERCASE, first/last Capitalize, telephones chiffres seuls, email
 * lowercase, liotPlates « ; »-normalise).
 *
 * Idempotence : lookup par `name` normalise (coherent avec l'index unique partiel
 * uq_carrier_name_active). Un transporteur deja present n'est pas reconstruit (ses
 * sous-collections ne sont pas redupliquees). Rejouable sans doublon.
 *
 * Audit / Blamable : persist hors contexte HTTP -> created_by / updated_by
 * restent null (« Systeme » cote front), c'est attendu.
 *
 * Portee : DONNEES DE DEMONSTRATION (dev uniquement). En environnement `test`, la
 * fixture ne charge rien : les tests seedent et nettoient leurs propres
 * transporteurs et comptent sur une table `carrier` vierge — y injecter des
 * transporteurs de demo casserait les comptages de liste et les cleanups. Meme
 * garde-fou que ClientFixtures / SupplierFixtures.
 */
class CarrierFixtures extends Fixture implements DependentFixtureInterface
{
    /** SIRET de la ligne qualimat_carrier de demo (cle naturelle, insert idempotent). */
    private const string QUALIMAT_DEMO_SIRET = '90000000000017';

    public function __construct(
        private readonly CarrierFieldNormalizer $normalizer,
        private readonly SiteProviderInterface $siteProvider,
        #[Autowire('%kernel.environment%')]
        private readonly string $environment,
    ) {}

    /**
     * @return array<int, class-string>
     */
    public function getDependencies(): array
    {
        // Les prix referencent des Client/Supplier/Site de demo (relations ORM
        // partagees) : ces fixtures doivent tourner avant.
        return [
            SitesFixtures::class,
            ClientFixtures::class,
            SupplierFixtures::class,
        ];
    }

    public function load(ObjectManager $manager): void
    {
        // Donnees de demo : dev uniquement. En test, on laisse la table vierge.
        if ('test' === $this->environment) {
            return;
        }

        // === Transporteur QUALIMAT (RG-4.01) — adresse copiee + validite PASSEE (RG-4.04) ===
        [$grelillier, $isNew] = $this->ensureCarrier($manager, 'Transports Grelillier');
        if ($isNew) {
            $grelillier->setQualimatCarrier($this->ensureQualimatDemoLine($manager));
            $grelillier->setCertificationType('QUALIMAT');
            // Adresse pre-remplie depuis la copie QUALIMAT (RG-4.05).
            $this->addAddress($grelillier, '86000', 'Poitiers', '12 rue des Acacias');
            $this->addContact($grelillier, 'Marie', 'Martin', 'Exploitation', '06 12 34 56 78', null, 'marie.martin@grelillier.fr');
        }

        // === Transporteur AUTRE + Decharge (RG-4.02) ===
        [$pandele, $isNew] = $this->ensureCarrier($manager, 'Transports Pandele');
        if ($isNew) {
            $pandele->setCertificationType('AUTRE');
            $pandele->setDischargeDocument($this->buildDischargeDocument($manager));
            $this->addContact($pandele, 'Luc', 'Pandele', 'Gerant', '05 49 11 22 33', null, 'luc.pandele@pandele.fr');
        }

        // === Transporteur affrete (RG-4.03) — indexation + benne + volume ===
        [$affrete, $isNew] = $this->ensureCarrier($manager, 'Affreteurs Reunis');
        if ($isNew) {
            $affrete->setCertificationType('GMP_PLUS');
            $affrete->setIsChartered(true);
            $affrete->setIndexationRate('5.00');
            $affrete->setContainerType('BENNE');
            $affrete->setVolumeM3('90.00');
            $this->addAddress($affrete, '17000', 'La Rochelle', '4 quai des Affreteurs');
        }

        // === Cas LIOT (RG-4.01) — immatriculations, certification non requise ===
        [$liot, $isNew] = $this->ensureCarrier($manager, 'LIOT');
        if ($isNew) {
            $liot->setLiotPlates($this->normalizer->normalizeLiotPlates('ab-123-cd ; ef-456-gh ; gh-789-ij'));
        }

        // === Transporteur COMPLET — contacts + adresses + prix CLIENT et FOURNISSEUR ===
        [$complet, $isNew] = $this->ensureCarrier($manager, 'Transports Logistique Globale');
        if ($isNew) {
            $complet->setCertificationType('OVOCOM');
            $this->addAddress($complet, '86100', 'Châtellerault', '20 zone des Transporteurs');
            $this->addContact($complet, 'Sophie', 'Bernard', 'Directrice', '05 49 44 55 66', '06 99 88 77 66', 'sophie.bernard@logistique-globale.fr', 0);
            $this->addContact($complet, 'Marc', 'Lopez', 'Affretement', '05 49 44 55 67', null, 'marc.lopez@logistique-globale.fr', 1);
            $this->addPrices($manager, $complet);
        }

        // === Transporteur archive (RG-4.14) ===
        [$archive, $isNew] = $this->ensureCarrier($manager, 'Transports Anciens', isArchived: true);
        if ($isNew) {
            $archive->setCertificationType('COMPTE_PROPRE');
            $this->addContact($archive, 'Paul', 'Ancien', 'Ex-gerant', '05 49 00 00 00', null, 'paul.ancien@anciens.fr');
        }

        $manager->flush();
    }

    /**
     * Cree un transporteur (nom normalise UPPERCASE) s'il n'existe pas encore,
     * sinon retourne l'existant. Retourne [Carrier, isNew] : isNew=false bloque la
     * reconstruction des sous-collections (idempotence sans doublon).
     *
     * @return array{0: Carrier, 1: bool}
     */
    private function ensureCarrier(ObjectManager $manager, string $name, bool $isArchived = false): array
    {
        $normalizedName = (string) $this->normalizer->normalizeName($name);

        $existing = $manager->getRepository(Carrier::class)->findOneBy(['name' => $normalizedName]);
        if ($existing instanceof Carrier) {
            return [$existing, false];
        }

        $carrier = new Carrier();
        $carrier->setName($normalizedName);

        if ($isArchived) {
            $carrier->setIsArchived(true);
            $carrier->setArchivedAt(new DateTimeImmutable());
        }

        $manager->persist($carrier);

        return [$carrier, true];
    }

    /**
     * Ajoute une adresse au transporteur (cascade persist via Carrier.addresses).
     */
    private function addAddress(Carrier $carrier, string $postalCode, string $city, string $street): void
    {
        $address = new CarrierAddress();
        $address->setPostalCode($postalCode);
        $address->setCity($city);
        $address->setStreet($street);
        $carrier->addAddress($address);
    }

    /**
     * Ajoute un contact normalise au transporteur (cascade persist via
     * Carrier.contacts). Au moins un champ est toujours fourni (RG-4.08).
     */
    private function addContact(
        Carrier $carrier,
        ?string $firstName,
        ?string $lastName,
        ?string $jobTitle,
        ?string $phonePrimary,
        ?string $phoneSecondary,
        ?string $email,
        int $position = 0,
    ): void {
        $contact = new CarrierContact();
        $contact->setFirstName($this->normalizer->normalizePersonName($firstName));
        $contact->setLastName($this->normalizer->normalizePersonName($lastName));
        $contact->setJobTitle($jobTitle);
        $contact->setPhonePrimary($this->normalizer->normalizePhone($phonePrimary));
        $contact->setPhoneSecondary($this->normalizer->normalizePhone($phoneSecondary));
        $contact->setEmail($this->normalizer->normalizeEmail($email));
        $contact->setPosition($position);

        $carrier->addContact($contact);
    }

    /**
     * Ajoute un prix CLIENT et un prix FOURNISSEUR au transporteur (RG-4.10/4.11),
     * en resolvant les relations cross-module (client/adresse de livraison + site
     * de depart ; fournisseur/adresse d'appro + site de livraison) via les contrats
     * Shared. Si la demo Commercial/Sites n'est pas disponible, les prix sont omis.
     */
    private function addPrices(ObjectManager $manager, Carrier $carrier): void
    {
        $site = $this->siteProvider->findByName('Chatellerault');

        // Branche CLIENT (RG-4.10) : 1ere adresse de livraison de la demo M1.
        $clientAddress = $manager->getRepository(ClientAddressInterface::class)->findOneBy(['isDelivery' => true]);
        if ($site instanceof SiteInterface && $clientAddress instanceof ClientAddressInterface && null !== $clientAddress->getClient()) {
            $clientPrice = new CarrierPrice();
            $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);
        }

        // Branche FOURNISSEUR (RG-4.11) : 1ere adresse de DEPART de la demo M2.
        $supplierAddress = $manager->getRepository(SupplierAddressInterface::class)->findOneBy(['addressType' => 'DEPART']);
        if ($site instanceof SiteInterface && $supplierAddress instanceof SupplierAddressInterface && null !== $supplierAddress->getSupplier()) {
            $supplierPrice = new CarrierPrice();
            $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);
        }
    }

    /**
     * Construit (non persiste explicitement — cascade via la FK Carrier) un
     * UploadedDocument de demo pour la Decharge (RG-4.02). Pas de fichier reel sur
     * disque : metadonnees factices suffisantes pour la demo.
     */
    private function buildDischargeDocument(ObjectManager $manager): UploadedDocument
    {
        $document = new UploadedDocument(
            'decharge-demo.pdf',
            'demo/decharge-demo.pdf',
            'application/pdf',
            12_345,
            str_repeat('0', 64),
            new DateTimeImmutable(),
        );
        $manager->persist($document);

        return $document;
    }

    /**
     * Insere (idempotent, par SIRET) une ligne `qualimat_carrier` de demo a
     * validite PASSEE (RG-4.04) puis retourne l'entite (lecture seule) rechargee.
     * La table est normalement alimentee par `app:qualimat:sync` ; en demo on pose
     * une ligne directe en DBAL (l'entite mappee n'expose aucune ecriture API).
     */
    private function ensureQualimatDemoLine(ObjectManager $manager): QualimatCarrier
    {
        $repository = $manager->getRepository(QualimatCarrier::class);
        $existing   = $repository->findOneBy(['siret' => self::QUALIMAT_DEMO_SIRET]);
        if ($existing instanceof QualimatCarrier) {
            return $existing;
        }

        if ($manager instanceof EntityManagerInterface) {
            $manager->getConnection()->insert('qualimat_carrier', [
                'siret'       => self::QUALIMAT_DEMO_SIRET,
                'name'        => 'TRANSPORTS GRELILLIER',
                'address'     => '12 rue des Acacias',
                'postal_code' => '86000',
                'city'        => 'Poitiers',
                'status'      => 'Valide',
                // Validite PASSEE : exerce le fond rouge RG-4.04 cote front.
                'validity_date'  => '2024-12-31',
                'is_active'      => 'true',
                'last_synced_at' => new DateTimeImmutable()->format('Y-m-d H:i:s'),
            ]);
        }

        // @var QualimatCarrier $line
        return $repository->findOneBy(['siret' => self::QUALIMAT_DEMO_SIRET]);
    }
}
