<?php
/**
 * Script AJAX para aplicar actualizaciones automáticas
 * Descarga ZIP y SQL, extrae, reemplaza archivos (excepto config.php) y aplica cambios SQL
 */

// Suprimir errores HTML para que no rompan el JSON
error_reporting(0);
ini_set('display_errors', 0);

// Manejador de errores global para convertir errores a JSON
set_error_handler(function($errno, $errstr, $errfile, $errline) {
    echo json_encode([
        'success' => false,
        'error' => "PHP Error: $errstr in $errfile on line $errline"
    ]);
    exit;
});

set_exception_handler(function($e) {
    echo json_encode([
        'success' => false,
        'error' => "Exception: " . $e->getMessage()
    ]);
    exit;
});

require_once(__DIR__ . '/../functions.php');

// Iniciar sesión si no está iniciada
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

// Solo admin puede ejecutar
if (!isset($_SESSION['tipo_usuario']) || $_SESSION['tipo_usuario'] !== 'admin') {
    echo json_encode(['success' => false, 'error' => 'No autorizado']);
    exit;
}

header('Content-Type: application/json');

// Configuración
$updateDir = realpath(__DIR__ . '/../../') . '/update';
$backupDir = $updateDir . '/backup_' . date('Y-m-d_H-i-s');
$extractDir = $updateDir . '/extracted';
$rootDir = realpath(__DIR__ . '/../../');

// Archivos protegidos que NUNCA se deben sobrescribir
$protectedFiles = [
    'app/config.php',
    '.htaccess',
    'update',
    'documents',
    'pdf',
    'assets/img/customlogo'
];

// Recibir parámetros
$action = $_POST['action'] ?? $_GET['action'] ?? '';
$zipUrl = $_POST['zip_url'] ?? '';
$sqlUrl = $_POST['sql_url'] ?? '';
$newVersion = $_POST['new_version'] ?? '';

try {
    switch ($action) {
        case 'download':
            echo json_encode(downloadUpdate($zipUrl, $sqlUrl, $updateDir));
            break;
            
        case 'extract':
            echo json_encode(extractUpdate($updateDir, $extractDir));
            break;
            
        case 'backup':
            echo json_encode(createBackup($rootDir, $backupDir, $protectedFiles));
            break;
            
        case 'apply':
            echo json_encode(applyUpdate($extractDir, $rootDir, $protectedFiles));
            break;
            
        case 'database':
            echo json_encode(updateDatabase($updateDir));
            break;
            
        case 'cleanup':
            echo json_encode(cleanupUpdate($updateDir, $extractDir));
            break;
            
        case 'full_update':
            // Proceso completo de actualización
            echo json_encode(fullUpdate($zipUrl, $sqlUrl, $newVersion, $updateDir, $extractDir, $backupDir, $rootDir, $protectedFiles));
            break;
            
        case 'list_backups':
            // Listar backups disponibles
            echo json_encode(listBackups($updateDir));
            break;
            
        case 'restore_backup':
            // Restaurar desde un backup
            $backupName = $_POST['backup_name'] ?? '';
            echo json_encode(restoreBackup($updateDir, $backupName, $rootDir, $protectedFiles));
            break;
            
        case 'delete_backup':
            // Eliminar un backup
            $backupName = $_POST['backup_name'] ?? '';
            echo json_encode(deleteBackupDir($updateDir, $backupName));
            break;
            
        default:
            echo json_encode(['success' => false, 'error' => 'Acción no válida']);
    }
} catch (Exception $e) {
    echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}

/**
 * Descarga los archivos de actualización
 */
