<?php

declare(strict_types=1);

namespace App\Module\Catalog\Infrastructure\DataFixtures;

use App\Module\Catalog\Domain\Entity\Category;
use App\Module\Catalog\Domain\Entity\CategoryType;
use App\Module\Catalog\Domain\Repository\CategoryTypeRepositoryInterface;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Persistence\ObjectManager;
use RuntimeException;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

/**
 * Fixtures dev/test du module Catalog : categories de demonstration rattachees
 * a leur CategoryType. Le type CLIENT porte ~11 categories clients (refonte
 * taxonomie ERP-78) ; le type FOURNISSEUR porte les categories fournisseurs
 * (ERP-84 : Negociant, Cooperative...) ; le type PRESTATAIRE porte les categories
 * prestataires (M3 1.1 : Maintenance industrielle, Nettoyage, Transport). Chaque
 * categorie porte un `code` stable.
 * Alimente le repertoire clients (ClientFixtures, module Commercial) avec des
 * donnees realistes couvrant RG-1.03 (codes DISTRIBUTEUR / COURTIER) et RG-1.29
 * (codes interdits sur adresse), et le multi-select Categorie fournisseur (M2).
 *
 * Depend de CategoryTypeFixtures : les types CLIENT et FOURNISSEUR doivent etre
 * seedes avant de pouvoir y rattacher des Category.
 *
 * Idempotence : lookup par `code` parmi les categories non supprimees (deletedAt
 * null), coherent avec l'index unique partiel uq_category_code (code WHERE
 * deleted_at IS NULL). Rejouable sans doublon meme si le purger Doctrine est
 * desactive.
 *
 * 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
 * categories (prefixe dedie) et comptent sur une table `category` vierge — y
 * injecter des categories de demo casserait comptages et cleanups FK
 * (client_category). Cf. ClientFixtures (meme garde-fou).
 */
class CategoryFixtures extends Fixture implements DependentFixtureInterface
{
    /**
     * Categories de demonstration par code de type. Les 4 premieres categories
     * CLIENT (Distributeur / Courtier / Secteur / Autre) sont les categories
     * « systeme » reportees des anciens types ; leurs codes pilotent les RG.
     * Les categories FOURNISSEUR (ERP-84) miroir de la migration
     * Version20260605120000. Chaque valeur : nom => code stable.
     *
     * @var array<string, array<string, string>>
     */
    private const CATEGORIES_BY_TYPE = [
        'CLIENT' => [
            'Distributeur'         => 'DISTRIBUTEUR',
            'Courtier'             => 'COURTIER',
            'Secteur'              => 'SECTEUR',
            'Autre'                => 'AUTRE',
            'BTP'                  => 'BTP',
            'Industrie'            => 'INDUSTRIE',
            'Agro-alimentaire'     => 'AGRO_ALIMENTAIRE',
            'Transport/Logistique' => 'TRANSPORT_LOGISTIQUE',
            'Services'             => 'SERVICES',
            'Association'          => 'ASSOCIATION',
            'Indépendant'          => 'INDEPENDANT',
        ],
        'FOURNISSEUR' => [
            'Négociant'   => 'NEGOCIANT',
            'Coopérative' => 'COOPERATIVE',
            'Producteur'  => 'PRODUCTEUR',
            'Grossiste'   => 'GROSSISTE',
            'Importateur' => 'IMPORTATEUR',
        ],
        'PRESTATAIRE' => [
            'Maintenance industrielle' => 'MAINTENANCE_INDUSTRIELLE',
            'Nettoyage'                => 'NETTOYAGE',
            'Transport'                => 'TRANSPORT',
        ],
    ];

    public function __construct(
        private readonly CategoryTypeRepositoryInterface $categoryTypeRepository,
        #[Autowire('%kernel.environment%')]
        private readonly string $environment,
    ) {}

    /**
     * @return array<int, class-string>
     */
    public function getDependencies(): array
    {
        return [CategoryTypeFixtures::class];
    }

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

        // Index des types presents par code, pour rattacher chaque categorie.
        $typesByCode = [];
        foreach ($this->categoryTypeRepository->findAllOrderedByLabel() as $type) {
            $typesByCode[$type->getCode()] = $type;
        }

        foreach (self::CATEGORIES_BY_TYPE as $typeCode => $categories) {
            $type = $typesByCode[$typeCode] ?? null;

            if (!$type instanceof CategoryType) {
                // Misconfiguration : CategoryTypeFixtures n'a pas seede ce type.
                throw new RuntimeException(sprintf(
                    'CategoryTypeFixtures doit avoir seede le type "%s" avant CategoryFixtures.',
                    $typeCode,
                ));
            }

            foreach ($categories as $name => $code) {
                $this->ensureCategory($manager, $name, $code, $type);
            }
        }

        $manager->flush();
    }

    /**
     * Cree la categorie (name, code) sous le type fourni si son code n'existe pas
     * encore parmi les categories actives, sinon la laisse en place. Lookup
     * aligne sur l'index unique partiel uq_category_code.
     */
    private function ensureCategory(ObjectManager $manager, string $name, string $code, CategoryType $type): void
    {
        $existing = $manager->getRepository(Category::class)->findOneBy([
            'code'      => $code,
            'deletedAt' => null,
        ]);

        if (null !== $existing) {
            return;
        }

        $category = new Category();
        $category->setName($name);
        $category->setCode($code);
        $category->addCategoryType($type);
        $manager->persist($category);
    }
}
