<?php
/**
 * restore_from_sync.php
 * Receives backup ZIP from sync_to_backup.php and restores files + database.
 *
 * Security goals:
 * - Keep local destination credentials/config intact (never overwrite config.php/db.php/.env)
 * - Validate token with hash_equals
 * - Validate upload metadata
 * - Prevent Zip Slip/path traversal
 * - Avoid leaking internals in production output
 */

// ---- Runtime ----
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
ini_set('log_errors', 1);
error_reporting(E_ALL);
set_time_limit(600);
ini_set('memory_limit', '1024M');
ignore_user_abort(true);

// ---- Configuration (prefer environment variables) ----
$secret_key = getenv('BACKUP_SYNC_TOKEN') ?: 'KDYOnuNhBNJO5SBew3QRFCK75m9bQd3wCXik8S81CfPtXoacLU';

$upload_folder = 'backups';
$backup_filename = 'backup.zip';
$sql_filename = 'database.sql';

// Telegram (optional)
$tg_bot_token = getenv('TELEGRAM_BOT_TOKEN') ?: '';
$tg_chat_id = getenv('TELEGRAM_CHAT_ID') ?: '';

// DESTINATION DATABASE CREDENTIALS (SPARTAN SERVER)
$dest_db_host = 'localhost';
$dest_db_user = 'spartanm_systemdb';   // UPDATE THIS
$dest_db_pass = '.CA}RN-yF8yUd.WK';   // UPDATE THIS
$dest_db_name = 'spartanm_systemdb';   // UPDATE THIS

// Optional: set APP_DEBUG=1 in environment if you need verbose HTML diagnostics.
$debug_mode = (getenv('APP_DEBUG') === '1');

function out($msg): void {
    echo $msg . "<br>";
}

function fail_and_exit(string $msg, int $status = 400): void {
    http_response_code($status);
    out("❌ " . htmlspecialchars($msg, ENT_QUOTES, 'UTF-8'));
    exit;
}

function normalize_zip_entry(string $name): string {
    return ltrim(str_replace('\\', '/', $name), '/');
}

function is_safe_zip_entry(string $entry): bool {
    if ($entry === '' || str_contains($entry, "\0")) return false;
    if (str_starts_with($entry, '/')) return false;
    if (preg_match('#(^|/)\.\.(/|$)#', $entry)) return false;
    return true;
}

function sendTelegram(string $msg, string $token, string $chat_id): void {
    if ($token === '' || $chat_id === '') return;

    $url = "https://api.telegram.org/bot{$token}/sendMessage";
    $post_fields = [
        'chat_id' => $chat_id,
        'text' => $msg,
        'parse_mode' => 'HTML'
    ];

    if (function_exists('curl_init')) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
        curl_setopt($ch, CURLOPT_TIMEOUT, 8);
        $result = curl_exec($ch);
        if ($result === false) {
            error_log('Telegram cURL error: ' . curl_error($ch));
        }
        curl_close($ch);
        return;
    }

    if (ini_get('allow_url_fopen')) {
        $context = stream_context_create([
            'http' => [
                'method' => 'POST',
                'header' => 'Content-Type: application/x-www-form-urlencoded',
                'content' => http_build_query($post_fields),
                'timeout' => 8
            ]
        ]);
        @file_get_contents($url, false, $context);
    }
}