function downloadUpdate($zipUrl, $sqlUrl, $updateDir) {
    $log = [];
    
    // Crear directorio update si no existe
    if (!is_dir($updateDir)) {
        if (!mkdir($updateDir, 0755, true)) {
            throw new Exception("No se pudo crear el directorio update/");
        }
        $log[] = "Directorio update/ creado";
    }
    
    $log[] = "URL ZIP recibida: " . ($zipUrl ?: "(vacía)");
    
    // Descargar ZIP
    if (!empty($zipUrl)) {
        $zipPath = $updateDir . '/update.zip';
        $log[] = "Descargando ZIP desde: $zipUrl";
        
        $context = stream_context_create([
            'http' => [
                'timeout' => 120,
                'user_agent' => 'FichApp-Updater/1.0'
            ],
            'ssl' => [
                'verify_peer' => false,
                'verify_peer_name' => false
            ]
        ]);
        
        $zipContent = @file_get_contents($zipUrl, false, $context);
        if ($zipContent === false) {
            $error = error_get_last();
            throw new Exception("Error al descargar el ZIP: " . ($error['message'] ?? 'desconocido'));
        }
        
        if (file_put_contents($zipPath, $zipContent) === false) {
            throw new Exception("Error al guardar el ZIP");
        }
        $log[] = "ZIP descargado: " . round(strlen($zipContent) / 1024 / 1024, 2) . " MB";
    } else {
        throw new Exception("No se proporcionó URL del ZIP");
    }
    
    // Descargar SQL
    if (!empty($sqlUrl)) {
        $sqlPath = $updateDir . '/update.sql';
        $log[] = "Descargando SQL...";
        
        $sqlContent = file_get_contents($sqlUrl);
        if ($sqlContent === false) {
            throw new Exception("Error al descargar el SQL");
        }
        
        if (file_put_contents($sqlPath, $sqlContent) === false) {
            throw new Exception("Error al guardar el SQL");
        }
        $log[] = "SQL descargado: " . round(strlen($sqlContent) / 1024, 2) . " KB";
    }
    
    return ['success' => true, 'log' => $log];
}

/**
 * Extrae el ZIP descargado
 */
function extractUpdate($updateDir, $extractDir) {
    $log = [];
    $zipPath = $updateDir . '/update.zip';
    
    if (!file_exists($zipPath)) {
        throw new Exception("No se encontró el archivo ZIP para extraer");
    }
    
    // Crear directorio de extracción
    if (is_dir($extractDir)) {
        deleteDirectory($extractDir);
    }
    mkdir($extractDir, 0755, true);
    
    $zip = new ZipArchive();
    if ($zip->open($zipPath) !== true) {
        throw new Exception("No se pudo abrir el archivo ZIP");
    }
    
    $log[] = "Extrayendo " . $zip->numFiles . " archivos...";
    $zip->extractTo($extractDir);
    $zip->close();
    
    $log[] = "Extracción completada";
    
    return ['success' => true, 'log' => $log];
}

/**
 * Crea backup de los archivos actuales y de la base de datos
 * Solo mantiene un backup, elimina los anteriores
 */
function createBackup($rootDir, $backupDir, $protectedFiles) {
    $log = [];
    
    // Eliminar backups anteriores (solo mantener uno)
    $updateDir = dirname($backupDir);
    if (is_dir($updateDir)) {
        $items = scandir($updateDir);
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') continue;
            $path = $updateDir . '/' . $item;
            if (is_dir($path) && strpos($item, 'backup_') === 0) {
                deleteDirectory($path);
                $log[] = "✓ Eliminado backup anterior: $item";
            }
        }
    }
    
    // Si el directorio ya existe (mismo segundo), añadir microsegundos
    if (is_dir($backupDir)) {
        $backupDir .= '_' . substr(microtime(), 2, 4);
    }
    
    if (!mkdir($backupDir, 0755, true)) {
        throw new Exception("No se pudo crear el directorio de backup");
    }
    
    $log[] = "Creando backup en " . basename($backupDir) . "/...";
    
    // Copiar archivos importantes al backup
    $dirsToBackup = ['app', 'views', 'js', 'css', 'img'];
    $fileCount = 0;
    
    foreach ($dirsToBackup as $dir) {
        $srcDir = $rootDir . '/' . $dir;
        $dstDir = $backupDir . '/' . $dir;
        if (is_dir($srcDir)) {
            copyDirectory($srcDir, $dstDir);
            $fileCount++;
        }
    }
    
    // Backup de archivos en raíz
    $rootFiles = ['index.php', '.htaccess'];
    foreach ($rootFiles as $file) {
        $src = $rootDir . '/' . $file;
        if (file_exists($src)) {
            copy($src, $backupDir . '/' . $file);
            $fileCount++;
        }
    }
    
    $log[] = "Backup de archivos completado: $fileCount elementos";
    
    // Backup de la base de datos SQL
    try {
        $sqlBackup = createSQLBackup($backupDir);
        $log = array_merge($log, $sqlBackup['log']);
    } catch (Exception $e) {
        $log[] = "⚠ No se pudo crear backup SQL: " . $e->getMessage();
    }
    
    // Guardar la versión actual en el backup
    $versionFile = $rootDir . '/app/version.php';
    if (file_exists($versionFile)) {
        $versionContent = file_get_contents($versionFile);
        if (preg_match('/\$version\s*=\s*["\']([^"\']+)["\']/', $versionContent, $matches)) {
            file_put_contents($backupDir . '/version.txt', $matches[1]);
            $log[] = "✓ Versión guardada: " . $matches[1];
        }
    }
    
    return ['success' => true, 'log' => $log, 'backup_path' => $backupDir];
}

