<?php
/**
 * Script de Backup Automático de Base de Datos
 * Sistema de Inventario Forestal de Guatemala
 * 
 * Este script crea backups automáticos de la base de datos MySQL
 * y puede ser ejecutado via cron job en cPanel
 */

// Configuración de backup
define('BACKUP_DIR', __DIR__ . '/../backups/database/');
define('MAX_BACKUPS', 30); // Mantener últimos 30 backups
define('COMPRESS_BACKUPS', true);

// Incluir configuración de base de datos
require_once __DIR__ . '/../api/config/database.php';

class DatabaseBackup {
    private $host;
    private $username;
    private $password;
    private $database;
    private $backupDir;
    
    public function __construct() {
        $this->host = DB_HOST;
        $this->username = DB_USER;
        $this->password = DB_PASS;
        $this->database = DB_NAME;
        $this->backupDir = BACKUP_DIR;
        
        // Crear directorio de backup si no existe
        if (!is_dir($this->backupDir)) {
            mkdir($this->backupDir, 0755, true);
        }
    }
    
    /**
     * Crear backup completo de la base de datos
     */
    public function createFullBackup() {
        try {
            $timestamp = date('Y-m-d_H-i-s');
            $filename = "sif_gt_backup_{$timestamp}.sql";
            $filepath = $this->backupDir . $filename;
            
            // Comando mysqldump
            $command = sprintf(
                'mysqldump --host=%s --user=%s --password=%s --single-transaction --routines --triggers --add-drop-table --add-locks --create-options --disable-keys --extended-insert --quick --set-charset %s > %s',
                escapeshellarg($this->host),
                escapeshellarg($this->username),
                escapeshellarg($this->password),
                escapeshellarg($this->database),
                escapeshellarg($filepath)
            );
            
            // Ejecutar backup
            $output = [];
            $returnCode = 0;
            exec($command, $output, $returnCode);
            
            if ($returnCode !== 0) {
                throw new Exception("Error ejecutando mysqldump: " . implode("\n", $output));
            }
            
            // Verificar que el archivo se creó correctamente
            if (!file_exists($filepath) || filesize($filepath) == 0) {
                throw new Exception("El archivo de backup no se creó correctamente");
            }
            
            // Comprimir backup si está habilitado
            if (COMPRESS_BACKUPS) {
                $this->compressBackup($filepath);
                $filename .= '.gz';
                $filepath .= '.gz';
            }
            
            // Registrar backup exitoso
            $this->logBackup($filename, filesize($filepath), 'SUCCESS');
            
            // Limpiar backups antiguos
            $this->cleanOldBackups();
            
            return [
                'success' => true,
                'filename' => $filename,
                'size' => filesize($filepath),
                'path' => $filepath
            ];
            
        } catch (Exception $e) {
            $this->logBackup('', 0, 'ERROR', $e->getMessage());
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
    
    /**
     * Crear backup incremental (solo cambios desde último backup)
     */
    public function createIncrementalBackup() {
        try {
            $timestamp = date('Y-m-d_H-i-s');
            $filename = "sif_gt_incremental_{$timestamp}.sql";
            $filepath = $this->backupDir . $filename;
            
            // Obtener timestamp del último backup
            $lastBackupTime = $this->getLastBackupTime();
            
            if (!$lastBackupTime) {
                // Si no hay backup previo, crear backup completo
                return $this->createFullBackup();
            }
            
            // Crear backup incremental basado en timestamps
            $pdo = new PDO("mysql:host={$this->host};dbname={$this->database}", $this->username, $this->password);
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            
            $sql = "
                SELECT 'datos_campo' as tabla, COUNT(*) as registros 
                FROM datos_campo 
                WHERE fecha_captura > :last_backup
                UNION ALL
                SELECT 'proyectos' as tabla, COUNT(*) as registros 
                FROM proyectos 
                WHERE fecha_creacion > :last_backup
                UNION ALL
                SELECT 'rodales' as tabla, COUNT(*) as registros 
                FROM rodales 
                WHERE fecha_creacion > :last_backup
            ";
            
            $stmt = $pdo->prepare($sql);
            $stmt->execute(['last_backup' => $lastBackupTime]);
            $changes = $stmt->fetchAll(PDO::FETCH_ASSOC);
            
            $totalChanges = array_sum(array_column($changes, 'registros'));
            
            if ($totalChanges == 0) {
                return [
                    'success' => true,
                    'message' => 'No hay cambios desde el último backup',
                    'changes' => 0
                ];
            }
            
            // Crear backup incremental usando mysqldump con WHERE
            $tables = ['datos_campo', 'proyectos', 'rodales'];
            $backupContent = "-- Backup Incremental SIF-GT\n";
            $backupContent .= "-- Fecha: " . date('Y-m-d H:i:s') . "\n";
            $backupContent .= "-- Cambios desde: {$lastBackupTime}\n\n";
            
            foreach ($tables as $table) {
                $whereClause = '';
                switch ($table) {
                    case 'datos_campo':
                        $whereClause = "WHERE fecha_captura > '{$lastBackupTime}'";
                        break;
                    case 'proyectos':
                        $whereClause = "WHERE fecha_creacion > '{$lastBackupTime}'";
                        break;
                    case 'rodales':
                        $whereClause = "WHERE fecha_creacion > '{$lastBackupTime}'";
                        break;
                }
                
                $command = sprintf(
                    'mysqldump --host=%s --user=%s --password=%s --no-create-info --complete-insert --where="%s" %s %s',
                    escapeshellarg($this->host),
                    escapeshellarg($this->username),
                    escapeshellarg($this->password),
                    $whereClause,
                    escapeshellarg($this->database),
                    $table
                );
                
                $output = [];
                exec($command, $output);
                $backupContent .= implode("\n", $output) . "\n\n";
            }
            
            file_put_contents($filepath, $backupContent);
            
            // Comprimir si está habilitado
            if (COMPRESS_BACKUPS) {
                $this->compressBackup($filepath);
                $filename .= '.gz';
                $filepath .= '.gz';
            }
            
            $this->logBackup($filename, filesize($filepath), 'SUCCESS', "Backup incremental - {$totalChanges} cambios");
            
            return [
                'success' => true,
                'filename' => $filename,
                'size' => filesize($filepath),
                'changes' => $totalChanges
            ];
            
        } catch (Exception $e) {
            $this->logBackup('', 0, 'ERROR', $e->getMessage());
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
    
    /**
     * Comprimir archivo de backup
     */
    private function compressBackup($filepath) {
        $compressed = gzopen($filepath . '.gz', 'w9');
        $file = fopen($filepath, 'rb');
        
        while (!feof($file)) {
            gzwrite($compressed, fread($file, 1024 * 512));
        }
        
        fclose($file);
        gzclose($compressed);
        
        // Eliminar archivo original
        unlink($filepath);
    }
    
    /**
     * Limpiar backups antiguos
     */
    private function cleanOldBackups() {
        $files = glob($this->backupDir . 'sif_gt_backup_*.sql*');
        
        // Ordenar por fecha de modificación (más reciente primero)
        usort($files, function($a, $b) {
            return filemtime($b) - filemtime($a);
        });
        
        // Eliminar backups que excedan el límite
        if (count($files) > MAX_BACKUPS) {
            $filesToDelete = array_slice($files, MAX_BACKUPS);
            foreach ($filesToDelete as $file) {
                unlink($file);
                $this->logBackup(basename($file), 0, 'DELETED', 'Backup antiguo eliminado automáticamente');
            }
        }
    }
    
    /**
     * Obtener timestamp del último backup
     */
    private function getLastBackupTime() {
        $logFile = $this->backupDir . 'backup.log';
        if (!file_exists($logFile)) {
            return null;
        }
        
        $lines = file($logFile, FILE_IGNORE_NEW_LINES);
        $lines = array_reverse($lines);
        
        foreach ($lines as $line) {
            if (strpos($line, 'SUCCESS') !== false) {
                $parts = explode(' | ', $line);
                return $parts[0] ?? null;
            }
        }
        
        return null;
    }
    
    /**
     * Registrar actividad de backup
     */
    private function logBackup($filename, $size, $status, $message = '') {
        $logFile = $this->backupDir . 'backup.log';
        $timestamp = date('Y-m-d H:i:s');
        $sizeFormatted = $this->formatBytes($size);
        
        $logEntry = "{$timestamp} | {$status} | {$filename} | {$sizeFormatted} | {$message}\n";
        file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
    }
    
    /**
     * Formatear bytes a formato legible
     */
    private function formatBytes($bytes, $precision = 2) {
        $units = array('B', 'KB', 'MB', 'GB', 'TB');
        
        for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
            $bytes /= 1024;
        }
        
        return round($bytes, $precision) . ' ' . $units[$i];
    }
    
    /**
     * Verificar integridad de backup
     */
    public function verifyBackup($filepath) {
        try {
            if (!file_exists($filepath)) {
                throw new Exception("Archivo de backup no encontrado");
            }
            
            // Si está comprimido, descomprimir temporalmente
            $tempFile = null;
            if (pathinfo($filepath, PATHINFO_EXTENSION) === 'gz') {
                $tempFile = tempnam(sys_get_temp_dir(), 'backup_verify_');
                $gz = gzopen($filepath, 'rb');
                $temp = fopen($tempFile, 'wb');
                
                while (!gzeof($gz)) {
                    fwrite($temp, gzread($gz, 4096));
                }
                
                gzclose($gz);
                fclose($temp);
                $filepath = $tempFile;
            }
            
            // Verificar que el archivo contiene SQL válido
            $content = file_get_contents($filepath, false, null, 0, 1024);
            
            if (strpos($content, 'CREATE TABLE') === false && strpos($content, 'INSERT INTO') === false) {
                throw new Exception("El archivo no parece contener un backup válido");
            }
            
            // Limpiar archivo temporal
            if ($tempFile) {
                unlink($tempFile);
            }
            
            return ['success' => true, 'message' => 'Backup verificado correctamente'];
            
        } catch (Exception $e) {
            if ($tempFile && file_exists($tempFile)) {
                unlink($tempFile);
            }
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }
    
    /**
     * Restaurar backup
     */
    public function restoreBackup($filepath) {
        try {
            if (!file_exists($filepath)) {
                throw new Exception("Archivo de backup no encontrado");
            }
            
            // Verificar backup antes de restaurar
            $verification = $this->verifyBackup($filepath);
            if (!$verification['success']) {
                throw new Exception("Backup no válido: " . $verification['error']);
            }
            
            // Crear backup de seguridad antes de restaurar
            $safetyBackup = $this->createFullBackup();
            if (!$safetyBackup['success']) {
                throw new Exception("No se pudo crear backup de seguridad");
            }
            
            // Preparar archivo para restauración
            $restoreFile = $filepath;
            if (pathinfo($filepath, PATHINFO_EXTENSION) === 'gz') {
                $restoreFile = tempnam(sys_get_temp_dir(), 'restore_');
                $gz = gzopen($filepath, 'rb');
                $temp = fopen($restoreFile, 'wb');
                
                while (!gzeof($gz)) {
                    fwrite($temp, gzread($gz, 4096));
                }
                
                gzclose($gz);
                fclose($temp);
            }
            
            // Ejecutar restauración
            $command = sprintf(
                'mysql --host=%s --user=%s --password=%s %s < %s',
                escapeshellarg($this->host),
                escapeshellarg($this->username),
                escapeshellarg($this->password),
                escapeshellarg($this->database),
                escapeshellarg($restoreFile)
            );
            
            $output = [];
            $returnCode = 0;
            exec($command, $output, $returnCode);
            
            // Limpiar archivo temporal
            if ($restoreFile !== $filepath) {
                unlink($restoreFile);
            }
            
            if ($returnCode !== 0) {
                throw new Exception("Error restaurando backup: " . implode("\n", $output));
            }
            
            $this->logBackup(basename($filepath), filesize($filepath), 'RESTORED', 'Backup restaurado exitosamente');
            
            return [
                'success' => true,
                'message' => 'Backup restaurado exitosamente',
                'safety_backup' => $safetyBackup['filename']
            ];
            
        } catch (Exception $e) {
            $this->logBackup(basename($filepath), 0, 'RESTORE_ERROR', $e->getMessage());
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
    
    /**
     * Obtener lista de backups disponibles
     */
    public function listBackups() {
        $files = glob($this->backupDir . 'sif_gt_*.sql*');
        $backups = [];
        
        foreach ($files as $file) {
            $backups[] = [
                'filename' => basename($file),
                'path' => $file,
                'size' => filesize($file),
                'size_formatted' => $this->formatBytes(filesize($file)),
                'date' => date('Y-m-d H:i:s', filemtime($file)),
                'type' => strpos($file, 'incremental') !== false ? 'incremental' : 'full'
            ];
        }
        
        // Ordenar por fecha (más reciente primero)
        usort($backups, function($a, $b) {
            return strtotime($b['date']) - strtotime($a['date']);
        });
        
        return $backups;
    }
}

// Ejecutar backup si se llama directamente
if (php_sapi_name() === 'cli' || (isset($_GET['action']) && $_GET['action'] === 'backup')) {
    $backup = new DatabaseBackup();
    
    // Determinar tipo de backup
    $type = $_GET['type'] ?? 'full';
    
    if ($type === 'incremental') {
        $result = $backup->createIncrementalBackup();
    } else {
        $result = $backup->createFullBackup();
    }
    
    // Mostrar resultado
    if (php_sapi_name() === 'cli') {
        echo "=== BACKUP DATABASE SCRIPT ===\n";
        echo "Fecha: " . date('Y-m-d H:i:s') . "\n";
        echo "Tipo: " . ucfirst($type) . "\n";
        
        if ($result['success']) {
            echo "Estado: EXITOSO\n";
            echo "Archivo: " . $result['filename'] . "\n";
            echo "Tamaño: " . number_format($result['size'] / 1024, 2) . " KB\n";
            if (isset($result['changes'])) {
                echo "Cambios: " . $result['changes'] . "\n";
            }
        } else {
            echo "Estado: ERROR\n";
            echo "Error: " . $result['error'] . "\n";
        }
        echo "===============================\n";
    } else {
        header('Content-Type: application/json');
        echo json_encode($result);
    }
}
?>