function importDatabasePHP(string $host, string $user, string $pass, string $name, string $sql_file) {
    if ($user === '' || $name === '') {
        return '❌ Destination DB credentials are not configured.';
    }

    $mysqli = new mysqli($host, $user, $pass, $name);
    if ($mysqli->connect_errno) {
        return '❌ DB connect error: ' . $mysqli->connect_error;
    }
    $mysqli->set_charset('utf8mb4');

    if (!$mysqli->query("SET FOREIGN_KEY_CHECKS=0")) {
        $mysqli->close();
        return '❌ Failed to disable FK checks: ' . $mysqli->error;
    }
    if (!$mysqli->query("SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'")) {
        $mysqli->close();
        return '❌ Failed to set SQL mode: ' . $mysqli->error;
    }

    $handle = fopen($sql_file, 'r');
    if (!$handle) {
        $mysqli->close();
        return '❌ Could not open SQL file.';
    }

    $query = '';
    $ok_count = 0;
    $err_count = 0;
    $first_error = '';

    while (($line = fgets($handle)) !== false) {
        $trimmed = trim($line);
        if ($trimmed === '' || str_starts_with($trimmed, '--') || str_starts_with($trimmed, '#')) {
            continue;
        }
        $query .= $line;
        if (str_ends_with($trimmed, ';')) {
            if (!$mysqli->query($query)) {
                $err_count++;
                if ($first_error === '') $first_error = $mysqli->error;
            } else {
                $ok_count++;
            }
            $query = '';
        }
    }
    fclose($handle);

    $mysqli->query("SET FOREIGN_KEY_CHECKS=1");
    $mysqli->close();

    if ($err_count > 0) {
        return "❌ Import completed with {$err_count} error(s), {$ok_count} successful. First error: {$first_error}";
    }
    return true;
}

// ---- Auth token check ----
$incoming_token = (string)($_GET['access_token'] ?? $_POST['access_token'] ?? '');
if ($incoming_token === '' || $secret_key === '' || !hash_equals(trim($secret_key), trim($incoming_token))) {
    if ($debug_mode) {
        out("⛔ Access denied (token mismatch).");
        out("Method: " . htmlspecialchars($_SERVER['REQUEST_METHOD'] ?? 'unknown'));
        out("Query: " . htmlspecialchars($_SERVER['QUERY_STRING'] ?? ''));
    }
    fail_and_exit('Access denied.', 403);
}

if (($_SERVER['REQUEST_METHOD'] ?? 'GET') !== 'POST') {
    if ($debug_mode) {
        out("ℹ️ Debug: method=" . htmlspecialchars($_SERVER['REQUEST_METHOD'] ?? 'unknown', ENT_QUOTES, 'UTF-8'));
        out("ℹ️ Debug: query=" . htmlspecialchars($_SERVER['QUERY_STRING'] ?? '', ENT_QUOTES, 'UTF-8'));
        out("ℹ️ Debug: content_length=" . htmlspecialchars((string)($_SERVER['CONTENT_LENGTH'] ?? '0'), ENT_QUOTES, 'UTF-8'));
    }
    fail_and_exit('Use POST with file field backup_file.', 405);
}

// ---- Upload checks ----
if (!isset($_FILES['backup_file'])) {
    if ($debug_mode) {
        out("ℹ️ Debug: backup_file missing in \$_FILES.");
        out("ℹ️ Debug: files_count=" . count($_FILES));
        out("ℹ️ Debug: post_count=" . count($_POST));
        out("ℹ️ Debug: upload_max_filesize=" . htmlspecialchars(ini_get('upload_max_filesize') ?: '', ENT_QUOTES, 'UTF-8'));
        out("ℹ️ Debug: post_max_size=" . htmlspecialchars(ini_get('post_max_size') ?: '', ENT_QUOTES, 'UTF-8'));
        out("ℹ️ Debug: max_file_uploads=" . htmlspecialchars(ini_get('max_file_uploads') ?: '', ENT_QUOTES, 'UTF-8'));
        out("ℹ️ Debug: content_length=" . htmlspecialchars((string)($_SERVER['CONTENT_LENGTH'] ?? '0'), ENT_QUOTES, 'UTF-8'));
    }
    fail_and_exit('No backup_file received.');
}

$upload = $_FILES['backup_file'];
if (!isset($upload['error']) || (int)$upload['error'] !== UPLOAD_ERR_OK) {
    fail_and_exit('Upload failed with code: ' . (int)($upload['error'] ?? -1));
}
if (empty($upload['tmp_name']) || !is_uploaded_file($upload['tmp_name'])) {
    fail_and_exit('Invalid uploaded file.');
}
if (($upload['size'] ?? 0) <= 0) {
    fail_and_exit('Uploaded file is empty.');
}

$ext = strtolower(pathinfo((string)($upload['name'] ?? ''), PATHINFO_EXTENSION));
if ($ext !== '' && $ext !== 'zip') {
    fail_and_exit('Only ZIP uploads are allowed.');
}