/**
 * Crea un backup SQL de la base de datos
 */
function createSQLBackup($backupDir) {
    $log = [];
    
    // Conectar a la base de datos
    $db = conectar();
    if (!$db) {
        throw new Exception("No hay conexión a la base de datos");
    }
    
    $sqlFile = $backupDir . '/database_backup.sql';
    $sql = "-- Backup de base de datos FichApp\n";
    $sql .= "-- Fecha: " . date('Y-m-d H:i:s') . "\n";
    $sql .= "-- -----------------------------------\n\n";
    $sql .= "SET FOREIGN_KEY_CHECKS=0;\n\n";
    
    // Obtener todas las tablas
    $result = $db->query("SHOW TABLES");
    $tables = [];
    while ($row = $result->fetch_row()) {
        $tables[] = $row[0];
    }
    
    $log[] = "Exportando " . count($tables) . " tablas...";
    
    foreach ($tables as $table) {
        // Obtener CREATE TABLE
        $createResult = $db->query("SHOW CREATE TABLE `$table`");
        $createRow = $createResult->fetch_row();
        
        $sql .= "-- Tabla: $table\n";
        $sql .= "DROP TABLE IF EXISTS `$table`;\n";
        $sql .= $createRow[1] . ";\n\n";
        
        // Obtener datos
        $dataResult = $db->query("SELECT * FROM `$table`");
        if ($dataResult && $dataResult->num_rows > 0) {
            $sql .= "-- Datos de $table\n";
            
            while ($row = $dataResult->fetch_assoc()) {
                $values = [];
                foreach ($row as $value) {
                    if ($value === null) {
                        $values[] = 'NULL';
                    } else {
                        $values[] = "'" . $db->real_escape_string($value) . "'";
                    }
                }
                $columns = array_keys($row);
                $sql .= "INSERT INTO `$table` (`" . implode("`, `", $columns) . "`) VALUES (" . implode(", ", $values) . ");\n";
            }
            $sql .= "\n";
        }
    }
    
    $sql .= "SET FOREIGN_KEY_CHECKS=1;\n";
    
    // Guardar archivo
    if (file_put_contents($sqlFile, $sql) === false) {
        throw new Exception("No se pudo guardar el archivo SQL");
    }
    
    $log[] = "✓ Backup SQL creado: database_backup.sql (" . round(strlen($sql) / 1024, 2) . " KB)";
    
    return ['success' => true, 'log' => $log];
}

/**
 * Aplica la actualización copiando archivos nuevos
 */
