<?php

declare(strict_types=1);

namespace App\Services;

use Database;

require_once __DIR__ . '/LogService.php';
require_once __DIR__ . '/ApiService.php';

use PDO;
use PDOException;
use Exception;
use AppConfig;
use App\Services\LogService;
use App\Services\ApiService;

class MigrationService {
    private ?PDO $sourceDb = null;
    private PDO $targetDb;
    private AppConfig $appConfig;
    private bool $sshTunnelCreated = false;

    // Configuration SSH et base source (TODO: à déplacer dans AppConfig)
    private const SSH_HOST = '212.83.164.111';
    private const SSH_PORT = 52266;
    private const SSH_USER = 'root';
    private const SSH_KEY_FILE = '/root/.ssh/id_rsa_db2';
    private const REMOTE_DB_HOST = '127.0.0.1';
    private const REMOTE_DB_PORT = 3306;
    private const SOURCE_DB_HOST = '127.0.0.1';
    private const SOURCE_DB_NAME = 'geosector';
    private const SOURCE_DB_USER = 'geo_front_user';
    private const SOURCE_DB_PASS = 'd66,GeoFront.User';
    private const SOURCE_DB_PORT = 13306;

    // Ordre des étapes de migration
    private const MIGRATION_STEPS = [
        'x_devises',
        'x_entites_types',
        'x_types_passages',
        'x_types_reglements',
        'x_users_roles',
        'x_pays',
        'x_regions',
        'x_departements',
        'x_villes',
        'entites',
        'users',
        'operations',
        'ope_sectors',
        'sectors_adresses',
        'ope_users',
        'ope_users_sectors',
        'ope_pass',
        'ope_pass_histo',
        'medias'
    ];

    public function __construct() {
        $this->targetDb = Database::getInstance();
        $this->appConfig = AppConfig::getInstance();
    }

    public function __destruct() {
        $this->closeSshTunnel();
    }

    /**
     * Crée un tunnel SSH vers le serveur distant
     */
    private function createSshTunnel(): bool {
        if ($this->sshTunnelCreated) {
            return true;
        }

        // Vérifier si un tunnel est déjà en cours d'exécution
        $checkCommand = "ps aux | grep 'ssh -[vf]* -N -L " . self::SOURCE_DB_PORT . ":' | grep -v grep";
        exec($checkCommand, $output, $return_var);

        if (empty($output)) {
            // Créer le tunnel SSH
            $command = sprintf(
                "ssh -f -N -o StrictHostKeyChecking=no -L %d:%s:%d -p %d %s@%s -i %s 2>&1",
                self::SOURCE_DB_PORT,
                self::REMOTE_DB_HOST,
                self::REMOTE_DB_PORT,
                self::SSH_PORT,
                self::SSH_USER,
                self::SSH_HOST,
                self::SSH_KEY_FILE
            );

            exec($command, $output, $return_var);

            if ($return_var !== 0) {
                LogService::log('Erreur lors de la création du tunnel SSH', [
                    'level' => 'error',
                    'output' => implode("\n", $output)
                ]);
                return false;
            }

            // Attendre que le tunnel soit établi
            sleep(2);

            // Vérification du tunnel
            $checkTunnel = "netstat -an | grep " . self::SOURCE_DB_PORT . " | grep LISTEN";
            exec($checkTunnel, $tunnelOutput);

            if (empty($tunnelOutput)) {
                LogService::log('Le tunnel SSH semble créé mais le port n\'est pas en écoute', [
                    'level' => 'warning',
                    'port' => self::SOURCE_DB_PORT
                ]);
            }

            LogService::log('Tunnel SSH établi', [
                'level' => 'info',
                'port' => self::SOURCE_DB_PORT
            ]);
        } else {
            LogService::log('Un tunnel SSH est déjà en cours d\'exécution', [
                'level' => 'info'
            ]);
        }

        $this->sshTunnelCreated = true;
        return true;
    }

    /**
     * Ferme le tunnel SSH
     */
    private function closeSshTunnel(): void {
        if (!$this->sshTunnelCreated) {
            return;
        }

        $command = "ps aux | grep 'ssh -[vf]* -N -L " . self::SOURCE_DB_PORT . ":' | grep -v grep | awk '{print $2}' | xargs -r kill 2>/dev/null";
        exec($command);

        $this->sshTunnelCreated = false;
        $this->sourceDb = null;

        LogService::log('Tunnel SSH fermé', ['level' => 'info']);
    }

