<?php

require_once __DIR__ . '/common.php';

admin_require_login();

$siteTitle = get_setting('site.title', '六柱博客');
$adminUser = admin_current_user();

$errors = [];
$infos  = [];

$dbPath       = DB_PATH;
$backupDir    = BASE_PATH . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'backups';
$manifestPath = $backupDir . DIRECTORY_SEPARATOR . 'manifest.json';
$lockPath     = $backupDir . DIRECTORY_SEPARATOR . '.backup.lock';




function ensure_dir(string $dir): bool
{
    if (is_dir($dir)) {
        return true;
    }
    return @mkdir($dir, 0777, true);
}




function normalize_backup_filename(string $name): ?string
{
    $name = trim($name);
    if ($name === '') {
        return null;
    }
    if ($name !== basename($name)) {
        return null;
    }
    if (!preg_match('/^[A-Za-z0-9._-]+\.sqlite$/', $name)) {
        return null;
    }
    return $name;
}






function load_backup_manifest(string $manifestPath): array
{
    if (!is_file($manifestPath)) {
        return [];
    }
    $raw = @file_get_contents($manifestPath);
    if (!is_string($raw) || $raw === '') {
        return [];
    }
    $arr = json_decode($raw, true);
    if (!is_array($arr)) {
        return [];
    }
    return $arr;
}