function applyUpdate($extractDir, $rootDir, $protectedFiles) {
    $log = [];
    $updatedFiles = 0;
    $skippedFiles = 0;
    
    $log[] = "Directorio destino: $rootDir";
    
    // Buscar el directorio raíz dentro del ZIP extraído
    $extractRoot = $extractDir;
    $contents = array_diff(scandir($extractDir), ['.', '..']);
    $dirs = [];
    $files = [];
    
    foreach ($contents as $item) {
        $path = $extractDir . '/' . $item;
        if (is_dir($path)) {
            $dirs[] = $item;
        } else {
            $files[] = $item;
        }
    }
    
    // Log del contenido del ZIP
    $log[] = "Contenido del ZIP: " . count($dirs) . " carpetas, " . count($files) . " archivos";
    if (!empty($dirs)) {
        $log[] = "Carpetas encontradas: " . implode(', ', $dirs);
    }
    
    // Solo usar un subdirectorio como raíz si hay EXACTAMENTE un directorio y ningún archivo
    // Esto es para ZIPs descargados de GitHub que tienen una carpeta contenedora
    if (count($dirs) === 1 && count($files) === 0) {
        $potentialRoot = $extractDir . '/' . $dirs[0];
        // Verificar que este directorio contiene archivos típicos de la raíz
        if (file_exists($potentialRoot . '/index.php')) {
            $extractRoot = $potentialRoot;
            $log[] = "Usando subdirectorio como raíz: {$dirs[0]}";
        }
    }
    
    if ($extractRoot === $extractDir) {
        $log[] = "Usando directorio extraído directamente";
    }
    
    $log[] = "Aplicando actualización...";
    
    // Copiar recursivamente excluyendo archivos protegidos
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($extractRoot, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );
    
    foreach ($iterator as $item) {
        $relativePath = str_replace($extractRoot . '/', '', $item->getPathname());
        $relativePath = str_replace($extractRoot . '\\', '', $relativePath);
        $destPath = $rootDir . '/' . $relativePath;
        
        // Verificar si es un archivo protegido
        $isProtected = false;
        foreach ($protectedFiles as $protected) {
            if (strpos($relativePath, $protected) === 0) {
                $isProtected = true;
                break;
            }
        }
        
        if ($isProtected) {
            $skippedFiles++;
            continue;
        }
        
        if ($item->isDir()) {
            if (!is_dir($destPath)) {
                mkdir($destPath, 0755, true);
            }
        } else {
            // Crear directorio padre si no existe
            $parentDir = dirname($destPath);
            if (!is_dir($parentDir)) {
                mkdir($parentDir, 0755, true);
            }
            
            copy($item->getPathname(), $destPath);
            $updatedFiles++;
        }
    }
    
    $log[] = "Archivos actualizados: $updatedFiles";
    $log[] = "Archivos protegidos (omitidos): $skippedFiles";
    
    return ['success' => true, 'log' => $log, 'updated' => $updatedFiles, 'skipped' => $skippedFiles];
}

/**
 * Actualiza la base de datos comparando esquemas
 */
function updateDatabase($updateDir) {
    $log = [];
    $sqlPath = $updateDir . '/update.sql';
    
    if (!file_exists($sqlPath)) {
        $log[] = "No hay archivo SQL para aplicar";
        return ['success' => true, 'log' => $log, 'changes' => 0];
    }
    
    // Intentar conectar a la base de datos
    try {
        $db = conectar();
    } catch (Exception $e) {
        $log[] = "No hay conexión a base de datos: " . $e->getMessage();
        $log[] = "SQL guardado en update/update.sql para aplicar manualmente";
        return ['success' => true, 'log' => $log, 'changes' => 0];
    }
    
    $log[] = "Analizando esquema SQL...";
    
    // Carga de clase diferida: Esto garantiza que si hemos machacado los archivos 
    // en el paso previo (applyUpdate), PHP cargue a memoria la versión NUEVA
    // del actualizador y no la que había al principio de la llamada.
    require_once(__DIR__ . '/../sql_schema_updater.php');
    $updater = new SQLSchemaUpdater();
    $currentSchema = $updater->getCurrentSchema();
    
    // Si no se pudo obtener el esquema actual, omitir
    if (empty($currentSchema)) {
        $log[] = "No se pudo obtener el esquema actual de la base de datos";
        $log[] = "SQL guardado para aplicar manualmente si es necesario";
        return ['success' => true, 'log' => $log, 'changes' => 0];
    }
    
    $newSchema = $updater->parseSQL($sqlPath);
    $changes = $updater->compareSchemas($newSchema, $currentSchema);
    
    if (empty($changes)) {
        $log[] = "No hay cambios de esquema que aplicar";
        return ['success' => true, 'log' => $log, 'changes' => 0];
    }
    
    $log[] = "Encontrados " . count($changes) . " cambios";
    
    $applied = $updater->applyChanges($changes);
    
    foreach ($applied as $change) {
        $log[] = $change;
    }
    
    return ['success' => true, 'log' => $log, 'changes' => count($applied)];
}

/**
 * Limpia los archivos temporales de actualización
 */
function cleanupUpdate($updateDir, $extractDir) {
    $log = [];
    
    // Eliminar directorio extraído
    if (is_dir($extractDir)) {
        deleteDirectory($extractDir);
        $log[] = "Directorio temporal eliminado";
    }
    
    // Eliminar ZIP
    $zipPath = $updateDir . '/update.zip';
    if (file_exists($zipPath)) {
        unlink($zipPath);
        $log[] = "ZIP eliminado";
    }
    
    // Eliminar SQL
    $sqlPath = $updateDir . '/update.sql';
    if (file_exists($sqlPath)) {
        unlink($sqlPath);
        $log[] = "SQL eliminado";
    }
    
    return ['success' => true, 'log' => $log];
}

/**
 * Proceso completo de actualización
 */
