<?php

/**
 * Script de migration pour la table ope_pass
 * Transfert les données depuis la table ope_pass de la base source vers la table ope_pass de la base cible
 * Ne migre que les passages liés aux opérations qui ont été migrées
 * Fait la correspondance entre les anciens secteurs (fk_old_sector) et les nouveaux secteurs (id) dans la table ope_sectors
 * Effectue les changements de champs demandés (date_eve=>passed_at, libelle=>encrypted_name, email=>encrypted_email, phone=>encrypted_phone)
 */

require_once dirname(__DIR__) . '/config.php';
require_once __DIR__ . '/MigrationConfig.php';
require_once dirname(dirname(__DIR__)) . '/src/Services/ApiService.php';

try {
    // Vérifier si un processus utilise déjà le port du tunnel SSH
    echo "Vérification du port SSH..." . PHP_EOL;
    
    // Création du tunnel SSH avec une meilleure gestion des erreurs
    try {
        // Tuer tout processus existant qui utilise le port 13306
        // Sous Linux/Mac
        @exec('kill $(lsof -t -i:13306) 2>/dev/null');
        // Attendre un moment pour s'assurer que le port est libéré
        sleep(1);
        
        createSshTunnel();
        echo "Tunnel SSH créé avec succès." . PHP_EOL;
    } catch (Exception $e) {
        echo "ERREUR lors de la création du tunnel SSH: " . $e->getMessage() . PHP_EOL;
        exit(1);
    }

    // Connexion aux bases de données avec paramètres pour éviter le timeout
    try {
        echo "Connexion à la base source..." . PHP_EOL;
        $sourceDb = getSourceConnection();
        echo "Connexion à la base source établie." . PHP_EOL;
    } catch (Exception $e) {
        echo "ERREUR de connexion à la base source: " . $e->getMessage() . PHP_EOL;
        closeSshTunnel();
        exit(1);
    }
    
    // Configuration spéciale pour éviter les timeouts sur les grosses opérations
    try {
        echo "Connexion à la base cible..." . PHP_EOL;
        $targetDb = getTargetConnection();
        echo "Connexion à la base cible établie." . PHP_EOL;
        
        // Configuration des timeouts adaptés à MariaDB 10.11
        $targetDb->setAttribute(PDO::ATTR_TIMEOUT, 600); // 10 minutes pour PDO
        
        echo "  - Configuration des timeouts pour MariaDB 10.11" . PHP_EOL;
        
        // Configurations spécifiques à MariaDB 10.11
        $timeoutVars = [
            "wait_timeout" => 3600,                // 1 heure
            "net_read_timeout" => 3600,           // 1 heure
            "net_write_timeout" => 3600,          // 1 heure
            "innodb_lock_wait_timeout" => 3600    // 1 heure
        ];
        
        // Configurer les variables de session
        foreach ($timeoutVars as $var => $value) {
            try {
                $sql = "SET SESSION $var=$value";
                $targetDb->exec($sql);
                echo "  - Config MariaDB: $var = $value" . PHP_EOL;
            } catch (PDOException $e) {
                echo "  - Impossible de configurer $var: " . $e->getMessage() . PHP_EOL;
            }
        }
        
        echo "Paramètres de timeout configurés." . PHP_EOL;
    } catch (Exception $e) {
        echo "ERREUR de connexion à la base cible: " . $e->getMessage() . PHP_EOL;
        closeSshTunnel();
        exit(1);
    }

    // Début de la migration
    
    // Vérifions la version de la base de données cible (MariaDB)
    $versionTarget = $targetDb->query('SELECT VERSION() as version')->fetch();
    echo "Version de la base cible (MariaDB): " . $versionTarget['version'] . PHP_EOL;
    
    // Vérifions la version de la base de données source (MySQL)
    $versionSource = $sourceDb->query('SELECT VERSION() as version')->fetch();
    echo "Version de la base source (MySQL): " . $versionSource['version'] . PHP_EOL;
    
    // Note sur les privilèges de contraintes
    echo "NOTE: La suppression et recréation des contraintes nécessitent des privilèges SUPER ou ALTER TABLE." . PHP_EOL;
    echo "      Ces opérations peuvent être ignorées si l'utilisateur n'a pas les privilèges suffisants." . PHP_EOL;
    echo "      Il est recommandé d'exécuter ces opérations manuellement avec un utilisateur admin." . PHP_EOL;
    
    // Suppression des contraintes relationnelles (tentatif)
    echo "Tentative de suppression des contraintes relationnelles... " . PHP_EOL;
    $dropConstraintsQueries = [
        "ALTER TABLE ope_pass DROP FOREIGN KEY ope_pass_ibfk_1",
        "ALTER TABLE ope_pass DROP FOREIGN KEY ope_pass_ibfk_2",
        "ALTER TABLE ope_pass DROP FOREIGN KEY ope_pass_ibfk_3",
        "ALTER TABLE ope_pass DROP FOREIGN KEY ope_pass_ibfk_4"
    ];
    
    $constraintDropFailed = false;
    foreach ($dropConstraintsQueries as $query) {
        try {
            $targetDb->exec($query);
            echo "  - Contrainte supprimée avec succès : " . substr($query, 0, 60) . "..." . PHP_EOL;
        } catch (PDOException $e) {
            echo "  - Erreur lors de la suppression de la contrainte : " . $e->getMessage() . PHP_EOL;
            $constraintDropFailed = true;
        }
    }
    
    if ($constraintDropFailed) {
        echo "ATTENTION: Les contraintes n'ont pas pu être supprimées. La migration continue sans cette étape." . PHP_EOL;
        echo "          Vous devrez peut-être désactiver les contraintes manuellement si la suppression échoue." . PHP_EOL;
    }

    // Suppression de toutes les données existantes dans la table ope_pass par lots pour éviter les timeouts
    echo "Suppression des données existantes dans la table ope_pass (par lots)... " . PHP_EOL;
    
    try {
        // Désactiver temporairement les vérifications de clés étrangères
        // Cela fonctionne à la fois dans MySQL et MariaDB
        try {
            $targetDb->exec("SET FOREIGN_KEY_CHECKS=0");
            echo "  - Vérification des clés étrangères temporairement désactivée." . PHP_EOL;
        } catch (PDOException $e) {
            echo "  - Erreur lors de la désactivation des clés étrangères: " . $e->getMessage() . PHP_EOL;
        }
        
        // Suppression par lots
        $batchSize = 100000;
        $totalDeleted = 0;
        $continue = true;
        
        echo "  - Suppression par lots de $batchSize enregistrements:" . PHP_EOL;
        
        while ($continue) {
            $deleteQuery = "DELETE FROM ope_pass LIMIT $batchSize";
            $rowCount = $targetDb->exec($deleteQuery);
            $totalDeleted += $rowCount;
            echo "    * Lot supprimé: $rowCount enregistrements (Total: $totalDeleted)" . PHP_EOL;
            
            // Vérifier si nous avons terminé
            if ($rowCount < $batchSize) {
                $continue = false;
            }
            
            // Petit délai pour permettre des traitements serveur
            if ($continue) {
                usleep(100000); // 0.1 seconde
            }
        }
        
        echo "  - Total: $totalDeleted enregistrements supprimés." . PHP_EOL;
        
        // Réactiver les vérifications de clés étrangères
        try {
            $targetDb->exec("SET FOREIGN_KEY_CHECKS=1");
            echo "  - Vérification des clés étrangères réactivée." . PHP_EOL;
        } catch (PDOException $e) {
            echo "  - Erreur lors de la réactivation des clés étrangères: " . $e->getMessage() . PHP_EOL;
        }
        
    } catch (PDOException $e) {
        echo "  - Erreur lors de la suppression des données : " . $e->getMessage() . PHP_EOL;
        
        // Réactiver les vérifications de clés étrangères en cas d'erreur
        try {
            $targetDb->exec("SET FOREIGN_KEY_CHECKS=1");
        } catch (Exception $e2) {
            echo "  - Erreur lors de la réactivation des clés étrangères : " . $e2->getMessage() . PHP_EOL;
        }
        
        closeSshTunnel();
        exit(1);
    }

    // Récupération des IDs des opérations qui ont été migrées
    $stmt = $targetDb->query("SELECT id FROM operations");
    $migratedOperations = $stmt->fetchAll(PDO::FETCH_COLUMN);

    if (empty($migratedOperations)) {
        echo "ERREUR: Aucune opération n'a été migrée. Veuillez d'abord migrer la table operations." . PHP_EOL;
        closeSshTunnel();
        exit(1);
    }

    // Récupération des IDs des utilisateurs qui ont été migrés
    $stmt = $targetDb->query("SELECT id FROM users");
    $migratedUsers = $stmt->fetchAll(PDO::FETCH_COLUMN);

    if (empty($migratedUsers)) {
        echo "ERREUR: Aucun utilisateur n'a été migré. Veuillez d'abord migrer la table users." . PHP_EOL;
        closeSshTunnel();
        exit(1);
    }

    // Récupération de la correspondance entre les anciens secteurs et les nouveaux
    $query = "SELECT id, fk_operation, fk_old_sector FROM ope_sectors WHERE fk_old_sector IS NOT NULL";
    $stmt = $targetDb->query($query);
    $sectorMapping = $stmt->fetchAll(PDO::FETCH_ASSOC);

    if (empty($sectorMapping)) {
        echo "ERREUR: Aucun secteur n'a été migré. Veuillez d'abord migrer la table ope_sectors." . PHP_EOL;
        closeSshTunnel();
        exit(1);
    }

    // Création d'un tableau associatif pour faciliter la recherche des correspondances
    $sectorMap = [];
    foreach ($sectorMapping as $mapping) {
        $key = $mapping['fk_operation'] . '_' . $mapping['fk_old_sector'];
        $sectorMap[$key] = $mapping['id'];
    }

    // Pas d'affichage en mode silencieux

    // Création de la liste des IDs d'opérations pour la requête IN
    $operationIds = implode(',', $migratedOperations);

    // Compter le nombre total de passages à migrer pour estimer le volume
    $countQuery = "
        SELECT COUNT(*) as total
        FROM ope_pass p
        WHERE p.fk_operation IN ($operationIds)
        AND p.active = 1
    ";
    $countStmt = $sourceDb->query($countQuery);
    $totalCount = $countStmt->fetch(PDO::FETCH_ASSOC)['total'];
    
    echo "Nombre total de passages à migrer: $totalCount" . PHP_EOL;
    
    // Définir la taille des lots pour éviter les problèmes de mémoire
    $batchSize = 5000;
    $totalBatches = ceil($totalCount / $batchSize);
    
    echo "Traitement par lots de $batchSize passages ($totalBatches lots au total)" . PHP_EOL;

    // Pas d'affichage du nombre de passages à migrer en mode silencieux

    // Préparation de la requête d'insertion
    $insertQuery = "INSERT INTO ope_pass (
                    fk_operation, 
                    fk_sector, 
                    fk_user, 
                    fk_adresse, 
                    passed_at, 
                    fk_type, 
                    numero, 
                    rue, 
                    rue_bis, 
                    ville, 
                    fk_habitat, 
                    appt, 
                    niveau, 
                    gps_lat, 
                    gps_lng, 
                    encrypted_name, 
                    montant, 
                    fk_type_reglement, 
                    remarque, 
                    encrypted_email, 
                    nom_recu, 
                    email_erreur, 
                    chk_email_sent, 
                    encrypted_phone, 
                    docremis, 
                    date_repasser, 
                    nb_passages, 
                    chk_gps_maj, 
                    chk_map_create, 
                    chk_mobile, 
                    chk_synchro, 
                    chk_api_adresse, 
                    chk_maj_adresse, 
                    anomalie, 
                    created_at, 
                    fk_user_creat, 
                    updated_at, 
                    fk_user_modif, 
                    chk_active
                ) VALUES (
                    :fk_operation, 
                    :fk_sector, 
                    :fk_user, 
                    :fk_adresse, 
                    :passed_at, 
                    :fk_type, 
                    :numero, 
                    :rue, 
                    :rue_bis, 
                    :ville, 
                    :fk_habitat, 
                    :appt, 
                    :niveau, 
                    :gps_lat, 
                    :gps_lng, 
                    :encrypted_name, 
                    :montant, 
                    :fk_type_reglement, 
                    :remarque, 
                    :encrypted_email, 
                    :nom_recu, 
                    :email_erreur, 
                    :chk_email_sent, 
                    :encrypted_phone, 
                    :docremis, 
                    :date_repasser, 
                    :nb_passages, 
                    :chk_gps_maj, 
                    :chk_map_create, 
                    :chk_mobile, 
                    :chk_synchro, 
                    :chk_api_adresse, 
                    :chk_maj_adresse, 
                    :anomalie, 
                    :created_at, 
                    :fk_user_creat, 
                    :updated_at, 
                    :fk_user_modif, 
                    :chk_active
                ) ON DUPLICATE KEY UPDATE 
                    fk_sector = VALUES(fk_sector),
                    passed_at = VALUES(passed_at),
                    numero = VALUES(numero),
                    rue = VALUES(rue),
                    rue_bis = VALUES(rue_bis),
                    ville = VALUES(ville),
                    fk_habitat = VALUES(fk_habitat),
                    appt = VALUES(appt),
                    niveau = VALUES(niveau),
                    gps_lat = VALUES(gps_lat),
                    gps_lng = VALUES(gps_lng),
                    encrypted_name = VALUES(encrypted_name),
                    montant = VALUES(montant),
                    fk_type_reglement = VALUES(fk_type_reglement),
                    remarque = VALUES(remarque),
                    encrypted_email = VALUES(encrypted_email),
                    nom_recu = VALUES(nom_recu),
                    email_erreur = VALUES(email_erreur),
                    chk_email_sent = VALUES(chk_email_sent),
                    encrypted_phone = VALUES(encrypted_phone),
                    docremis = VALUES(docremis),
                    date_repasser = VALUES(date_repasser),
                    nb_passages = VALUES(nb_passages),
                    chk_gps_maj = VALUES(chk_gps_maj),
                    chk_map_create = VALUES(chk_map_create),
                    chk_mobile = VALUES(chk_mobile),
                    chk_synchro = VALUES(chk_synchro),
                    chk_api_adresse = VALUES(chk_api_adresse),
                    chk_maj_adresse = VALUES(chk_maj_adresse),
                    anomalie = VALUES(anomalie),
                    updated_at = VALUES(updated_at),
                    fk_user_modif = VALUES(fk_user_modif),
                    chk_active = VALUES(chk_active)";

    $insertStmt = $targetDb->prepare($insertQuery);

    // Compteurs
    $inserted = 0;
    $skipped = 0;
    $errors = 0;

    // Traitement par lots pour éviter les problèmes de mémoire
    for ($batch = 0; $batch < $totalBatches; $batch++) {
        $offset = $batch * $batchSize;
        
        echo "Traitement du lot " . ($batch + 1) . "/$totalBatches (offset: $offset)" . PHP_EOL;
        
        // Récupération d'un lot de passages
        $query = "
            SELECT p.*
            FROM ope_pass p
            WHERE p.fk_operation IN ($operationIds)
            AND p.active = 1
            LIMIT $batchSize OFFSET $offset
        ";
        
        $stmt = $sourceDb->query($query);
        $passages = $stmt->fetchAll(PDO::FETCH_ASSOC);
        
        echo "  - " . count($passages) . " passages récupérés dans ce lot" . PHP_EOL;
        
        // Libérer la mémoire après avoir récupéré les données
        $stmt->closeCursor();
        unset($stmt);
        
        // Traitement des passages de ce lot
        $batchInserted = 0;
        $batchSkipped = 0;
        $batchErrors = 0;
        
        // Commencer une transaction pour les insertions de ce lot
        $targetDb->beginTransaction();
        
        foreach ($passages as $passage) {
        $fkOperation = $passage['fk_operation'];
        $fkOldSector = $passage['fk_sector'];

        // Vérifier si le secteur existe dans la table ope_sectors de la cible
        // On utilise la requête pour trouver le secteur correspondant dans la table ope_sectors
        $sectorQuery = "SELECT id FROM ope_sectors WHERE fk_operation = :fk_operation AND fk_old_sector = :fk_old_sector";
        $sectorStmt = $targetDb->prepare($sectorQuery);
        $sectorStmt->execute([
            ':fk_operation' => $fkOperation,
            ':fk_old_sector' => $fkOldSector
        ]);

        $newSector = $sectorStmt->fetch(PDO::FETCH_ASSOC);

        // Si le secteur n'a pas été migré, on ignore ce passage silencieusement
        if (!$newSector) {
            $skipped++;
            continue;
        }

        $fkNewSector = $newSector['id'];

        // Vérifier si l'utilisateur existe dans la table users de la cible
        $fkUser = $passage['fk_user'] ?? 0;
        if ($fkUser > 0 && !in_array($fkUser, $migratedUsers)) {
            // L'utilisateur n'a pas été migré, on ignore ce passage silencieusement
            $skipped++;
            continue;
        }

        // Conversion des dates
        $passedAt = !empty($passage['date_eve']) ? date('Y-m-d H:i:s', strtotime($passage['date_eve'])) : null;
        $dateRepasser = !empty($passage['date_repasser']) ? date('Y-m-d H:i:s', strtotime($passage['date_repasser'])) : null;
        $createdAt = !empty($passage['date_creat']) ? date('Y-m-d H:i:s', strtotime($passage['date_creat'])) : date('Y-m-d H:i:s');
        $updatedAt = !empty($passage['date_modif']) ? date('Y-m-d H:i:s', strtotime($passage['date_modif'])) : null;

        // Chiffrement des données sensibles
        // Validation et chiffrement du nom
        $encryptedName = '';
        if (!empty($passage['libelle'])) {
            $encryptedName = ApiService::encryptData($passage['libelle']);
        }
        
        // Validation et chiffrement de l'email
        $encryptedEmail = '';
        if (!empty($passage['email'])) {
            // Vérifier si l'email est valide
            if (filter_var($passage['email'], FILTER_VALIDATE_EMAIL)) {
                $encryptedEmail = ApiService::encryptSearchableData($passage['email']);
            }
        }
        
        $encryptedPhone = !empty($passage['phone']) ? ApiService::encryptData($passage['phone']) : '';

        // Vérification et correction du type de règlement
        $fkTypeReglement = $passage['fk_type_reglement'] ?? 1;
        if (!in_array($fkTypeReglement, [1, 2, 3])) {
            $fkTypeReglement = 4; // Forcer à 4 si différent de 1, 2 ou 3
        }

        // Préparation des données pour l'insertion
        $passageData = [
            'fk_operation' => $fkOperation,
            'fk_sector' => $fkNewSector,
            'fk_user' => $passage['fk_user'] ?? 0,
            'fk_adresse' => $passage['fk_adresse'] ?? '',
            'passed_at' => $passedAt, // Mapping date_eve => passed_at
            'fk_type' => (isset($passage['fk_type']) && $passage['fk_type'] == '9') ? 6 : 
                        ((isset($passage['fk_type']) && $passage['fk_type'] == '8') ? 5 : 
                        (isset($passage['fk_type']) ? (int)$passage['fk_type'] : 0)),
            'numero' => $passage['numero'] ?? '',
            'rue' => $passage['rue'] ?? '',
            'rue_bis' => $passage['rue_bis'] ?? '',
            'ville' => $passage['ville'] ?? '',
            'fk_habitat' => $passage['fk_habitat'] ?? 1,
            'appt' => $passage['appt'] ?? '',
            'niveau' => $passage['niveau'] ?? '',
            'gps_lat' => $passage['gps_lat'] ?? '',
            'gps_lng' => $passage['gps_lng'] ?? '',
            'encrypted_name' => $encryptedName, // Mapping libelle => encrypted_name avec chiffrement
            'montant' => $passage['montant'] ?? 0,
            'fk_type_reglement' => $fkTypeReglement, // Valeur corrigée
            'remarque' => $passage['remarque'] ?? '',
            'encrypted_email' => $encryptedEmail, // Mapping email => encrypted_email avec chiffrement
            'nom_recu' => $passage['recu'] ?? null, // Mapping recu => nom_recu
            'email_erreur' => $passage['email_erreur'] ?? '',
            'chk_email_sent' => $passage['chk_email_sent'] ?? 0,
            'encrypted_phone' => $encryptedPhone, // Mapping phone => encrypted_phone avec chiffrement
            'docremis' => $passage['docremis'] ?? 0,
            'date_repasser' => $dateRepasser,
            'nb_passages' => $passage['nb_passages'] ?? 1,
            'chk_gps_maj' => $passage['chk_gps_maj'] ?? 0,
            'chk_map_create' => $passage['chk_map_create'] ?? 0,
            'chk_mobile' => $passage['chk_mobile'] ?? 0,
            'chk_synchro' => $passage['chk_synchro'] ?? 1,
            'chk_api_adresse' => $passage['chk_api_adresse'] ?? 0,
            'chk_maj_adresse' => $passage['chk_maj_adresse'] ?? 0,
            'anomalie' => $passage['anomalie'] ?? 0,
            'created_at' => $createdAt,
            'fk_user_creat' => $passage['fk_user_creat'] ?? null,
            'updated_at' => $updatedAt,
            'fk_user_modif' => $passage['fk_user_modif'] ?? null,
            'chk_active' => $passage['active'] ?? 1
        ];

        try {
            // Insertion dans la table cible
            $insertStmt->execute($passageData);
            $inserted++;
            $batchInserted++;

            // Libérer un peu de mémoire entre chaque insertion
            if ($batchInserted % 100 == 0) {
                unset($passageData);
                gc_collect_cycles(); // Forcer le garbage collector
            }
        } catch (PDOException $e) {
            echo "ERREUR: Migration du passage (rowid " . $passage['rowid'] . ", opération $fkOperation) : " . $e->getMessage() . "\n";
            $errors++;
            $batchErrors++;
        }
        
        // Libérer la mémoire du passage traité
        unset($passage);
        }
        
        // Valider la transaction pour ce lot
        try {
            $targetDb->commit();
            echo "  - Lot $batch commité avec succès: $batchInserted insérés, $batchSkipped ignorés, $batchErrors erreurs" . PHP_EOL;
        } catch (PDOException $e) {
            $targetDb->rollBack();
            echo "ERREUR lors du commit du lot $batch: " . $e->getMessage() . PHP_EOL;
        }
        
        // Libérer la mémoire après chaque lot
        unset($passages);
        gc_collect_cycles(); // Forcer le garbage collector

        // Petite pause entre les lots pour éviter de surcharger le serveur
        sleep(1);
    }

    echo "Migration de la table ope_pass terminée. $inserted passages insérés, $skipped passages ignorés, $errors erreurs." . PHP_EOL;

    // Recréation des contraintes relationnelles
    echo "Tentative de recréation des contraintes relationnelles... " . PHP_EOL;
    $addConstraintsQueries = [
        "ALTER TABLE ope_pass ADD CONSTRAINT ope_pass_ibfk_1 FOREIGN KEY (fk_operation) REFERENCES operations (id) ON DELETE RESTRICT ON UPDATE CASCADE",
        "ALTER TABLE ope_pass ADD CONSTRAINT ope_pass_ibfk_2 FOREIGN KEY (fk_sector) REFERENCES ope_sectors (id) ON DELETE RESTRICT ON UPDATE CASCADE", 
        "ALTER TABLE ope_pass ADD CONSTRAINT ope_pass_ibfk_3 FOREIGN KEY (fk_user) REFERENCES users (id) ON DELETE RESTRICT ON UPDATE CASCADE",
        "ALTER TABLE ope_pass ADD CONSTRAINT ope_pass_ibfk_4 FOREIGN KEY (fk_type_reglement) REFERENCES x_types_reglements (id) ON DELETE RESTRICT ON UPDATE CASCADE"
    ];
    
    $constraintAddFailed = false;
    foreach ($addConstraintsQueries as $query) {
        try {
            $targetDb->exec($query);
            echo "  - Contrainte recréée avec succès : " . substr($query, 0, 60) . "..." . PHP_EOL;
        } catch (PDOException $e) {
            echo "  - Erreur lors de la recréation de la contrainte : " . $e->getMessage() . PHP_EOL;
            $constraintAddFailed = true;
        }
    }
    
    if ($constraintAddFailed) {
        echo "ATTENTION: Certaines contraintes n'ont pas pu être recréées." . PHP_EOL;
        echo "          Script SQL pour recréer manuellement les contraintes:" . PHP_EOL;
        echo "--------------------------------------------------------------" . PHP_EOL;
        foreach ($addConstraintsQueries as $query) {
            echo "$query;" . PHP_EOL;
        }
        echo "--------------------------------------------------------------" . PHP_EOL;
    }

    // Fermer le tunnel SSH
    closeSshTunnel();
} catch (Exception $e) {
    echo "ERREUR CRITIQUE: " . $e->getMessage() . PHP_EOL;

    // Fermer le tunnel SSH en cas d'erreur
    closeSshTunnel();

    exit(1);
}