// ---- Prepare paths ----
$base_dir = __DIR__;
$upload_dir = $base_dir . DIRECTORY_SEPARATOR . $upload_folder;
if (!is_dir($upload_dir) && !mkdir($upload_dir, 0755, true)) {
    fail_and_exit('Failed to create upload directory.');
}
$zip_path = $upload_dir . DIRECTORY_SEPARATOR . $backup_filename;
if (!move_uploaded_file($upload['tmp_name'], $zip_path)) {
    fail_and_exit('Could not save uploaded ZIP.');
}
@chmod($zip_path, 0644);
out("✅ Upload received. Starting restore...");

// ---- Extract with protection ----
$zip = new ZipArchive();
if ($zip->open($zip_path) !== true) {
    fail_and_exit('ZIP cannot be opened (corrupted or unsupported).');
}

$protected_exact = [
    'restore_from_sync.php',
    'config.php',
    'db.php',
    '.env'
];

$extracted_count = 0;
$skipped_count = 0;

for ($i = 0; $i < $zip->numFiles; $i++) {
    $raw_name = $zip->getNameIndex($i);
    if ($raw_name === false) continue;

    $entry = normalize_zip_entry($raw_name);
    if ($entry === '' || str_ends_with($entry, '/')) continue; // dir
    if (!is_safe_zip_entry($entry)) {
        $skipped_count++;
        continue;
    }

    $entry_lower = strtolower($entry);
    $skip = false;
    foreach ($protected_exact as $p) {
        if ($entry_lower === strtolower($p)) {
            $skip = true;
            break;
        }
    }
    if (str_starts_with($entry_lower, 'backups/')) {
        $skip = true;
    }
    if ($skip) {
        $skipped_count++;
        out("⚠️ Skipped protected file: " . htmlspecialchars($entry, ENT_QUOTES, 'UTF-8'));
        continue;
    }

    $dest = $base_dir . DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $entry);
    $dest_dir = dirname($dest);
    if (!is_dir($dest_dir) && !mkdir($dest_dir, 0755, true)) {
        $skipped_count++;
        continue;
    }

    if ($zip->extractTo($base_dir, [$raw_name])) {
        $extracted_count++;
        if (is_file($dest)) @chmod($dest, 0644);
    } else {
        $skipped_count++;
    }
}
$zip->close();
out("✅ Extracted {$extracted_count} files ({$skipped_count} skipped/protected).");

// ---- Database import (optional if database.sql exists) ----
$sql_path = $base_dir . DIRECTORY_SEPARATOR . $sql_filename;
$db_status = 'Skipped';
$db_details = '';

if (is_file($sql_path)) {
    out("<h3>Importing database...</h3>");
    $import_result = importDatabasePHP($dest_db_host, $dest_db_user, $dest_db_pass, $dest_db_name, $sql_path);
    if ($import_result === true) {
        $db_status = '✅ Success';
        out("✅ Database imported successfully.");
        @unlink($sql_path);
    } else {
        $db_status = '❌ Failed';
        $db_details = (string)$import_result;
        out(htmlspecialchars($db_details, ENT_QUOTES, 'UTF-8'));
    }
} else {
    out("⚠️ database.sql not found in extracted payload (files restored only).");
}

// ---- Final ----
$timestamp = date('Y-m-d H:i:s');
$msg = "✅ <b>Restore Complete</b>\n"
    . "📁 Files: {$extracted_count} extracted ({$skipped_count} skipped)\n"
    . "🗄 DB: {$db_status}\n"
    . "🕒 {$timestamp}";
if ($db_details !== '') {
    $msg .= "\n⚠️ " . strip_tags($db_details);
}
sendTelegram($msg, $tg_bot_token, $tg_chat_id);

echo "<hr>";
echo "<h2>✅ PROCESS COMPLETE</h2>";
echo "<p><b>Files Extracted:</b> " . (int)$extracted_count . "</p>";
echo "<p><b>Files Skipped/Protected:</b> " . (int)$skipped_count . "</p>";
echo "<p><b>Database Status:</b> " . htmlspecialchars($db_status, ENT_QUOTES, 'UTF-8') . "</p>";
echo "<p><b>Time:</b> " . htmlspecialchars($timestamp, ENT_QUOTES, 'UTF-8') . "</p>";