function fullUpdate($zipUrl, $sqlUrl, $newVersion, $updateDir, $extractDir, $backupDir, $rootDir, $protectedFiles) {
    $fullLog = [];
    $steps = [];
    
    try {
        // Paso 1: Descargar
        $fullLog[] = "=== PASO 1: Descarga ===";
        $result = downloadUpdate($zipUrl, $sqlUrl, $updateDir);
        $fullLog = array_merge($fullLog, $result['log']);
        $steps['download'] = true;
        
        // Paso 2: Extraer
        $fullLog[] = "=== PASO 2: Extracción ===";
        $result = extractUpdate($updateDir, $extractDir);
        $fullLog = array_merge($fullLog, $result['log']);
        $steps['extract'] = true;
        
        // Paso 3: Backup
        $fullLog[] = "=== PASO 3: Backup ===";
        $result = createBackup($rootDir, $backupDir, $protectedFiles);
        $fullLog = array_merge($fullLog, $result['log']);
        $steps['backup'] = true;
        
        // Paso 4: Aplicar archivos
        $fullLog[] = "=== PASO 4: Aplicar archivos ===";
        $result = applyUpdate($extractDir, $rootDir, $protectedFiles);
        $fullLog = array_merge($fullLog, $result['log']);
        $steps['apply'] = true;
        
        // Paso 5: Base de datos (solo si hay SQL)
        $fullLog[] = "=== PASO 5: Base de datos ===";
        $result = updateDatabase($updateDir);
        $fullLog = array_merge($fullLog, $result['log']);
        $steps['database'] = true;
        
        // Paso 6: Limpiar
        $fullLog[] = "=== PASO 6: Limpieza ===";
        $result = cleanupUpdate($updateDir, $extractDir);
        $fullLog = array_merge($fullLog, $result['log']);
        $steps['cleanup'] = true;
        
        $fullLog[] = "=== ACTUALIZACIÓN COMPLETADA ===";
        
        return [
            'success' => true,
            'log' => $fullLog,
            'steps' => $steps,
            'message' => "Actualización completada con éxito"
        ];
        
    } catch (Exception $e) {
        $fullLog[] = "ERROR: " . $e->getMessage();
        return [
            'success' => false,
            'error' => $e->getMessage(),
            'log' => $fullLog,
            'steps' => $steps
        ];
    }
}

/**
 * Funciones auxiliares
 */
function deleteDirectory($dir) {
    if (!is_dir($dir)) return;
    
    $files = array_diff(scandir($dir), ['.', '..']);
    foreach ($files as $file) {
        $path = $dir . '/' . $file;
        is_dir($path) ? deleteDirectory($path) : unlink($path);
    }
    rmdir($dir);
}

function copyDirectory($src, $dst) {
    if (!is_dir($dst)) {
        mkdir($dst, 0755, true);
    }
    
    $dir = opendir($src);
    while (($file = readdir($dir)) !== false) {
        if ($file !== '.' && $file !== '..') {
            $srcPath = $src . '/' . $file;
            $dstPath = $dst . '/' . $file;
            
            if (is_dir($srcPath)) {
                copyDirectory($srcPath, $dstPath);
            } else {
                copy($srcPath, $dstPath);
            }
        }
    }
    closedir($dir);
}

/**
 * Lista todos los backups disponibles
 */
function listBackups($updateDir) {
    $backups = [];
    
    if (!is_dir($updateDir)) {
        return ['success' => true, 'backups' => []];
    }
    
    $items = scandir($updateDir);
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') continue;
        
        $path = $updateDir . '/' . $item;
        if (is_dir($path) && strpos($item, 'backup_') === 0) {
            // Obtener información del backup
            $hasSql = file_exists($path . '/database_backup.sql');
            $size = getDirectorySize($path);
            
            // Extraer fecha del nombre
            $dateStr = str_replace('backup_', '', $item);
            $dateParts = explode('_', $dateStr);
            $date = isset($dateParts[0]) ? $dateParts[0] : '';
            $time = isset($dateParts[1]) ? str_replace('-', ':', $dateParts[1]) : '';
            
            // Leer versión del backup si existe
            $backupVersion = '';
            $versionFile = $path . '/version.txt';
            if (file_exists($versionFile)) {
                $backupVersion = trim(file_get_contents($versionFile));
            }
            
            $backups[] = [
                'name' => $item,
                'path' => $path,
                'date' => $date,
                'time' => $time,
                'has_sql' => $hasSql,
                'size' => $size,
                'size_formatted' => formatBytes($size),
                'version' => $backupVersion
            ];
        }
    }
    
    // Ordenar por fecha descendente (más reciente primero)
    usort($backups, function($a, $b) {
        return strcmp($b['name'], $a['name']);
    });
    
    // Obtener versión actual del sistema
    $currentVersion = '';
    $versionFile = realpath(__DIR__ . '/../../') . '/app/version.php';
    if (file_exists($versionFile)) {
        $versionContent = file_get_contents($versionFile);
        if (preg_match('/\$version\s*=\s*["\']([^"\']+)["\']/', $versionContent, $matches)) {
            $currentVersion = $matches[1];
        }
    }
    
    return ['success' => true, 'backups' => $backups, 'current_version' => $currentVersion];
}

