<?php
/**
 * sync_to_backup.php - Creates DB dump + source files ZIP and POSTs to backup server.
 * Run via browser: https://yoursite.com/sync_to_backup.php
 * Or via CLI: php sync_to_backup.php
 * For scheduled runs (cron/Task Scheduler): use CLI or ?cron=1 for plain-text output and logging.
 *
 * RECEIVER (restore_from_sync.php) must:
 * - Accept access_token via GET (URL): restore_from_sync.php?access_token=SECRET
 * - Accept POST with multipart/form-data and one file field: backup_file (ZIP)
 * - Expect ZIP containing: database.sql at root + source files with relative paths
 * - Use same $secret_key as here for authentication
 */
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
error_reporting(E_ALL);
set_time_limit(300);
ini_set('memory_limit', '256M');

function syncExitWithError(string $message, int $status = 400, bool $asJson = false): void {
    http_response_code($status);
    if ($asJson) {
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode(['success' => false, 'error' => $message], JSON_UNESCAPED_SLASHES);
    } else {
        echo "❌ " . htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
    }
    exit;
}

$is_cli = (php_sapi_name() === 'cli');
$ajax_mode = isset($_GET['ajax']);
// Cron/scheduled mode: CLI or ?cron=1 (plain-text output, log to backups/sync_log.txt)
$cron_mode = ($is_cli || isset($_GET['cron']));

// Load config once (if available)
$config = [];
if (file_exists(__DIR__ . '/config.php')) {
    $loadedConfig = require __DIR__ . '/config.php';
    if (is_array($loadedConfig)) {
        $config = $loadedConfig;
    }
}

// Security gate for web access:
// - normal web run requires authenticated session
// - ajax run additionally requires CSRF header + POST
// - web cron run requires cron key
if (!$is_cli) {
    require_once __DIR__ . '/db.php'; // ensures session is available

    if (isset($_GET['cron'])) {
        $configuredCronKey = $config['backup_sync']['cron_key'] ?? '';
        if (!$configuredCronKey) {
            $configuredCronKey = getenv('BACKUP_SYNC_CRON_KEY') ?: '';
        }
        $providedCronKey = $_GET['cron_key'] ?? '';
        if (!$configuredCronKey || !hash_equals((string)$configuredCronKey, (string)$providedCronKey)) {
            syncExitWithError('Invalid cron key.', 403, $ajax_mode);
        }
    } else {
        if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
            syncExitWithError('Authentication required.', 401, $ajax_mode);
        }
        if ($ajax_mode) {
            if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
                syncExitWithError('Method not allowed.', 405, true);
            }
            $csrfHeader = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
            $sessionCsrf = $_SESSION['csrf_token'] ?? '';
            if (!$csrfHeader || !$sessionCsrf || !hash_equals($sessionCsrf, $csrfHeader)) {
                syncExitWithError('Invalid CSRF token.', 403, true);
            }
        }
    }
}

if ($cron_mode) {
    if (!$is_cli) {
        header('Content-Type: text/plain; charset=utf-8');
    }
    ob_start();
}

// --- 1. CONFIGURATION ---
// Read precedence: ENV > config.php. No hardcoded fallbacks.
$secret_key      = (string) (getenv('FONFIX_BACKUP_SECRET') ?: ($config['backup_sync']['secret_key'] ?? ''));
$base_target_url = (string) (getenv('FONFIX_BACKUP_URL')    ?: ($config['backup_sync']['target_url']  ?? 'https://systembackup.spartanmnc.com/restore_from_sync.php'));

$zip_filename = 'backup.zip';
$sql_filename = 'database.sql';

$db_host = (string) (getenv('FONFIX_DB_HOST') ?: ($config['database']['host']     ?? 'localhost'));
$db_name = (string) (getenv('FONFIX_DB_NAME') ?: ($config['database']['dbname']   ?? ''));
$db_user = (string) (getenv('FONFIX_DB_USER') ?: ($config['database']['username'] ?? ''));
$db_pass = (string) (getenv('FONFIX_DB_PASS') ?: ($config['database']['password'] ?? ''));

if ($db_name === '' || $db_user === '' || $db_pass === '' || $secret_key === '') {
    syncExitWithError('Backup sync configuration incomplete (DB credentials or secret_key missing).', 500, $ajax_mode);
}

// Allow overrides for scheduler usage (CLI only):
// - CLI env vars: BACKUP_SYNC_URL / BACKUP_SYNC_TOKEN
$env_url = getenv('BACKUP_SYNC_URL');
$env_token = getenv('BACKUP_SYNC_TOKEN');
if ($is_cli && is_string($env_url) && $env_url !== '') {
    $base_target_url = $env_url;
}
if ($is_cli && is_string($env_token) && $env_token !== '') {
    $secret_key = $env_token;
}

$parsedTarget = parse_url($base_target_url);
if (!$parsedTarget || (($parsedTarget['scheme'] ?? '') !== 'https')) {
    syncExitWithError('Backup target must use HTTPS.', 400, $ajax_mode);
}

