<?php

namespace App\Command;


use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use PDO;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;


class UpdateRppsAddressCommand extends Command
{
    protected static $defaultName = 'update:ref:address';
    private PDO $pdo;
    private string $banCsvUrl;

    public function __construct(
        private EntityManagerInterface $em,
        private HttpClientInterface $httpClient,
        private readonly ParameterBagInterface $params
    ) {
        $this->banCsvUrl = $this->params->get('address.ban.link');
        parent::__construct();
        $connection = $em->getConnection();
        $this->pdo = $connection->getNativeConnection();
    }

    protected function configure(): void
    {
        $this
            ->setDescription('Télécharge et importe automatiquement les données BAN dans la table "address"')
            ->addOption('skip-download', null, InputOption::VALUE_NONE, 'Utiliser le fichier local existant sans télécharger')
            ->addOption('csv-path', null, InputOption::VALUE_OPTIONAL, 'Chemin du fichier CSV local', $this->params->get('address.csv.path'))
            ->addOption('batch-size', null, InputOption::VALUE_OPTIONAL, 'Taille du batch', 5000)
            ->addOption('log-path', null, InputOption::VALUE_OPTIONAL, 'Chemin du fichier log', $this->params->get('log.path'));
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $io = new SymfonyStyle($input, $output);
        $csvPath = $input->getOption('csv-path');
        $batchSize = (int) $input->getOption('batch-size');
        $logPath = $input->getOption('log-path');
        $skipDownload = $input->getOption('skip-download');
        $table = 'address';

        // Créer le dossier var/data s'il n'existe pas
        $dataDir = dirname($csvPath);
        if (!is_dir($dataDir)) {
            mkdir($dataDir, 0755, true);
        }

        // Téléchargement automatique du fichier
        if (!$skipDownload) {
            $io->section('📥 Téléchargement du fichier BAN...');

            try {
                $downloadResult = $this->downloadAndExtractCsv($csvPath, $io);
                if (!$downloadResult) {
                    return Command::FAILURE;
                }
            } catch (\Exception $e) {
                $io->error('Erreur lors du téléchargement : ' . $e->getMessage());
                $io->note('Utilisez --skip-download pour utiliser le fichier local existant.');
                return Command::FAILURE;
            }
        } else {
            $io->note('⏭️  Téléchargement ignoré, utilisation du fichier local.');
        }

        // Vérifier que le fichier existe
        if (!file_exists($csvPath)) {
            $io->error("Fichier CSV introuvable : $csvPath");
            return Command::FAILURE;
        }

        // Afficher les infos du fichier
        $fileSize = filesize($csvPath);
        $io->info(sprintf('📄 Fichier : %s (%.2f MB)', basename($csvPath), $fileSize / 1024 / 1024));

        // Récupérer les colonnes de la table
        $stmt = $this->pdo->query("SHOW COLUMNS FROM `$table`");
        $allDbColumns = $stmt->fetchAll(PDO::FETCH_ASSOC);

        // Identifier la clé primaire auto-increment à exclure
        $primaryKey = null;
        foreach ($allDbColumns as $col) {
            if ($col['Key'] === 'PRI' && strpos($col['Extra'], 'auto_increment') !== false) {
                $primaryKey = $this->normalizeColumn($col['Field']);
                break;
            }
        }

        // Créer la liste des colonnes sans la clé primaire
        $dbColumns = [];
        foreach ($allDbColumns as $col) {
            $normalized = $this->normalizeColumn($col['Field']);
            if ($normalized !== $primaryKey) {
                $dbColumns[] = $normalized;
            }
        }

        // Lire le header du CSV
        $handle = fopen($csvPath, 'r');
        if (!$handle) {
            $io->error('Impossible d\'ouvrir le fichier CSV.');
            return Command::FAILURE;
        }

        $csvHeader = fgetcsv($handle, 0, ';');
        $csvHeader = array_map(fn($h) => $this->normalizeColumn($h), $csvHeader);

        // Créer le mapping : CSV 'id' → DB 'id_addr', autres colonnes identiques
        $csvToDbMap = [];
        foreach ($csvHeader as $csvCol) {
            if ($csvCol === 'id') {
                $csvToDbMap['id'] = 'id_addr'; // Mapper CSV 'id' vers table 'id_addr'
            } elseif (in_array($csvCol, $dbColumns)) {
                $csvToDbMap[$csvCol] = $csvCol; // Colonnes identiques
            }
        }

        // Vérifier que toutes les colonnes DB sont couvertes
        $mappedDbCols = array_values($csvToDbMap);
        $diff1 = array_diff($dbColumns, $mappedDbCols);
        $diff2 = array_diff($mappedDbCols, $dbColumns);

        if ($diff1 || $diff2) {
            $io->error('Structure du CSV différente de la table SQL.');
            $io->writeln('Colonnes manquantes dans le CSV : ' . implode(', ', $diff1));
            $io->writeln('Colonnes en trop dans le CSV : ' . implode(', ', $diff2));
            fclose($handle);
            return Command::FAILURE;
        }

        // Vider la table
        $io->section('🗑️  Vidage de la table...');
        $this->pdo->exec("TRUNCATE TABLE `$table`");
        $io->success('Table vidée avec succès.');

        // Préparer l'insertion (sans 'id' qui est auto-increment)
        $placeholders = '(' . implode(',', array_fill(0, count($dbColumns), '?')) . ')';
        $sql = "INSERT INTO `$table` (`" . implode('`,`', $dbColumns) . "`) VALUES $placeholders";
        $stmtInsert = $this->pdo->prepare($sql);

        $batch = [];
        $batchNum = 1;
        $totalInserted = 0;

        file_put_contents($logPath, "=== Début import " . date('Y-m-d H:i:s') . " ===\n", FILE_APPEND);
        $io->section('⚡ Import en cours...');
        $progressBar = new ProgressBar($output);
        $progressBar->start();

        // Lire et insérer les données ligne par ligne
        while (($row = fgetcsv($handle, 0, ';')) !== false) {
            $row = array_map('trim', $row);

            // Associer les valeurs CSV aux colonnes CSV
            $rowAssoc = array_combine($csvHeader, $row);

            // Mapper les valeurs selon le mapping défini (CSV → DB)
            $mappedRow = [];
            foreach ($csvToDbMap as $csvCol => $dbCol) {
                $mappedRow[$dbCol] = $rowAssoc[$csvCol] ?? null;
            }

            // Forcer les id_addr vides à NULL
            if (empty($mappedRow['id_addr'])) {
                $mappedRow['id_addr'] = null;
            }

            // Ordonner les valeurs selon l'ordre des colonnes DB
            $values = [];
            foreach ($dbColumns as $col) {
                $values[] = $mappedRow[$col] ?? null;
            }

            $batch[] = $values;

            // Insérer par batch pour optimiser les performances
            if (count($batch) >= $batchSize) {
                $this->insertBatch($stmtInsert, $batch);
                $totalInserted += count($batch);
                $batch = [];
                file_put_contents($logPath, "Batch $batchNum inséré à " . date('H:i:s') . "\n", FILE_APPEND);
                $batchNum++;
                $progressBar->advance($batchSize);
            }
        }

        // Insérer le dernier batch s'il reste des lignes
        if ($batch) {
            $this->insertBatch($stmtInsert, $batch);
            $totalInserted += count($batch);
        }

        fclose($handle);
        $progressBar->finish();
        $io->newLine(2);

        file_put_contents($logPath, "=== Fin import " . date('Y-m-d H:i:s') . " ===\n", FILE_APPEND);
        $io->success("✅ Import terminé avec succès !");
        $io->info("📊 Total : $totalInserted lignes insérées.");

        return Command::SUCCESS;
    }