/**
 * Restaura un backup completo (archivos + base de datos)
 */
function restoreBackup($updateDir, $backupName, $rootDir, $protectedFiles) {
    $log = [];
    
    if (empty($backupName)) {
        throw new Exception("No se especificó el nombre del backup");
    }
    
    // Validar que el nombre no contenga caracteres peligrosos
    if (preg_match('/[^a-zA-Z0-9_\-]/', $backupName)) {
        throw new Exception("Nombre de backup inválido");
    }
    
    $backupPath = $updateDir . '/' . $backupName;
    
    if (!is_dir($backupPath)) {
        throw new Exception("El backup no existe: $backupName");
    }
    
    $log[] = "Restaurando desde: $backupName";
    
    // Restaurar archivos
    $log[] = "=== Restaurando archivos ===";
    $restoredFiles = 0;
    
    $dirsToRestore = ['app', 'views', 'js', 'css', 'img'];
    foreach ($dirsToRestore as $dir) {
        $srcDir = $backupPath . '/' . $dir;
        $dstDir = $rootDir . '/' . $dir;
        
        if (is_dir($srcDir)) {
            // No eliminar el directorio destino, solo sobrescribir archivos
            restoreDirectory($srcDir, $dstDir, $protectedFiles);
            $restoredFiles++;
            $log[] = "✓ Restaurado: $dir/";
        }
    }
    
    // Restaurar archivos en raíz (excepto protegidos)
    $rootFiles = ['index.php'];
    foreach ($rootFiles as $file) {
        $src = $backupPath . '/' . $file;
        $dst = $rootDir . '/' . $file;
        
        if (file_exists($src) && !in_array($file, $protectedFiles)) {
            copy($src, $dst);
            $restoredFiles++;
            $log[] = "✓ Restaurado: $file";
        }
    }
    
    $log[] = "Archivos restaurados: $restoredFiles carpetas/archivos";
    
    // Restaurar base de datos si existe el backup SQL
    $sqlFile = $backupPath . '/database_backup.sql';
    if (file_exists($sqlFile)) {
        $log[] = "=== Restaurando base de datos ===";
        try {
            $dbResult = restoreDatabase($sqlFile);
            $log = array_merge($log, $dbResult['log']);
        } catch (Exception $e) {
            $log[] = "⚠ Error al restaurar la base de datos: " . $e->getMessage();
        }
    } else {
        $log[] = "⚠ No se encontró backup de base de datos";
    }
    
    $log[] = "=== RESTAURACIÓN COMPLETADA ===";
    
    return ['success' => true, 'log' => $log];
}

/**
 * Restaura un directorio desde backup
 */
function restoreDirectory($src, $dst, $protectedFiles) {
    if (!is_dir($dst)) {
        mkdir($dst, 0755, true);
    }
    
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($src, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );
    
    foreach ($iterator as $item) {
        $relativePath = str_replace($src . '/', '', $item->getPathname());
        $relativePath = str_replace($src . '\\', '', $relativePath);
        $destPath = $dst . '/' . $relativePath;
        
        // Verificar si es un archivo protegido
        $isProtected = false;
        foreach ($protectedFiles as $protected) {
            if (strpos($relativePath, $protected) !== false) {
                $isProtected = true;
                break;
            }
        }
        
        if ($isProtected) {
            continue;
        }
        
        if ($item->isDir()) {
            if (!is_dir($destPath)) {
                mkdir($destPath, 0755, true);
            }
        } else {
            $parentDir = dirname($destPath);
            if (!is_dir($parentDir)) {
                mkdir($parentDir, 0755, true);
            }
            copy($item->getPathname(), $destPath);
        }
    }
}