    /**
     * Récupère la connexion à la base source
     */
    private function getSourceConnection(): PDO {
        if ($this->sourceDb !== null) {
            return $this->sourceDb;
        }

        // Établir le tunnel SSH
        if (!$this->createSshTunnel()) {
            throw new Exception("Impossible d'établir le tunnel SSH");
        }

        // Options de connexion PDO
        $options = [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4',
            PDO::ATTR_TIMEOUT => 600
        ];

        if (defined('PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT')) {
            $options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = false;
        }

        try {
            // Se connecter à la base spécifique
            $dsn = sprintf(
                'mysql:host=%s;dbname=%s;port=%d',
                self::SOURCE_DB_HOST,
                self::SOURCE_DB_NAME,
                self::SOURCE_DB_PORT
            );

            $this->sourceDb = new PDO($dsn, self::SOURCE_DB_USER, self::SOURCE_DB_PASS, $options);

            LogService::log('Connexion établie à la base source', [
                'level' => 'info',
                'database' => self::SOURCE_DB_NAME
            ]);

            return $this->sourceDb;
        } catch (PDOException $e) {
            $this->closeSshTunnel();
            throw new Exception("Erreur de connexion à la base source: " . $e->getMessage());
        }
    }

    /**
     * Teste les connexions aux deux bases de données
     */
    public function testConnections(): array {
        $result = [
            'source' => ['status' => 'error', 'message' => ''],
            'target' => ['status' => 'error', 'message' => '']
        ];

        // Test connexion source
        try {
            $sourceDb = $this->getSourceConnection();
            $stmt = $sourceDb->query('SELECT DATABASE() as db, VERSION() as version');
            $info = $stmt->fetch();

            $result['source'] = [
                'status' => 'success',
                'database' => $info['db'],
                'version' => $info['version'],
                'message' => 'Connexion réussie'
            ];
        } catch (Exception $e) {
            $result['source']['message'] = $e->getMessage();
        }

        // Test connexion cible
        try {
            $stmt = $this->targetDb->query('SELECT DATABASE() as db, VERSION() as version');
            $info = $stmt->fetch();

            $result['target'] = [
                'status' => 'success',
                'database' => $info['db'],
                'version' => $info['version'],
                'message' => 'Connexion réussie'
            ];
        } catch (Exception $e) {
            $result['target']['message'] = $e->getMessage();
        }

        return $result;
    }

