<?php
// PHP database backup script with smooth progress updates
// Disable error display for production
ini_set('display_errors', 0);
error_reporting(0);

// Set longer time limits and larger memory limit
set_time_limit(0); // No time limit
ini_set('memory_limit', '2048M'); // Increase memory limit

// Set up progress reporting variables
$backup_dir = "/home/anesoft/public_html/backups";
$progress_file = $backup_dir . '/db_backup_progress.json';
$current_progress = 0;
$last_message = '';
$animation_dots = ['', '.', '..', '...'];
$animation_index = 0;
$last_animation_time = 0;

// Flag to disable progress updates once complete
$progress_complete = false;

// Function to update progress with smooth increments
function update_progress($progress, $message = '') {
    global $progress_file, $current_progress, $last_message, $animation_dots, $animation_index, $last_animation_time, $progress_complete;
    
    // If progress is marked complete, never update it again
    if ($progress_complete) {
        return;
    }
    
    // If this is the 100% update, mark progress as complete
    if ($progress === 100) {
        $progress_complete = true;
    }
    
    $now = microtime(true);
    $should_update = false;
    
    // Update if progress has changed
    if ($progress > $current_progress || $progress == -1) {
        $current_progress = $progress;
        $should_update = true;
    }
    
    // Update if message has changed (but avoid empty messages)
    if ($message !== null && $message !== $last_message && !empty($message)) {
        $last_message = $message;
        $should_update = true;
    }
    
    // Update animation dots every 250ms regardless of other changes
    if ($now - $last_animation_time >= 0.25) {
        $animation_index = ($animation_index + 1) % count($animation_dots);
        $last_animation_time = $now;
        $should_update = true;
    }
    
    // Only write to file if there's a reason to update
    if ($should_update) {
        // Add animated dots to the message for table processing
        $animated_message = $message;
        if (!empty($message) && strpos($message, 'Processing') !== false) {
            $animated_message = $message . $animation_dots[$animation_index];
        }
        
        $data = [
            'progress' => $progress,
            'message' => $animated_message,
            'time' => time()
        ];
        
        file_put_contents($progress_file, json_encode($data));
        clearstatcache(true, $progress_file);
    }
}

// Function to continuously update animation without changing progress
function animate_progress($message) {
    global $current_progress;
    update_progress($current_progress, $message);
}

// Check if this is a progress check request
if (isset($_GET['check_progress'])) {
    header('Content-Type: application/json');
    if (file_exists($progress_file)) {
        echo file_get_contents($progress_file);
    } else {
        echo json_encode(['progress' => 0, 'message' => 'Not started']);
    }
    exit;
}

// Handle AJAX request to start backup
if (isset($_GET['start_backup']) && $_GET['start_backup'] == '1') {
    // Tell browser we're returning JSON
    header('Content-Type: application/json');
    
    // Initialize progress
    update_progress(1, 'Starting database backup process');
    
    // Rest of the backup process...
} else {
    // This is an initial page load - return current progress
    header('Content-Type: application/json');
    if (file_exists($progress_file)) {
        echo file_get_contents($progress_file);
    } else {
        echo json_encode(['progress' => 0, 'message' => 'Not started']);
    }
    exit;
}

// Start a background animation process for large operations
$animation_pid = null;

// Log array 
$log = array();
$log[] = "Starting database backup at " . date('Y-m-d H:i:s');

