<?php

declare(strict_types=1);

namespace App\Command;

use App\Entity\Constructeur;
use App\Entity\ConstructeurCategorie;
use App\Entity\ConstructeurTelephone;
use Doctrine\ORM\EntityManagerInterface;
use SplObjectStorage;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

/**
 * Importe un référentiel de fournisseurs depuis un fichier JSON de la forme
 * {"count": N, "data": [{"reference": "...", "name": "...", "categoriesStr": "a, b", "organizationsStr": "...", "phone": "..."}, ...]}.
 *
 * Règles : on garde l'existant. Si un fournisseur du fichier porte le même nom (insensible à la casse/aux espaces)
 * qu'un fournisseur déjà en base, on le complète sans changer son id : on n'ajoute que les catégories et les
 * téléphones manquants, on n'écrase ni ne supprime jamais rien.
 */
#[AsCommand(
    name: 'app:import-fournisseurs',
    description: 'Importe/complète les fournisseurs depuis un fichier JSON (customer.json par défaut). Dry-run par défaut : utiliser --force pour écrire.',
)]
class ImportFournisseursCommand extends Command
{
    public function __construct(
        private readonly EntityManagerInterface $em,
        #[Autowire('%kernel.project_dir%')]
        private readonly string $projectDir,
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this
            ->addArgument('file', InputArgument::OPTIONAL, 'Chemin du fichier JSON', 'customer.json')
            ->addOption('force', null, InputOption::VALUE_NONE, 'Écrit réellement en base (sinon dry-run)')
            ->addOption('limit', null, InputOption::VALUE_REQUIRED, 'Ne traiter que les N premières entrées (debug)')
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io    = new SymfonyStyle($input, $output);
        $write = (bool) $input->getOption('force');
        $limit = null !== $input->getOption('limit') ? max(0, (int) $input->getOption('limit')) : null;

        $path = (string) $input->getArgument('file');
        if (!str_starts_with($path, '/')) {
            $path = rtrim($this->projectDir, '/').'/'.$path;
        }

        if (!is_file($path) || !is_readable($path)) {
            $io->error(sprintf('Fichier introuvable ou illisible : %s', $path));

            return Command::FAILURE;
        }

        $raw     = file_get_contents($path);
        $decoded = json_decode((string) $raw, true);
        if (!is_array($decoded) || !isset($decoded['data']) || !is_array($decoded['data'])) {
            $io->error('JSON invalide : la clé "data" (tableau) est attendue.');

            return Command::FAILURE;
        }

        /** @var array<int, array<string, mixed>> $rows */
        $rows = $decoded['data'];
        if (null !== $limit) {
            $rows = array_slice($rows, 0, $limit);
        }

        $io->title('Import fournisseurs');
        $io->writeln(sprintf('Fichier : <info>%s</info>', $path));
        $io->writeln(sprintf('Entrées : <info>%d</info>', count($rows)));
        $io->writeln($write ? '<comment>Mode écriture (--force)</comment>' : '<comment>Mode dry-run — aucune écriture. Ajouter --force pour appliquer.</comment>');
        $io->newLine();

        // --- Chargement des référentiels existants ---------------------------------
        /** @var array<string, Constructeur> $constructeursByName */
        $constructeursByName = [];
        foreach ($this->em->getRepository(Constructeur::class)->findAll() as $c) {
            $constructeursByName[$this->normalizeKey((string) $c->getName())] = $c;
        }

        /** @var array<string, ConstructeurCategorie> $categoriesByName */
        $categoriesByName = [];
        foreach ($this->em->getRepository(ConstructeurCategorie::class)->findAll() as $cat) {
            $categoriesByName[$this->normalizeKey((string) $cat->getName())] = $cat;
        }

        // numéros et liens catégorie déjà présents, indexés par objet Constructeur
        $seenNumeros  = new SplObjectStorage(); // Constructeur => array<string,true> (clé = numéro normalisé)
        $seenCatLinks = new SplObjectStorage(); // Constructeur => array<string,true> (clé = nom catégorie normalisé)

        // pré-remplissage pour les fournisseurs existants
        $existingTel = $this->em->getRepository(ConstructeurTelephone::class)->findAll();
        foreach ($existingTel as $tel) {
            $owner = $tel->getConstructeur();
            if (null === $owner) {
                continue;
            }
            $map                                                  = $seenNumeros[$owner] ?? [];
            $map[$this->normalizeKey((string) $tel->getNumero())] = true;
            $seenNumeros[$owner]                                  = $map;
        }

        /** @var array<int, array{cname: string, catname: string}> $catLinkPairs */
        $catLinkPairs = $this->em->createQuery(
            'SELECT c.name AS cname, cat.name AS catname FROM '.Constructeur::class.' c JOIN c.categories cat'
        )->getArrayResult();
        foreach ($catLinkPairs as $pair) {
            $cKey   = $this->normalizeKey((string) $pair['cname']);
            $catKey = $this->normalizeKey((string) $pair['catname']);
            $owner  = $constructeursByName[$cKey] ?? null;
            if (null === $owner) {
                continue;
            }
            $map                  = $seenCatLinks[$owner] ?? [];
            $map[$catKey]         = true;
            $seenCatLinks[$owner] = $map;
        }

        // --- Traitement ------------------------------------------------------------
        $created           = 0;
        $matched           = 0;
        $phonesAdded       = 0;
        $categoriesCreated = 0;
        $catLinksAdded     = 0;
        $skippedNoName     = 0;
        $tooLong           = [];

        $i = 0;
        foreach ($rows as $row) {
            ++$i;
            $name = trim((string) ($row['name'] ?? $row['reference'] ?? ''));
            if ('' === $name) {
                ++$skippedNoName;

                continue;
            }
            if (mb_strlen($name) > 255) {
                $tooLong[] = $name;
                $name      = mb_substr($name, 0, 255);
            }

            $key = $this->normalizeKey($name);
            if (isset($constructeursByName[$key])) {
                $constructeur = $constructeursByName[$key];
                ++$matched;
            } else {
                $constructeur = new Constructeur()->setName($name);
                if ($write) {
                    $this->em->persist($constructeur);
                }
                $constructeursByName[$key] = $constructeur;
                ++$created;
            }

            // --- téléphones ---
            foreach ($this->splitPhones((string) ($row['phone'] ?? '')) as $numero) {
                $numero = mb_substr($numero, 0, 50);
                $nKey   = $this->normalizeKey($numero);
                $map    = $seenNumeros[$constructeur] ?? [];
                if (isset($map[$nKey])) {
                    continue;
                }
                $tel = new ConstructeurTelephone()->setNumero($numero);
                $constructeur->addTelephone($tel);
                if ($write) {
                    $this->em->persist($tel);
                }
                $map[$nKey]                 = true;
                $seenNumeros[$constructeur] = $map;
                ++$phonesAdded;
            }

            // --- catégories ---
            foreach ($this->splitCategories((string) ($row['categoriesStr'] ?? '')) as $catName) {
                $catName = mb_substr($catName, 0, 255);
                $catKey  = $this->normalizeKey($catName);
                if (isset($categoriesByName[$catKey])) {
                    $categorie = $categoriesByName[$catKey];
                } else {
                    $categorie = new ConstructeurCategorie()->setName($catName);
                    if ($write) {
                        $this->em->persist($categorie);
                    }
                    $categoriesByName[$catKey] = $categorie;
                    ++$categoriesCreated;
                }

                $linkMap = $seenCatLinks[$constructeur] ?? [];
                if (isset($linkMap[$catKey])) {
                    continue;
                }
                $constructeur->addCategory($categorie);
                $linkMap[$catKey]            = true;
                $seenCatLinks[$constructeur] = $linkMap;
                ++$catLinksAdded;
            }

            if ($write && 0 === $i % 200) {
                $this->em->flush();
            }
        }

        if ($write) {
            $this->em->flush();
        }

        // --- Rapport ---------------------------------------------------------------
        $io->section('Résultat');
        $io->table(
            ['Action', 'Nombre'],
            [
                ['Fournisseurs créés', $created],
                ['Fournisseurs déjà en base (complétés si besoin)', $matched],
                ['Téléphones ajoutés', $phonesAdded],
                ['Catégories créées', $categoriesCreated],
                ['Liens fournisseur↔catégorie ajoutés', $catLinksAdded],
                ['Entrées ignorées (sans nom)', $skippedNoName],
                ['Noms tronqués (>255)', count($tooLong)],
            ]
        );

        if ($tooLong) {
            $io->warning(sprintf('%d nom(s) dépassaient 255 caractères et ont été tronqués.', count($tooLong)));
        }

        if ($write) {
            $io->success('Import terminé.');
        } else {
            $io->note('Dry-run : rien n\'a été écrit. Relancer avec --force pour appliquer.');
        }

        return Command::SUCCESS;
    }

    /**
     * @return list<string>
     */
    private function splitPhones(string $value): array
    {
        $parts = preg_split('#[/;\n\r]+#', $value) ?: [];
        $out   = [];
        foreach ($parts as $p) {
            $p = trim($p);
            if ('' !== $p) {
                $out[] = $p;
            }
        }

        return array_values(array_unique($out));
    }

    /**
     * @return list<string>
     */
    private function splitCategories(string $value): array
    {
        $parts = explode(',', $value);
        $out   = [];
        $seen  = [];
        foreach ($parts as $p) {
            $p = trim($p);
            if ('' === $p) {
                continue;
            }
            $k = $this->normalizeKey($p);
            if (isset($seen[$k])) {
                continue;
            }
            $seen[$k] = true;
            $out[]    = $p;
        }

        return $out;
    }

    private function normalizeKey(string $value): string
    {
        return mb_strtolower(trim(preg_replace('/\s+/u', ' ', $value) ?? $value));
    }
}