    /**
     * Liste les entités disponibles à migrer depuis la base source
     */
    public function getAvailableEntities(): array {
        $sourceDb = $this->getSourceConnection();

        $stmt = $sourceDb->query("
            SELECT
                rowid as id,
                libelle as name,
                cp as postal_code,
                ville as city,
                active,
                date_creat as created_at
            FROM users_entites
            WHERE active = 1
            ORDER BY libelle ASC
        ");

        return $stmt->fetchAll();
    }

    /**
     * Récupère les détails d'une entité source
     */
    public function getEntityDetails(int $id): ?array {
        $sourceDb = $this->getSourceConnection();

        $stmt = $sourceDb->prepare("
            SELECT
                e.rowid as id,
                e.libelle as name,
                e.cp as postal_code,
                e.ville as city,
                e.active,
                e.date_creat as created_at,
                (SELECT COUNT(*) FROM users WHERE fk_entite = e.rowid) as users_count,
                (SELECT COUNT(*) FROM operations WHERE fk_entite = e.rowid) as operations_count,
                (SELECT COUNT(*) FROM ope_pass p
                 JOIN operations o ON p.fk_operation = o.rowid
                 WHERE o.fk_entite = e.rowid) as passages_count
            FROM users_entites e
            WHERE e.rowid = ?
        ");

        $stmt->execute([$id]);
        $entity = $stmt->fetch();

        return $entity ?: null;
    }

    /**
     * Migre une entité complète
     */
    public function migrateEntity(
        int $entityId,
        ?array $steps = null,
        bool $dryRun = false,
        bool $truncate = false
    ): array {
        $startTime = microtime(true);
        $migrationId = 'mig_' . time() . '_' . $entityId;

        // Si aucune étape spécifiée, migrer toutes les étapes
        if ($steps === null) {
            $steps = self::MIGRATION_STEPS;
        }

        // Récupérer le nom de l'entité
        $entityDetails = $this->getEntityDetails($entityId);
        if (!$entityDetails) {
            throw new Exception("Entité $entityId non trouvée");
        }

        $stepsCompleted = [];
        $totalRecords = 0;
        $totalErrors = 0;
        $totalWarnings = 0;

        LogService::log('Début de migration entité', [
            'level' => 'info',
            'migration_id' => $migrationId,
            'entity_id' => $entityId,
            'entity_name' => $entityDetails['name'],
            'steps' => $steps,
            'dry_run' => $dryRun
        ]);

        foreach ($steps as $step) {
            try {
                $stepResult = $this->migrateStep($entityId, $step, $dryRun, []);
                $stepsCompleted[] = [
                    'step' => $step,
                    'status' => 'success',
                    'records_migrated' => $stepResult['records_migrated'],
                    'duration_ms' => $stepResult['duration_ms']
                ];

                $totalRecords += $stepResult['records_migrated'];
                $totalWarnings += count($stepResult['warnings'] ?? []);
            } catch (Exception $e) {
                $stepsCompleted[] = [
                    'step' => $step,
                    'status' => 'error',
                    'error' => $e->getMessage(),
                    'duration_ms' => 0
                ];
                $totalErrors++;

                LogService::log('Erreur lors de la migration d\'une étape', [
                    'level' => 'error',
                    'migration_id' => $migrationId,
                    'step' => $step,
                    'error' => $e->getMessage()
                ]);

                // Arrêter la migration en cas d'erreur critique
                break;
            }
        }

        $totalDuration = (microtime(true) - $startTime) * 1000;

        LogService::log('Fin de migration entité', [
            'level' => 'info',
            'migration_id' => $migrationId,
            'entity_id' => $entityId,
            'total_records' => $totalRecords,
            'total_errors' => $totalErrors,
            'duration_ms' => $totalDuration
        ]);

        return [
            'entity_name' => $entityDetails['name'],
            'migration_id' => $migrationId,
            'steps_completed' => $stepsCompleted,
            'total_duration_ms' => round($totalDuration, 2),
            'summary' => [
                'total_records' => $totalRecords,
                'total_errors' => $totalErrors,
                'total_warnings' => $totalWarnings
            ]
        ];
    }

    /**
     * Migre une étape spécifique pour une entité
     */
    public function migrateStep(
        int $entityId,
        string $step,
        bool $dryRun = false,
        array $options = []
    ): array {
        $startTime = microtime(true);

        LogService::log("Début migration étape: $step", [
            'level' => 'info',
            'entity_id' => $entityId,
            'step' => $step,
            'dry_run' => $dryRun
        ]);

        // Appeler la méthode spécifique pour chaque étape
        $methodName = 'migrate' . $this->snakeToPascal($step);

        if (!method_exists($this, $methodName)) {
            throw new Exception("Méthode de migration non trouvée pour l'étape: $step");
        }

        $result = $this->$methodName($entityId, $dryRun, $options);

        $duration = (microtime(true) - $startTime) * 1000;

        LogService::log("Fin migration étape: $step", [
            'level' => 'info',
            'entity_id' => $entityId,
            'records_migrated' => $result['records_migrated'],
            'duration_ms' => $duration
        ]);

        return [
            'records_migrated' => $result['records_migrated'],
            'duration_ms' => round($duration, 2),
            'warnings' => $result['warnings'] ?? [],
            'details' => $result['details'] ?? []
        ];
    }

    /**
     * Convertit snake_case en PascalCase
     */
    private function snakeToPascal(string $string): string {
        return str_replace('_', '', ucwords($string, '_'));
    }

    /**
     * Migration des devises (x_devises)
     */
    private function migrateXDevises(int $entityId, bool $dryRun, array $options): array {
        // Les tables x_* sont globales, pas liées à une entité
        $sourceDb = $this->getSourceConnection();

        $stmt = $sourceDb->query("SELECT * FROM x_devises WHERE active = 1");
        $records = $stmt->fetchAll();

        if ($dryRun) {
            return ['records_migrated' => count($records), 'warnings' => []];
        }

        $inserted = 0;
        foreach ($records as $record) {
            $stmt = $this->targetDb->prepare("
                INSERT INTO x_devises (id, code, libelle, symbole, chk_active, created_at, updated_at)
                VALUES (?, ?, ?, ?, ?, NOW(), NOW())
                ON DUPLICATE KEY UPDATE
                    code = VALUES(code),
                    libelle = VALUES(libelle),
                    symbole = VALUES(symbole),
                    chk_active = VALUES(chk_active),
                    updated_at = NOW()
            ");

            $stmt->execute([
                $record['rowid'],
                $record['code'],
                $record['libelle'],
                $record['symbole'],
                $record['active']
            ]);

            $inserted++;
        }

        return ['records_migrated' => $inserted, 'warnings' => []];
    }

    /**
     * Migration des types d'entités (x_entites_types)
     */
    private function migrateXEntitesTypes(int $entityId, bool $dryRun, array $options): array {
        $sourceDb = $this->getSourceConnection();

        $stmt = $sourceDb->query("SELECT * FROM x_entites_types WHERE active = 1");
        $records = $stmt->fetchAll();

        if ($dryRun) {
            return ['records_migrated' => count($records), 'warnings' => []];
        }

        $inserted = 0;
        foreach ($records as $record) {
            $stmt = $this->targetDb->prepare("
                INSERT INTO x_entites_types (id, libelle, chk_active, created_at, updated_at)
                VALUES (?, ?, ?, NOW(), NOW())
                ON DUPLICATE KEY UPDATE
                    libelle = VALUES(libelle),
                    chk_active = VALUES(chk_active),
                    updated_at = NOW()
            ");

            $stmt->execute([
                $record['rowid'],
                $record['libelle'],
                $record['active']
            ]);

            $inserted++;
        }

        return ['records_migrated' => $inserted, 'warnings' => []];
    }

    /**
     * Méthodes de migration pour les autres tables x_* (structure similaire)
     * TODO: Implémenter les autres méthodes
     */
    private function migrateXTypesPassages(int $entityId, bool $dryRun, array $options): array {
        // TODO: À implémenter
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    private function migrateXTypesReglements(int $entityId, bool $dryRun, array $options): array {
        // TODO: À implémenter
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    private function migrateXUsersRoles(int $entityId, bool $dryRun, array $options): array {
        // TODO: À implémenter
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    private function migrateXPays(int $entityId, bool $dryRun, array $options): array {
        // TODO: À implémenter
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    private function migrateXRegions(int $entityId, bool $dryRun, array $options): array {
        // TODO: À implémenter
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    private function migrateXDepartements(int $entityId, bool $dryRun, array $options): array {
        // TODO: À implémenter
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    private function migrateXVilles(int $entityId, bool $dryRun, array $options): array {
        // TODO: À implémenter
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    /**
     * Migration de l'entité (users_entites -> entites)
     */
    private function migrateEntites(int $entityId, bool $dryRun, array $options): array {
        $sourceDb = $this->getSourceConnection();

        $stmt = $sourceDb->prepare("SELECT * FROM users_entites WHERE rowid = ?");
        $stmt->execute([$entityId]);
        $entity = $stmt->fetch();

        if (!$entity) {
            throw new Exception("Entité $entityId non trouvée dans la base source");
        }

        if ($dryRun) {
            return ['records_migrated' => 1, 'warnings' => []];
        }

        // Chiffrement des données sensibles
        $encryptedName = ApiService::encryptData($entity['libelle']);
        $encryptedEmail = !empty($entity['email']) ? ApiService::encryptSearchableData($entity['email']) : null;

        // Gestion des téléphones
        $phone = $entity['tel1'] ?? '';
        $mobile = $entity['tel2'] ?? '';

        // Détection mobile (commence par 06 ou 07)
        if (preg_match('/^0[67]/', $phone)) {
            $mobile = $phone;
            $phone = $entity['tel2'] ?? '';
        }

        $encryptedPhone = !empty($phone) ? ApiService::encryptData($phone) : null;
        $encryptedMobile = !empty($mobile) ? ApiService::encryptData($mobile) : null;
        $encryptedIban = !empty($entity['iban']) ? ApiService::encryptData($entity['iban']) : null;
        $encryptedBic = !empty($entity['bic']) ? ApiService::encryptData($entity['bic']) : null;

        $stmt = $this->targetDb->prepare("
            INSERT INTO entites (
                id, fk_type, encrypted_name, encrypted_email, encrypted_phone, encrypted_mobile,
                code_postal, ville, encrypted_iban, encrypted_bic, chk_active, chk_demo,
                created_at, updated_at
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, NOW(), NOW())
            ON DUPLICATE KEY UPDATE
                fk_type = VALUES(fk_type),
                encrypted_name = VALUES(encrypted_name),
                encrypted_email = VALUES(encrypted_email),
                encrypted_phone = VALUES(encrypted_phone),
                encrypted_mobile = VALUES(encrypted_mobile),
                code_postal = VALUES(code_postal),
                ville = VALUES(ville),
                encrypted_iban = VALUES(encrypted_iban),
                encrypted_bic = VALUES(encrypted_bic),
                chk_active = VALUES(chk_active),
                updated_at = NOW()
        ");

        $stmt->execute([
            $entity['rowid'],
            $entity['fk_type'] ?? 1,
            $encryptedName,
            $encryptedEmail,
            $encryptedPhone,
            $encryptedMobile,
            $entity['cp'] ?? null,
            $entity['ville'] ?? null,
            $encryptedIban,
            $encryptedBic,
            $entity['active']
        ]);

        return ['records_migrated' => 1, 'warnings' => []];
    }

    /**
     * Migration des utilisateurs
     */
    private function migrateUsers(int $entityId, bool $dryRun, array $options): array {
        // TODO: À implémenter (voir migrate_users.php pour la logique)
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    /**
     * Migration des opérations
     */
    private function migrateOperations(int $entityId, bool $dryRun, array $options): array {
        // TODO: À implémenter
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    /**
     * Autres méthodes de migration
     * TODO: Implémenter toutes les méthodes pour chaque étape
     */
    private function migrateOpeSectors(int $entityId, bool $dryRun, array $options): array {
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    private function migrateSectorsAdresses(int $entityId, bool $dryRun, array $options): array {
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    private function migrateOpeUsers(int $entityId, bool $dryRun, array $options): array {
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    private function migrateOpeUsersSectors(int $entityId, bool $dryRun, array $options): array {
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    private function migrateOpePass(int $entityId, bool $dryRun, array $options): array {
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    private function migrateOpePassHisto(int $entityId, bool $dryRun, array $options): array {
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    private function migrateMedias(int $entityId, bool $dryRun, array $options): array {
        return ['records_migrated' => 0, 'warnings' => ['Non implémenté']];
    }

    /**
     * Récupère le statut de migration d'une entité
     */
    public function getMigrationStatus(int $entityId): array {
        // TODO: Implémenter la vérification du statut
        return [
            'entity_id' => $entityId,
            'migrated' => false,
            'steps_completed' => [],
            'last_migration_date' => null
        ];
    }

    /**
     * Récupère les logs de migration d'une entité
     */
    public function getMigrationLogs(int $entityId): array {
        // TODO: Lire les logs depuis LogService
        return [];
    }

    /**
     * Génère un rapport de migration
     */
    public function generateMigrationReport(int $entityId): array {
        // TODO: Implémenter la génération de rapport
        return [
            'entity_id' => $entityId,
            'generated_at' => date('Y-m-d H:i:s'),
            'summary' => []
        ];
    }

    /**
     * Compare les données source vs cible
     */
    public function compareEntityData(int $entityId): array {
        $sourceDb = $this->getSourceConnection();

        $comparison = [];

        // Comparer les counts pour chaque table
        $tables = [
            'users' => 'fk_entite',
            'operations' => 'fk_entite'
        ];

        foreach ($tables as $table => $fkColumn) {
            $sourceTable = $table;
            $targetTable = $table;

            // Cas spéciaux
            if ($table === 'users_entites') {
                $sourceTable = 'users_entites';
                $targetTable = 'entites';
            }

            // Count source
            $stmt = $sourceDb->prepare("SELECT COUNT(*) as count FROM $sourceTable WHERE $fkColumn = ?");
            $stmt->execute([$entityId]);
            $sourceCount = $stmt->fetch()['count'];

            // Count cible
            $stmt = $this->targetDb->prepare("SELECT COUNT(*) as count FROM $targetTable WHERE $fkColumn = ?");
            $stmt->execute([$entityId]);
            $targetCount = $stmt->fetch()['count'];

            $comparison[$table] = [
                'source_count' => $sourceCount,
                'target_count' => $targetCount,
                'difference' => $targetCount - $sourceCount,
                'status' => $sourceCount === $targetCount ? 'ok' : 'warning'
            ];
        }

        return $comparison;
    }

    /**
     * Vérifie l'intégrité des données migrées
     */
    public function verifyMigration(int $entityId): array {
        // TODO: Implémenter les vérifications d'intégrité
        return [
            'entity_id' => $entityId,
            'verified_at' => date('Y-m-d H:i:s'),
            'checks' => [],
            'errors' => [],
            'warnings' => []
        ];
    }

    /**
     * Annule la migration d'une entité (rollback)
     */
    public function rollbackEntity(int $entityId): array {
        // TODO: Implémenter le rollback complet
        $deletedRecords = [];

        // Supprimer dans l'ordre inverse des dépendances
        $tables = array_reverse(self::MIGRATION_STEPS);

        foreach ($tables as $table) {
            // Logique de suppression selon la table
        }

        return ['deleted_records' => $deletedRecords];
    }

    /**
     * Annule une étape spécifique de migration
     */
    public function rollbackStep(int $entityId, string $step): array {
        // TODO: Implémenter le rollback d'une étape
        return ['deleted_records' => []];
    }
}