$target_url = rtrim($base_target_url, '?&') . (str_contains($base_target_url, '?') ? '&' : '?') . 'access_token=' . rawurlencode($secret_key);

// --- 2. DATABASE DUMP FUNCTION ---
function backupDatabaseTables($host, $user, $pass, $name, $outFile) {
    $mysqli = new mysqli($host, $user, $pass, $name);
    if ($mysqli->connect_errno) { die("❌ DB Connection Failed: " . $mysqli->connect_error); }
    $mysqli->set_charset("utf8mb4");

    $f = fopen($outFile, 'w');
    if (!$f) die("❌ Could not create SQL file.");

    fwrite($f, "-- Database Backup: $name\n-- Date: " . date('Y-m-d H:i:s') . "\n\n");
    fwrite($f, "SET FOREIGN_KEY_CHECKS=0;\nSET SQL_MODE = \"NO_AUTO_VALUE_ON_ZERO\";\n\n");

    $tables = [];
    $result = $mysqli->query("SHOW TABLES");
    while ($row = $result->fetch_row()) { $tables[] = $row[0]; }

    foreach ($tables as $table) {
        $tableEsc = '`' . str_replace('`', '``', $table) . '`';
        $createResult = $mysqli->query("SHOW CREATE TABLE $tableEsc");
        if (!$createResult) { continue; }
        $row2 = $createResult->fetch_row();
        fwrite($f, "\n\n-- Structure for `$table`\n");
        fwrite($f, "DROP TABLE IF EXISTS `$table`;\n");
        fwrite($f, $row2[1] . ";\n\n");

        $result = $mysqli->query("SELECT * FROM $tableEsc");
        if (!$result) { continue; }
        $num_fields = $result->field_count;

        while ($row = $result->fetch_row()) {
            fwrite($f, "INSERT INTO `$table` VALUES(");
            for ($j = 0; $j < $num_fields; $j++) {
                $value = $row[$j];
                if ($value === null) {
                    fwrite($f, 'NULL');
                } else {
                    $value = $mysqli->real_escape_string($value);
                    $value = str_replace(["\r", "\n"], ["\\r", "\\n"], $value);
                    fwrite($f, '"' . $value . '"');
                }
                if ($j < ($num_fields - 1)) { fwrite($f, ','); }
            }
            fwrite($f, ");\n");
        }
    }
    fwrite($f, "\nSET FOREIGN_KEY_CHECKS=1;\n");
    fclose($f);
    $mysqli->close();
}

// --- 3. EXECUTE BACKUP ---
echo "<h3>1. Exporting Database...</h3>";
backupDatabaseTables($db_host, $db_user, $db_pass, $db_name, $sql_filename);
echo "✅ Database exported.<br>";

// --- 4. CREATE ZIP WITH ALL SOURCE FILES ---
echo "<h3>2. Creating Zip Archive with Source Files...</h3>";
$zip = new ZipArchive();
if ($zip->open($zip_filename, ZipArchive::CREATE | ZipArchive::OVERWRITE) === TRUE) {
    // Add SQL dump
    $zip->addFile($sql_filename, 'database.sql');
    echo "✅ Added database.sql<br>";
    
    // Add all source files (like backup.php does)
    $rootPath = realpath(__DIR__);
    $files = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($rootPath, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::LEAVES_ONLY
    );
    
    $fileCount = 0;
    foreach ($files as $name => $file) {
        if (!$file->isDir()) {
            $filePath = $file->getRealPath();
            if (!$filePath) continue;
            
            $relativePath = substr($filePath, strlen($rootPath) + 1);
            
            // EXCLUSIONS - Don't include:
            // - The current zip being created (avoid recursion)
            // - Other backup zip files (to save space)
            // - Error logs
            // - Git/VCS files
            // - Archives-Executed folder (old migration scripts - not needed on backup server)
            // - Temporary/debug files
            // - Database config files (backup server uses different credentials)
            if ($relativePath === $zip_filename) continue; // Don't include the zip we're currently creating
            if (strpos($relativePath, '.zip') !== false) continue; // Skip other zip files
            if (strpos($filePath, 'error_log') !== false) continue;
            if (strpos($relativePath, '.git') === 0) continue; // Skip .git directory
            if (strpos($relativePath, '.svn') === 0) continue; // Skip .svn directory
            if (strpos($relativePath, 'Archives-Executed') === 0) continue;
            if (strpos($relativePath, 'debug_log.txt') !== false) continue;
            // Exclude database config files (backup server keeps its own config.php/db.php)
            if ($relativePath === 'config.php') continue;
            if ($relativePath === 'db.php') continue;
            
            // Add file to ZIP
            if (file_exists($filePath)) {
                $zip->addFile($filePath, $relativePath);
                $fileCount++;
            }
        }
    }
    
    $zip->close();
    echo "✅ Zip file created with $fileCount source files.<br>";
    unlink($sql_filename); // Clean up temp SQL file
} else {
    die("❌ Failed to create ZIP file.");
}

// --- 5. SEND VIA CURL ---
echo "<h3>3. Sending to Backup Server...</h3>";