/**
 * Restaura la base de datos de forma inteligente comparando esquemas
 * - Tablas en backup pero no en BD actual → Crear tabla + importar datos
 * - Tablas en BD actual pero no en backup → Eliminar tabla
 * - Tablas en ambas → Comparar columnas y sincronizar datos
 */
function restoreDatabase($sqlFile) {
    $log = [];
    
    $db = conectar();
    if (!$db) {
        throw new Exception("No hay conexión a la base de datos");
    }
    
    $sqlContent = file_get_contents($sqlFile);
    if (!$sqlContent) {
        throw new Exception("No se pudo leer el archivo SQL");
    }
    
    $log[] = "Analizando backup para restauración inteligente...";
    
    // Parsear el backup SQL para extraer tablas y sus estructuras
    $backupTables = parseBackupSQL($sqlContent);
    $log[] = "✓ Encontradas " . count($backupTables) . " tablas en el backup";
    
    // Obtener tablas actuales de la base de datos
    $currentTables = getCurrentTables($db);
    $log[] = "✓ Encontradas " . count($currentTables) . " tablas en la BD actual";
    
    // Deshabilitar verificación de claves foráneas temporalmente
    $db->query("SET FOREIGN_KEY_CHECKS=0");
    mysqli_report(MYSQLI_REPORT_OFF);
    
    $backupTableNames = array_keys($backupTables);
    $currentTableNames = array_keys($currentTables);
    
    // 1. Tablas que están en backup pero no en BD actual → CREAR
    $tablesToCreate = array_diff($backupTableNames, $currentTableNames);
    foreach ($tablesToCreate as $tableName) {
        $log[] = "➕ Creando tabla nueva: $tableName";
        // Ejecutar CREATE TABLE
        $db->query($backupTables[$tableName]['create']);
        // Insertar datos
        foreach ($backupTables[$tableName]['inserts'] as $insert) {
            @$db->query($insert);
        }
        $log[] = "  ✓ Insertados " . count($backupTables[$tableName]['inserts']) . " registros";
    }
    
    // 2. Tablas que están en BD actual pero no en backup → ELIMINAR
    $tablesToDrop = array_diff($currentTableNames, $backupTableNames);
    foreach ($tablesToDrop as $tableName) {
        $log[] = "➖ Eliminando tabla: $tableName";
        $db->query("DROP TABLE IF EXISTS `$tableName`");
    }
    
    // 3. Tablas que existen en ambas → COMPARAR Y SINCRONIZAR SOLO ESQUEMA
    $tablesToSync = array_intersect($backupTableNames, $currentTableNames);
    foreach ($tablesToSync as $tableName) {
        $changes = syncTableSchema($db, $tableName, $backupTables[$tableName], $currentTables[$tableName]);
        if (!empty($changes)) {
            $log[] = "🔄 Sincronizando esquema de tabla: $tableName";
            foreach ($changes as $change) {
                $log[] = "  " . $change;
            }
        }
        // NO sincronizar datos - solo esquema para preservar registros actuales
    }
    
    // Rehabilitar verificación de claves foráneas
    $db->query("SET FOREIGN_KEY_CHECKS=1");
    mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
    
    $log[] = "✓ Restauración inteligente completada";
    
    return ['success' => true, 'log' => $log];
}

/**
 * Parsea el contenido SQL del backup y extrae tablas con sus estructuras
 */
function parseBackupSQL($sqlContent) {
    $tables = [];
    
    // Encontrar CREATE TABLE y sus datos
    preg_match_all('/^CREATE TABLE\s+`?(\w+)`?\s*\((.*?)\)\s*(ENGINE.*?)?;/ims', $sqlContent, $createMatches, PREG_SET_ORDER);
    
    foreach ($createMatches as $match) {
        $tableName = $match[1];
        $fullCreate = $match[0];
        
        // Extraer columnas del CREATE TABLE
        $columns = parseColumns($match[2]);
        
        $tables[$tableName] = [
            'create' => $fullCreate,
            'columns' => $columns,
            'inserts' => []
        ];
    }
    
    // Encontrar INSERTs para cada tabla
    preg_match_all('/^INSERT INTO `?(\w+)`?\s*\(.*?\)\s*VALUES\s*\(.*?\);/ims', $sqlContent, $insertMatches, PREG_SET_ORDER);
    
    foreach ($insertMatches as $match) {
        $tableName = $match[1];
        if (isset($tables[$tableName])) {
            $tables[$tableName]['inserts'][] = $match[0];
        }
    }
    
    return $tables;
}