function save_backup_manifest(string $manifestPath, array $manifest): bool
{
    $json = json_encode($manifest, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
    if (!is_string($json)) {
        return false;
    }
    return @file_put_contents($manifestPath, $json, LOCK_EX) !== false;
}




function format_bytes(int $bytes): string
{
    if ($bytes <= 0) {
        return '0 B';
    }
    $units = ['B', 'KB', 'MB', 'GB', 'TB'];
    $i = 0;
    $size = (float)$bytes;
    while ($size >= 1024 && $i < count($units) - 1) {
        $size /= 1024;
        $i++;
    }
    if ($i === 0) {
        return (string)(int)$size . ' ' . $units[$i];
    }
    return number_format($size, 2) . ' ' . $units[$i];
}




function sqlite_clone_database(string $sourcePath, string $targetPath, ?string &$error = null): bool
{
    $error = null;
    if (!is_file($sourcePath)) {
        $error = '源数据库文件不存在。';
        return false;
    }

    
    if (is_file($targetPath)) {
        @unlink($targetPath);
    }
    @unlink($targetPath . '-wal');
    @unlink($targetPath . '-shm');

    if (class_exists('SQLite3') && defined('SQLITE3_OPEN_READONLY')) {
        $src = null;
        $dst = null;
        try {
            $src = new SQLite3($sourcePath, SQLITE3_OPEN_READONLY);
            $dst = new SQLite3($targetPath, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE);
            $src->busyTimeout(5000);
            $dst->busyTimeout(5000);

            $ok = $src->backup($dst);
            $srcErr = trim((string)$src->lastErrorMsg());
            $dstErr = trim((string)$dst->lastErrorMsg());

            $dst->close();
            $src->close();

            if (!$ok) {
                $error = 'SQLite 复制失败。' . ($srcErr !== '' ? (' 源错误：' . $srcErr) : '') . ($dstErr !== '' ? (' 目标错误：' . $dstErr) : '');
                return false;
            }

            return is_file($targetPath) && filesize($targetPath) > 0;
        } catch (Throwable $e) {
            if ($dst instanceof SQLite3) {
                @$dst->close();
            }
            if ($src instanceof SQLite3) {
                @$src->close();
            }
            $error = 'SQLite 复制异常：' . $e->getMessage();
            return false;
        }
    }

    if (!@copy($sourcePath, $targetPath)) {
        $error = '当前环境不支持 SQLite3 backup API，且文件复制失败。';
        return false;
    }
    return is_file($targetPath) && filesize($targetPath) > 0;
}




function sqlite_integrity_ok(string $dbPath, ?string &$error = null): bool
{
    $error = null;
    if (!is_file($dbPath)) {
        $error = '数据库文件不存在。';
        return false;
    }
    if (!class_exists('SQLite3') || !defined('SQLITE3_OPEN_READONLY')) {
        
        return true;
    }

    $db = null;
    try {
        $db = new SQLite3($dbPath, SQLITE3_OPEN_READONLY);
        $db->busyTimeout(5000);
        $result = $db->querySingle('PRAGMA integrity_check', true);
        $db->close();

        $first = '';
        if (is_array($result)) {
            $values = array_values($result);
            $first = isset($values[0]) ? (string)$values[0] : '';
        } elseif (is_string($result)) {
            $first = $result;
        }

        if (strtolower(trim($first)) !== 'ok') {
            $error = '完整性检查未通过：' . ($first !== '' ? $first : 'unknown');
            return false;
        }
        return true;
    } catch (Throwable $e) {
        if ($db instanceof SQLite3) {
            @$db->close();
        }
        $error = '完整性检查异常：' . $e->getMessage();
        return false;
    }
}

if (!ensure_dir($backupDir)) {
    $errors[] = '无法创建备份目录：' . $backupDir;
}

$manifest = load_backup_manifest($manifestPath);

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (!admin_csrf_check($_POST['csrf_token'] ?? null)) {
        $errors[] = '请求已失效，请刷新页面后重试。';
    } else {
        $action = trim((string)($_POST['action'] ?? ''));

        $lockFp = @fopen($lockPath, 'c+');
        if (!$lockFp) {
            $errors[] = '无法创建备份锁文件，操作已取消。';
        } else {
            $locked = function_exists('flock') ? @flock($lockFp, LOCK_EX) : true;
            if (!$locked) {
                $errors[] = '备份系统正忙，请稍后重试。';
            } else {
                if ($action === 'create') {
                    if (!is_file($dbPath)) {
                        $errors[] = '找不到数据库文件：' . $dbPath;
                    } else {
                        $filename = 'blog_db_' . date('Ymd_His') . '.sqlite';
                        $targetPath = $backupDir . DIRECTORY_SEPARATOR . $filename;
                        if (is_file($targetPath)) {
                            $filename = 'blog_db_' . date('Ymd_His') . '_' . bin2hex(random_bytes(2)) . '.sqlite';
                            $targetPath = $backupDir . DIRECTORY_SEPARATOR . $filename;
                        }

                        $err = null;
                        if (!sqlite_clone_database($dbPath, $targetPath, $err)) {
                            $errors[] = '创建备份失败：' . ($err ?: '未知错误');
                        } else {
                            $integrityErr = null;
                            if (!sqlite_integrity_ok($targetPath, $integrityErr)) {
                                $errors[] = '备份已生成但完整性检查失败：' . $integrityErr;
                            }

                            $meta = [
                                'created_at'   => time(),
                                'size'         => (int)@filesize($targetPath),
                                'sha256'       => (string)@hash_file('sha256', $targetPath),
                                'created_by'   => (int)($adminUser['id'] ?? 0),
                                'created_name' => (string)($adminUser['nickname'] ?? 'admin'),
                            ];
                            $manifest[$filename] = $meta;
                            if (!save_backup_manifest($manifestPath, $manifest)) {
                                $errors[] = '备份已生成，但写入元数据失败。';
                            }

                            admin_log('db_backup_create', '创建数据库备份: ' . $filename);
                            if (!$errors) {
                                $infos[] = '备份创建成功：' . $filename . '（' . format_bytes((int)$meta['size']) . '）';
                            }
                        }
                    }
                } elseif ($action === 'delete') {
                    $backupFile = normalize_backup_filename((string)($_POST['backup_file'] ?? ''));
                    if ($backupFile === null) {
                        $errors[] = '备份文件名不合法。';
                    } else {
                        $targetPath = $backupDir . DIRECTORY_SEPARATOR . $backupFile;
                        if (!is_file($targetPath)) {
                            $errors[] = '备份文件不存在：' . $backupFile;
                        } else {
                            if (!@unlink($targetPath)) {
                                $errors[] = '删除备份失败：' . $backupFile;
                            } else {
                                @unlink($targetPath . '-wal');
                                @unlink($targetPath . '-shm');
                                unset($manifest[$backupFile]);
                                if (!save_backup_manifest($manifestPath, $manifest)) {
                                    $errors[] = '备份文件已删除，但更新元数据失败。';
                                }

                                admin_log('db_backup_delete', '删除数据库备份: ' . $backupFile);
                                if (!$errors) {
                                    $infos[] = '备份已删除：' . $backupFile;
                                }
                            }
                        }
                    }
                } elseif ($action === 'restore') {
                    $backupFile = normalize_backup_filename((string)($_POST['backup_file'] ?? ''));
                    if ($backupFile === null) {
                        $errors[] = '备份文件名不合法。';
                    } else {
                        $sourcePath = $backupDir . DIRECTORY_SEPARATOR . $backupFile;
                        if (!is_file($sourcePath)) {
                            $errors[] = '备份文件不存在：' . $backupFile;
                        } else {
                            $expectedHash = '';
                            if (isset($manifest[$backupFile]['sha256']) && is_string($manifest[$backupFile]['sha256'])) {
                                $expectedHash = trim((string)$manifest[$backupFile]['sha256']);
                            }
                            if ($expectedHash !== '') {
                                $actualHash = (string)@hash_file('sha256', $sourcePath);
                                if ($actualHash === '' || !hash_equals($expectedHash, $actualHash)) {
                                    $errors[] = '备份文件哈希不匹配，已拒绝还原。';
                                }
                            }

                            if (!$errors) {
                                $integrityErr = null;
                                if (!sqlite_integrity_ok($sourcePath, $integrityErr)) {
                                    $errors[] = '备份文件完整性检查失败：' . $integrityErr;
                                } else {
                                    $err = null;
                                    if (!sqlite_clone_database($sourcePath, $dbPath, $err)) {
                                        $errors[] = '还原失败：' . ($err ?: '未知错误');
                                    } else {
                                        $afterErr = null;
                                        if (!sqlite_integrity_ok($dbPath, $afterErr)) {
                                            $errors[] = '还原完成，但当前数据库完整性检查失败：' . $afterErr;
                                        } else {
                                            admin_log('db_backup_restore', '还原数据库备份: ' . $backupFile);
                                            $infos[] = '还原成功，当前数据库已切换为：' . $backupFile;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }

                if (function_exists('flock')) {
                    @flock($lockFp, LOCK_UN);
                }
            }
            @fclose($lockFp);
        }
    }
}

$backupItems = [];
if (is_dir($backupDir)) {
    $files = glob($backupDir . DIRECTORY_SEPARATOR . '*.sqlite') ?: [];
    usort($files, static function (string $a, string $b): int {
        $ta = @filemtime($a) ?: 0;
        $tb = @filemtime($b) ?: 0;
        return $tb <=> $ta;
    });

    foreach ($files as $path) {
        if (!is_file($path)) {
            continue;
        }
        $name = basename($path);
        $meta = $manifest[$name] ?? [];
        $backupItems[] = [
            'name'         => $name,
            'path'         => $path,
            'size'         => (int)@filesize($path),
            'file_mtime'   => (int)@filemtime($path),
            'created_at'   => isset($meta['created_at']) ? (int)$meta['created_at'] : (int)@filemtime($path),
            'sha256'       => isset($meta['sha256']) ? (string)$meta['sha256'] : '',
            'created_name' => isset($meta['created_name']) ? (string)$meta['created_name'] : '-',
        ];
    }
}

?><!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>数据库备份 - <?php echo h($siteTitle); ?></title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        html, body { margin:0; padding:0; }
        body {
            margin: 0;
            padding: 0;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
            background: #020617;
            color: #e5e7eb;
        }
        a { color: #60a5fa; text-decoration: none; }
        a:hover { text-decoration: underline; }
        .topbar {
            background: #0f172a;
            border-bottom: 1px solid #1f2937;
            padding: 18px 16px 6px;
            display: flex;
            align-items: flex-start;
            justify-content: space-between;
            margin: 0;
            margin-top: -12px;
        }
        .topbar-title { font-size: 16px; font-weight: 600; }
        .topbar-nav a { margin-left: 10px; font-size: 13px; }
        .page {
            max-width: 980px;
            margin: 0 auto;
            padding: 16px;
        }
        .card {
            background: #020617;
            border-radius: 6px;
            border: 1px solid #1f2937;
            padding: 12px 14px;
            margin-bottom: 12px;
        }
        .card-title {
            font-size: 14px;
            font-weight: 600;
            margin-bottom: 8px;
        }
        .small-text { font-size: 12px; color: #9ca3af; margin-top: 4px; }
        .errors {
            background: #7f1d1d;
            border: 1px solid #fecaca;
            color: #fee2e2;
            padding: 6px 8px;
            border-radius: 4px;
            font-size: 12px;
            margin-bottom: 10px;
        }
        .success {
            background: #14532d;
            border: 1px solid #22c55e;
            color: #bbf7d0;
            padding: 6px 8px;
            border-radius: 4px;
            font-size: 12px;
            margin-bottom: 10px;
        }
        .btn {
            border-radius: 999px;
            border: 1px solid #3b82f6;
            background: #1d4ed8;
            color: #e5e7eb;
            font-size: 13px;
            padding: 5px 10px;
            cursor: pointer;
            text-decoration: none;
        }
        .btn-danger {
            border-color: #fca5a5;
            background: #7f1d1d;
            color: #fee2e2;
        }
        .backup-table-wrap {
            overflow-x: auto;
            border: 1px solid #1f2937;
            border-radius: 6px;
        }
        .backup-table {
            width: 100%;
            border-collapse: collapse;
            min-width: 760px;
        }
        .backup-table th,
        .backup-table td {
            border-bottom: 1px solid #1f2937;
            padding: 8px 10px;
            font-size: 12px;
            text-align: left;
            vertical-align: top;
        }
        .backup-table th {
            color: #93c5fd;
            background: #0b1220;
            font-weight: 600;
        }
        .hash-text {
            max-width: 220px;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            color: #9ca3af;
        }
        .inline-form { display: inline; }
    </style>
</head>
<body>
    <div class="topbar">
        <div class="topbar-title">&#25968;&#25454;&#24211;&#22791;&#20221;</div>
        <div class="topbar-user">
            &#31649;&#29702;&#21592;&#65306;<?php echo h($adminUser['nickname'] ?? $adminUser['email'] ?? ''); ?>
            <span class="topbar-nav">
                <a href="index.php">&#20202;&#34920;&#30424;</a>
                <a href="articles.php">&#25991;&#31456;</a>
                <a href="categories.php">&#20998;&#31867;</a>
                <a href="tags.php">&#26631;&#31614;</a>
                <a href="comments.php">&#35780;&#35770;</a>
                <a href="links.php">&#21451;&#38142;</a>
                <a href="media.php">&#25991;&#20214;</a>
                <a href="users.php">&#20250;&#21592;</a>
                <a href="sensitive_words.php">&#25935;&#24863;&#35789;</a>
                <a href="stats.php">&#27969;&#37327;</a>
                <a href="security.php">&#38450;&#25252;</a>
                <a href="logs.php">&#26085;&#24535;</a>
                <a href="settings.php">&#35774;&#32622;</a>
                <a href="backup_sqlite.php"><strong>&#25968;&#25454;</strong></a>
                <a href="../index.php">&#21069;&#21488;</a>
                <a href="../logout.php">&#36864;&#20986;</a>
            </span>
        </div>
    </div>

    <div class="page">
        <?php if ($errors): ?>
            <div class="errors">
                <?php foreach ($errors as $err): ?>
                    <div><?php echo h($err); ?></div>
                <?php endforeach; ?>
            </div>
        <?php endif; ?>

        <?php if ($infos): ?>
            <div class="success">
                <?php foreach ($infos as $msg): ?>
                    <div><?php echo h($msg); ?></div>
                <?php endforeach; ?>
            </div>
        <?php endif; ?>

        <div class="card">
            <div class="card-title">创建备份（服务器内）</div>
            <div class="small-text">
                备份文件将保存到服务器目录 <code>data/backups</code>，不会自动下载到本地。
            </div>
            <div class="small-text">
                当前数据库：<?php echo h($dbPath); ?>
            </div>
            <form method="post" action="backup_sqlite.php" style="margin-top:8px;">
                <input type="hidden" name="csrf_token" value="<?php echo h(admin_csrf_token()); ?>">
                <input type="hidden" name="action" value="create">
                <button type="submit" class="btn">创建备份</button>
            </form>
        </div>

        <div class="card">
            <div class="card-title">备份列表（服务器内）</div>
            <?php if (!$backupItems): ?>
                <div class="small-text">暂无备份文件，请先创建一份备份。</div>
            <?php else: ?>
                <div class="backup-table-wrap">
                    <table class="backup-table">
                        <thead>
                            <tr>
                                <th>文件名</th>
                                <th>创建时间</th>
                                <th>大小</th>
                                <th>创建人</th>
                                <th>SHA256</th>
                                <th>操作</th>
                            </tr>
                        </thead>
                        <tbody>
                            <?php foreach ($backupItems as $item): ?>
                                <tr>
                                    <td><?php echo h($item['name']); ?></td>
                                    <td><?php echo date('Y-m-d H:i:s', (int)$item['created_at']); ?></td>
                                    <td><?php echo h(format_bytes((int)$item['size'])); ?></td>
                                    <td><?php echo h((string)$item['created_name']); ?></td>
                                    <td><div class="hash-text" title="<?php echo h((string)$item['sha256']); ?>"><?php echo h((string)$item['sha256'] ?: '-'); ?></div></td>
                                    <td>
                                        <form class="inline-form" method="post" action="backup_sqlite.php">
                                            <input type="hidden" name="csrf_token" value="<?php echo h(admin_csrf_token()); ?>">
                                            <input type="hidden" name="action" value="delete">
                                            <input type="hidden" name="backup_file" value="<?php echo h($item['name']); ?>">
                                            <button type="submit" class="btn btn-danger">删除</button>
                                        </form>
                                        <form class="inline-form" method="post" action="backup_sqlite.php" onsubmit="return confirm('确认要还原该备份吗？当前数据库将被覆盖。');">
                                            <input type="hidden" name="csrf_token" value="<?php echo h(admin_csrf_token()); ?>">
                                            <input type="hidden" name="action" value="restore">
                                            <input type="hidden" name="backup_file" value="<?php echo h($item['name']); ?>">
                                            <button type="submit" class="btn btn-danger">还原</button>
                                        </form>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                        </tbody>
                    </table>
                </div>
            <?php endif; ?>
        </div>
    </div>
</body>
</html>