$curl = curl_init();
$cfile = new CURLFile(realpath($zip_filename));

// CHANGE 2: Send only the file in the body (Token is now in URL)
$post_data = array('backup_file' => $cfile);

curl_setopt($curl, CURLOPT_URL, $target_url);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 20);
curl_setopt($curl, CURLOPT_TIMEOUT, 300);
curl_setopt($curl, CURLOPT_USERAGENT, 'Fon2FixBackupSync/1.1');
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Expect:']);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);

$response = curl_exec($curl);
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$curl_error = curl_error($curl);
$curl_errno = curl_errno($curl);

curl_close($curl);

if ($ajax_mode) {
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode([
        'success' => ($http_code == 200),
        'http_code' => $http_code,
        'curl_errno' => $curl_errno,
        'curl_error' => $curl_error,
        'server_response' => $response
    ], JSON_UNESCAPED_SLASHES);
    exit;
}

// Display results
echo '<div style="margin-top:20px; padding:15px; border:2px solid ' . ($http_code == 200 ? '#2ecc71' : '#e74c3c') . '; border-radius:8px; background:' . ($http_code == 200 ? '#d4edda' : '#f8d7da') . ';">';
echo '<h3 style="margin-top:0; color:' . ($http_code == 200 ? '#155724' : '#721c24') . ';">';
echo ($http_code == 200 ? '✅ SUCCESS' : '❌ ERROR') . ' - HTTP Status: ' . $http_code;
echo '</h3>';

if ($curl_errno) {
    echo '<p style="color:#721c24;"><b>cURL Error #' . $curl_errno . ':</b> ' . htmlspecialchars($curl_error) . '</p>';
}

if ($http_code == 200) {
    echo '<p style="color:#155724;"><b>✅ Backup uploaded successfully!</b></p>';
    if ($response) {
        echo '<div style="background:#fff; padding:10px; border:1px solid #c3e6cb; border-radius:4px; margin-top:10px;">';
        echo '<b>Server Response:</b><br><pre style="white-space:pre-wrap; word-wrap:break-word;">' . htmlspecialchars($response) . '</pre>';
        echo '</div>';
    }
} else {
    echo '<p style="color:#721c24;"><b>❌ Upload failed!</b></p>';
    echo '<p><b>Possible causes:</b></p>';
    echo '<ul style="color:#721c24;">';
    if ($http_code == 403) {
        echo '<li>Backup server rejected request (403). Token mismatch or WAF rule likely.</li>';
        echo '<li>Verify <code>access_token</code> value on BOTH this sender and <code>restore_from_sync.php</code>.</li>';
    } elseif ($http_code == 404) {
        echo '<li>The backup server endpoint <code>restore_from_sync.php</code> was not found (404)</li>';
        echo '<li>Check if the file exists on the backup server</li>';
        echo '<li>Verify the URL path is correct</li>';
    } elseif ($http_code == 401 || $http_code == 403) {
        echo '<li>Authentication failed - check the access token</li>';
    } elseif ($http_code == 500) {
        echo '<li>Server error on backup server - check server logs</li>';
    } else {
        echo '<li>Unexpected HTTP status code: ' . $http_code . '</li>';
    }
    echo '</ul>';
    
    if ($response) {
        echo '<div style="background:#fff; padding:10px; border:1px solid #f5c6cb; border-radius:4px; margin-top:10px;">';
        echo '<b>Server Response:</b><br><pre style="white-space:pre-wrap; word-wrap:break-word; color:#721c24;">' . htmlspecialchars($response) . '</pre>';
        echo '</div>';
    }
    
    echo '<p style="margin-top:15px;"><b>Note:</b> The backup ZIP file was created successfully and is still available locally.</p>';
}

echo '</div>';

// --- 6. CLEANUP (Optional - comment out if you want to keep the zip locally) ---
// Uncomment the line below to delete the zip file after successful upload
// if ($http_code == 200) { unlink($zip_filename); }

// --- 7. CRON MODE: plain-text output and log ---
if ($cron_mode) {
    $html = ob_get_clean();
    $text = strip_tags(str_replace(['<br>', '<br/>', '<br />'], "\n", $html));
    $text = trim(preg_replace('/\n\s*\n+/', "\n", $text));
    $line = "[" . date('Y-m-d H:i:s') . "] HTTP $http_code - " . ($http_code == 200 ? 'OK' : 'FAIL') . "\n" . $text . "\n";
    echo $line;
    $log_dir = __DIR__ . '/backups';
    if (!is_dir($log_dir)) {
        @mkdir($log_dir, 0755, true);
    }
    @file_put_contents($log_dir . '/sync_log.txt', $line . "\n", FILE_APPEND);
    $status_payload = [
        'time' => date('c'),
        'http_code' => $http_code,
        'ok' => ($http_code == 200),
        'curl_errno' => $curl_errno,
        'curl_error' => $curl_error,
        'target' => $base_target_url
    ];
    @file_put_contents($log_dir . '/sync_last_status.json', json_encode($status_payload, JSON_PRETTY_PRINT));
}
?>