// Database connection settings
try {
    // Path to config file
    update_progress(2, 'Loading configuration');
    $config_path = $_SERVER['DOCUMENT_ROOT'] . '/configs/db.config.php';
    
    // Load database configuration
    if (file_exists($config_path)) {
        $log[] = "Loading database configuration from file";
        require_once($config_path);
        
        // Check if config variables exist
        if (!isset($db_config) || !is_array($db_config)) {
            throw new Exception("Database configuration not properly defined in config file");
        }
    } else {
        throw new Exception("Database configuration file not found at: $config_path");
    }
    
    // Backup directory and file
    update_progress(3, 'Setting up backup directory');
    $date = date('Y-m-d_H-i');
    $uncompressed_file = "{$backup_dir}/backup_app.anesoft.com_database_{$date}.sql";
    $backup_file = "{$backup_dir}/backup_app.anesoft.com_database_{$date}.sql.gz";
    
    // Create backup directory if needed
    if (!is_dir($backup_dir)) {
        if (!mkdir($backup_dir, 0755, true)) {
            throw new Exception("Failed to create backup directory");
        }
        $log[] = "Created backup directory";
    }
    
    // Test if directory is writable
    if (!is_writable($backup_dir)) {
        throw new Exception("Backup directory is not writable");
    }
    
    // Connect to database
    update_progress(5, 'Connecting to database');
    $mysqli = new mysqli($db_config['host'], $db_config['username'], $db_config['password'], $db_config['database']);
    
    // Check connection
    if ($mysqli->connect_error) {
        throw new Exception("Database connection failed: " . $mysqli->connect_error);
    }
    
    $log[] = "Connected to database successfully";
    update_progress(7, 'Connected to database');
    
    // Set charset
    $mysqli->set_charset("utf8");
    
    // Get all tables
    update_progress(10, 'Getting table list');
    $tables = array();
    $result = $mysqli->query("SHOW TABLES");
    
    if (!$result) {
        throw new Exception("Failed to retrieve tables: " . $mysqli->error);
    }
    
    while ($row = $result->fetch_row()) {
        $tables[] = $row[0];
    }
    
    $total_tables = count($tables);
    $log[] = "Found " . $total_tables . " tables";
    update_progress(12, "Found $total_tables tables");
    
    // Memory-efficient backup approach
    $fileHandler = fopen($uncompressed_file, 'w');
    
    if (!$fileHandler) {
        throw new Exception("Failed to open output file for writing");
    }
    
    // Write header
    fwrite($fileHandler, "-- Database Backup\n");
    fwrite($fileHandler, "-- Generated: " . date('Y-m-d H:i:s') . "\n");
    fwrite($fileHandler, "-- Database: `$db_name`\n\n");
    fwrite($fileHandler, "SET FOREIGN_KEY_CHECKS=0;\n");
    fwrite($fileHandler, "SET SQL_MODE = \"NO_AUTO_VALUE_ON_ZERO\";\n");
    fwrite($fileHandler, "SET time_zone = \"+00:00\";\n\n");
    
    // Calculate progress increments for each phase
    // Allocate progress percentages:
    // - Table structure & prep: 0-15%
    // - Data dumping: 15-80% 
    // - Compression: 80-99%
    // - Cleanup & finish: 99-100%
    $setup_progress = 15; // Progress after setup
    $dump_progress = 80;  // Progress after dump
    $progress_per_table = ($dump_progress - $setup_progress) / $total_tables;
    
    // Process each table
    foreach ($tables as $table_index => $table) {
        // Calculate current progress based on completed tables
        $current_table_progress = $setup_progress + ($table_index * $progress_per_table);
        $next_table_progress = $current_table_progress + $progress_per_table;
        
        update_progress(round($current_table_progress), "Processing table: $table");
        $log[] = "Processing table: $table";
        
        // Get create table statement
        $result = $mysqli->query("SHOW CREATE TABLE `$table`");
        
        if (!$result) {
            throw new Exception("Failed to get structure for table $table: " . $mysqli->error);
        }
        
        $row = $result->fetch_row();
        
        // Write table structure
        fwrite($fileHandler, "\n--\n-- Table structure for table `$table`\n--\n\n");
        fwrite($fileHandler, "DROP TABLE IF EXISTS `$table`;\n");
        fwrite($fileHandler, $row[1] . ";\n\n");
        
        $result->free();
        
        // Get row count for large tables and use chunking
        $count_result = $mysqli->query("SELECT COUNT(*) FROM `$table`");
        $count_row = $count_result->fetch_row();
        $total_rows = $count_row[0];
        $count_result->free();
        
        // Table size determines how we handle progress updates
        // Small tables: normal updates
        // Medium tables: more frequent updates
        // Large tables: continuous animation + micro-progress updates
        $table_size_category = 'small';
        if ($total_rows > 50000) {
            $table_size_category = 'large';
        } elseif ($total_rows > 5000) {
            $table_size_category = 'medium';
        }
        
        if ($total_rows > 0) {
            $log[] = "- Table $table has $total_rows rows";
            
            fwrite($fileHandler, "--\n-- Dumping data for table `$table`\n--\n\n");
            
            // Process in chunks of 1000 rows to avoid memory issues
            $chunk_size = 1000;
            $num_chunks = ceil($total_rows / $chunk_size);
            $progress_per_chunk = $progress_per_table / $num_chunks;
            
            // Set up background animation for large tables
            if ($table_size_category == 'large') {
                // Create independent progress updater for this table
                register_shutdown_function(function() use ($progress_file) {
                    // Clean up on exit
                    if (file_exists($progress_file)) {
                        @unlink($progress_file);
                    }
                });
                
                // Set initial progress
                $table_message = "Processing $table (0% of $total_rows rows)";
                update_progress(round($current_table_progress), $table_message);
            }
            
            // Track total rows processed across all chunks
            $total_rows_processed = 0;
            $last_animation_time = microtime(true);
            $last_percent_complete = 0;
            
            for ($chunk = 0; $chunk < $num_chunks; $chunk++) {
                $offset = $chunk * $chunk_size;
                
                // Calculate current progress for this chunk
                $chunk_start_progress = $current_table_progress + ($chunk * $progress_per_chunk);
                
                // Use consistent message format based on table size
                if ($table_size_category == 'large') {
                    // For large tables, always use row-based format
                    // Start with at least 1 row to avoid 0% display
                    $row_count = max(1, $total_rows_processed);
                    $initialPercent = round(($row_count / $total_rows) * 100);
                    $chunk_message = "Processing $table: $initialPercent% ($row_count of $total_rows rows)";
                } else {
                    // For small/medium tables, use chunk-based format
                    $percent_complete = round(($chunk / $num_chunks) * 100);
                    $chunk_message = "Processing $table: $percent_complete% (chunk " . ($chunk+1) . " of $num_chunks)";
                }
                
                // For large tables, update at least at the start of each chunk
                if ($table_size_category == 'large' || $table_size_category == 'medium') {
                    update_progress(round($chunk_start_progress), $chunk_message);
                    $last_animation_time = microtime(true);
                }
                
                $data_query = "SELECT * FROM `$table` LIMIT $offset, $chunk_size";
                $data_result = $mysqli->query($data_query);
                
                if (!$data_result) {
                    throw new Exception("Failed to get data for table $table chunk $chunk: " . $mysqli->error);
                }
                
                $fields_count = $data_result->field_count;
                $rows_in_chunk = $data_result->num_rows;
                
                if ($rows_in_chunk > 0) {
                    // Start INSERT statement for this chunk
                    fwrite($fileHandler, "INSERT INTO `$table` VALUES\n");
                    
                    $row_counter = 0;
                    $last_animation_check = microtime(true);
                    
                    while ($row = $data_result->fetch_row()) {
                        fwrite($fileHandler, "(");
                        
                        for ($j = 0; $j < $fields_count; $j++) {
                            if (isset($row[$j])) {
                                if (is_numeric($row[$j])) {
                                    fwrite($fileHandler, $row[$j]);
                                } else {
                                    fwrite($fileHandler, "'" . $mysqli->real_escape_string($row[$j]) . "'");
                                }
                            } else {
                                fwrite($fileHandler, "NULL");
                            }
                            
                            if ($j < ($fields_count - 1)) {
                                fwrite($fileHandler, ", ");
                            }
                        }
                        
                        $row_counter++;
                        
                        if ($row_counter < $rows_in_chunk) {
                            fwrite($fileHandler, "),\n");
                        } else {
                            fwrite($fileHandler, ");\n\n");
                        }
                        
                        // Update progress based on rows processed within large tables
                        $total_rows_processed++;
                        $now = microtime(true);
                        
                        // For large tables, frequently update both animation and progress
                        if ($table_size_category == 'large' && $now - $last_animation_time >= 0.1) {
                            $last_animation_time = $now;
                            
                            // Calculate exact progress based on total rows processed
                            // Make sure we never display 0%
                            $row_count = max(1, $total_rows_processed);
                            $overall_percent = max(1, round(($row_count / $total_rows) * 100));
                            $precise_progress = $current_table_progress + ($progress_per_table * ($row_count / $total_rows));
                            
                            // For large tables, always use the row format, not chunk format
                            update_progress(round($precise_progress), "Processing $table: $overall_percent% ($row_count of $total_rows rows)");
                        } 
                        // For medium tables, update less frequently but still smooth
                        else if ($table_size_category == 'medium' && $now - $last_animation_time >= 0.5) {
                            $last_animation_time = $now;
                            $overall_percent = round(($total_rows_processed / $total_rows) * 100);
                            $precise_progress = $current_table_progress + ($progress_per_table * ($total_rows_processed / $total_rows));
                            
                            // For medium tables, use chunk format for consistency
                            update_progress(round($precise_progress), "Processing $table: $overall_percent% (chunk " . ($chunk+1) . " of $num_chunks)");
                        }
                    }
                    
                    // Free result set after processing each chunk
                    $data_result->free();
                    
                    // Update progress at the end of the chunk
                    update_progress(round($chunk_start_progress + $progress_per_chunk), 
                                   "Processed chunk " . ($chunk+1) . " of $num_chunks for $table");
                }
            }
        } else {
            $log[] = "- Table $table is empty";
        }
        
        // Update to the progress at the end of this table
        update_progress(round($next_table_progress), "Finished processing table: $table");
    }
    
    // Write footer
    fwrite($fileHandler, "\nSET FOREIGN_KEY_CHECKS=1;\n");
    
    // Close the file
    fclose($fileHandler);
    
    // Small smooth transition from tables to compression
    $transition_steps = 10;
    $transition_increment = ($dump_progress - $current_progress) / $transition_steps;
    for ($i = 0; $i < $transition_steps; $i++) {
        $transition_progress = $current_progress + ($i * $transition_increment);
        update_progress(round($transition_progress), "SQL dump completed, preparing compression");
        usleep(10000); // 10ms delay
    }
    
    $log[] = "SQL dump completed, compressing file";
    update_progress($dump_progress, "SQL dump completed, starting file compression");
    
    // Compress the file - track progress during compression
    $success = false;
    
    // Use gzwrite method - more memory efficient
    $fp = fopen($uncompressed_file, 'rb');
    $gz = gzopen($backup_file, 'wb9');
    
    if ($fp && $gz) {
        // Get file size for progress tracking
        $file_size = filesize($uncompressed_file);
        $bytes_processed = 0;
        $compression_progress_range = 99 - $dump_progress; // % allocated for compression
        $last_progress_update = $dump_progress;
        $last_update_time = microtime(true);
        
        update_progress($dump_progress + 1, "Starting compression: 0% of " . round($file_size/1048576, 2) . " MB");
        
        // Read in smaller chunks for more frequent updates
        $chunk_size = 131072; // 128KB for more frequent updates
        while (!feof($fp)) {
            $data = fread($fp, $chunk_size);
            $bytes_processed += strlen($data);
            gzwrite($gz, $data);
            
            $now = microtime(true);
            
            // Update progress based on percentage of file compressed
            $percent_done = round(($bytes_processed / $file_size) * 100);
            $compression_progress = $dump_progress + (($bytes_processed / $file_size) * $compression_progress_range);
            
            // Update more frequently - every 100ms for smooth animation
            if ($now - $last_update_time >= 0.1) {
                $last_update_time = $now;
                $mb_processed = round($bytes_processed / 1048576, 2);
                $total_mb = round($file_size / 1048576, 2);
                update_progress(round($compression_progress), "Compressing database: $percent_done% ($mb_processed MB of $total_mb MB)");
            }
        }
        
        fclose($fp);
        gzclose($gz);
        
        // Delete the uncompressed file
        unlink($uncompressed_file);
        
        $success = true;
    } else {
        throw new Exception("Failed to compress the database backup");
    }
    
    // Check if compressed file exists and has size
    if (file_exists($backup_file) && filesize($backup_file) > 0) {
        $size = filesize($backup_file);
        $size_text = ($size > 1048576) ? round($size/1048576, 2) . " MB" : round($size/1024, 2) . " KB";
        $log[] = "Database backup created successfully: " . $size_text;
        
        // Close database connection
        $mysqli->close();
        
        $log[] = "Finished database backup at " . date('Y-m-d H:i:s');
        
        // Create and write the FINAL progress file that won't be overwritten
        // Format complete message with file details
        $full_message = "Database backup completed successfully!\n\n" .
                      "File: $backup_file\n" .
                      "Size: $size_text\n" .
                      "Time: " . date('Y-m-d H:i:s');
        
        $final_data = [
            'progress' => 100,
            'message' => $full_message,
            'backup_file' => $backup_file,
            'backup_size' => $size_text,
            'timestamp' => date('Y-m-d H:i:s'),
            'success' => true,
            'log' => $log,
            'time' => time()
        ];
        
        // Set the global flag to prevent any further updates
        $progress_complete = true;
        
        // Write using file_put_contents with exclusive lock to prevent race conditions
        file_put_contents($progress_file, json_encode($final_data), LOCK_EX);
        
        // Return success response
        header('Content-Type: application/json');
        echo json_encode($final_data);
        
        // Register a shutdown function to ensure the progress file doesn't get overwritten
        register_shutdown_function(function() use ($progress_file, $final_data) {
            // Re-write the progress file at shutdown to ensure it has the final state
            file_put_contents($progress_file, json_encode($final_data), LOCK_EX);
        });
        
        // Exit immediately to prevent further processing
        exit();
    } else {
        throw new Exception("Compressed backup file not created or has zero size");
    }

} catch (Exception $e) {
    // Log the error
    $log[] = "Error: " . $e->getMessage();
    
    // Update progress to show error
    update_progress(-1, "Error: " . $e->getMessage());
    
    // Return error response
    $response = array(
        'success' => false,
        'log' => $log,
        'backup_file' => '',
        'backup_size' => 'N/A',
        'timestamp' => date('Y-m-d H:i:s'),
        'error' => $e->getMessage(),
        'progress' => -1,
        'message' => "Error: " . $e->getMessage()
    );
    
    echo json_encode($response);
}

exit;
?>