/**
 * Parsea las columnas de una definición CREATE TABLE
 */
function parseColumns($columnDef) {
    $columns = [];
    $lines = explode("\n", $columnDef);
    
    foreach ($lines as $line) {
        $line = trim($line);
        // Ignorar líneas de PRIMARY KEY, INDEX, CONSTRAINT, etc.
        if (preg_match('/^`(\w+)`\s+(.+?)(?:,|$)/i', $line, $match)) {
            $colName = $match[1];
            $colDef = trim(rtrim($match[2], ','));
            $columns[$colName] = $colDef;
        }
    }
    
    return $columns;
}

/**
 * Obtiene las tablas actuales de la base de datos con sus estructuras
 */
function getCurrentTables($db) {
    $tables = [];
    
    $result = $db->query("SHOW TABLES");
    while ($row = $result->fetch_row()) {
        $tableName = $row[0];
        
        // Obtener CREATE TABLE
        $createResult = $db->query("SHOW CREATE TABLE `$tableName`");
        $createRow = $createResult->fetch_row();
        
        // Obtener columnas
        $columns = [];
        $colResult = $db->query("SHOW COLUMNS FROM `$tableName`");
        while ($col = $colResult->fetch_assoc()) {
            $columns[$col['Field']] = $col['Type'];
        }
        
        $tables[$tableName] = [
            'create' => $createRow[1],
            'columns' => $columns
        ];
    }
    
    return $tables;
}

/**
 * Sincroniza el esquema de una tabla: añade columnas faltantes, elimina las sobrantes
 */
function syncTableSchema($db, $tableName, $backupTable, $currentTable) {
    $changes = [];
    
    $backupColumns = array_keys($backupTable['columns']);
    $currentColumns = array_keys($currentTable['columns']);
    
    // Columnas que están en backup pero no en tabla actual → AÑADIR
    $columnsToAdd = array_diff($backupColumns, $currentColumns);
    foreach ($columnsToAdd as $colName) {
        $colDef = $backupTable['columns'][$colName];
        $sql = "ALTER TABLE `$tableName` ADD COLUMN `$colName` $colDef";
        if (@$db->query($sql)) {
            $changes[] = "➕ Añadida columna: $colName";
        }
    }
    
    // Columnas que están en tabla actual pero no en backup → ELIMINAR
    $columnsToDrop = array_diff($currentColumns, $backupColumns);
    foreach ($columnsToDrop as $colName) {
        $sql = "ALTER TABLE `$tableName` DROP COLUMN `$colName`";
        if (@$db->query($sql)) {
            $changes[] = "➖ Eliminada columna: $colName";
        }
    }
    
    return $changes;
}

/**
 * Elimina un backup específico
 */
function deleteBackupDir($updateDir, $backupName) {
    if (empty($backupName)) {
        throw new Exception("No se especificó el nombre del backup");
    }
    
    // Validar que el nombre no contenga caracteres peligrosos
    if (preg_match('/[^a-zA-Z0-9_\-]/', $backupName)) {
        throw new Exception("Nombre de backup inválido");
    }
    
    // Validar que es un backup (empieza con backup_)
    if (strpos($backupName, 'backup_') !== 0) {
        throw new Exception("No es un directorio de backup válido");
    }
    
    $backupPath = $updateDir . '/' . $backupName;
    
    if (!is_dir($backupPath)) {
        throw new Exception("El backup no existe: $backupName");
    }
    
    deleteDirectory($backupPath);
    
    return ['success' => true, 'message' => "Backup eliminado: $backupName"];
}

/**
 * Obtiene el tamaño total de un directorio
 */
function getDirectorySize($dir) {
    $size = 0;
    
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
    );
    
    foreach ($iterator as $file) {
        if ($file->isFile()) {
            $size += $file->getSize();
        }
    }
    
    return $size;
}

/**
 * Formatea bytes a formato legible
 */
function formatBytes($bytes, $precision = 2) {
    $units = ['B', 'KB', 'MB', 'GB'];
    
    $bytes = max($bytes, 0);
    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
    $pow = min($pow, count($units) - 1);
    
    $bytes /= pow(1024, $pow);
    
    return round($bytes, $precision) . ' ' . $units[$pow];
}