    /**
     * Télécharge et extrait le fichier CSV depuis la BAN
     */
    private function downloadAndExtractCsv(string $targetPath, SymfonyStyle $io): bool
    {
        $gzPath = $targetPath . '.gz';

        try {
            // Télécharger le fichier .gz
            $io->text('Téléchargement depuis : ' . $this->banCsvUrl);
            $io->text('Cela peut prendre plusieurs minutes...');

            $response = $this->httpClient->request('GET', $this->banCsvUrl, [
                'timeout' => 600, // 10 minutes de timeout
            ]);

            $statusCode = $response->getStatusCode();
            if ($statusCode !== 200) {
                $io->error("Erreur HTTP $statusCode lors du téléchargement.");
                return false;
            }

            // Sauvegarder le fichier .gz
            $fileHandler = fopen($gzPath, 'w');
            foreach ($this->httpClient->stream($response) as $chunk) {
                fwrite($fileHandler, $chunk->getContent());
            }
            fclose($fileHandler);

            $io->success('✅ Téléchargement terminé.');
            $io->section('📦 Extraction du fichier compressé...');

            // Extraire le fichier .gz
            $gzFile = gzopen($gzPath, 'rb');
            $csvFile = fopen($targetPath, 'w');

            if (!$gzFile || !$csvFile) {
                $io->error('Erreur lors de l\'ouverture des fichiers pour extraction.');
                return false;
            }

            // Lire et écrire par chunks
            while (!gzeof($gzFile)) {
                fwrite($csvFile, gzread($gzFile, 4096));
            }

            gzclose($gzFile);
            fclose($csvFile);

            // Supprimer le fichier .gz
            unlink($gzPath);

            $io->success('✅ Extraction terminée.');
            return true;
        } catch (\Exception $e) {
            $io->error('Erreur : ' . $e->getMessage());

            // Nettoyer les fichiers temporaires en cas d'erreur
            if (file_exists($gzPath)) {
                unlink($gzPath);
            }

            return false;
        }
    }

    private function insertBatch(\PDOStatement $stmt, array $batch): void
    {
        $this->pdo->beginTransaction();
        foreach ($batch as $values) {
            $stmt->execute($values);
        }
        $this->pdo->commit();
    }

    private function normalizeColumn(string $name): string
    {
        return strtolower(trim(str_replace([' ', '-', "\t"], '_', $name)));
    }
}
