<#
.SYNOPSIS
    灾备演练脚本(Windows PowerShell 版本)
.DESCRIPTION
    自动化灾备演练:从备份恢复到测试数据库,验证数据完整性
.EXAMPLE
    .\dr-drill.ps1
.EXAMPLE
    .\dr-drill.ps1 -BackupFile "backups\db_backup_20260617_020000.sql.gz" -TestDb "next_edu_dr_drill"
.EXAMPLE
    .\dr-drill.ps1 -NoCleanup
.PARAMETER BackupFile
    指定备份文件(不指定则使用最新备份)
.PARAMETER TestDb
    测试数据库名(默认 next_edu_dr_drill)
.PARAMETER NoCleanup
    演练后不清理测试数据库
.PARAMETER ReportDir
    报告输出目录(默认 docs\dr\reports)
.PARAMETER Help
    显示帮助信息
#>

[CmdletBinding()]
param(
    [Parameter(Position = 0)]
    [string]$BackupFile = "",

    [Parameter()]
    [string]$TestDb = "",

    [Parameter()]
    [switch]$NoCleanup,

    [Parameter()]
    [string]$ReportDir = "",

    [Parameter()]
    [switch]$Help
)

# 显示帮助
if ($Help) {
    Get-Help $MyInvocation.MyCommand.Path -Detailed
    exit 0
}

# 配置
$ErrorActionPreference = "Stop"

$DatabaseUrl = $env:DATABASE_URL
if ([string]::IsNullOrEmpty($DatabaseUrl)) {
    Write-Host "ERROR: DATABASE_URL not set" -ForegroundColor Red
    exit 1
}

$BackupDir = if ($env:BACKUP_DIR) { $env:BACKUP_DIR } else { ".\backups" }
if ([string]::IsNullOrEmpty($TestDb)) {
    $TestDb = if ($env:DR_DRILL_TEST_DB) { $env:DR_DRILL_TEST_DB } else { "next_edu_dr_drill" }
}
if ([string]::IsNullOrEmpty($ReportDir)) {
    $ReportDir = if ($env:DR_DRILL_REPORT_DIR) { $env:DR_DRILL_REPORT_DIR } else { "docs\dr\reports" }
}

$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$ReportFile = Join-Path $ReportDir "dr_drill_${Timestamp}.md"

# 解析 DATABASE_URL
# 格式: mysql://user:password@host:port/dbname
function Parse-DatabaseUrl {
    param([string]$Url)

    try {
        $uri = [System.Uri]$Url
        $userInfo = $uri.UserInfo -split ':', 2
        return @{
            User   = $userInfo[0]
            Pass   = if ($userInfo.Length -gt 1) { $userInfo[1] } else { "" }
            Host   = $uri.Host
            Port   = if ($uri.Port -gt 0) { $uri.Port } else { 3306 }
            DbName = $uri.AbsolutePath.TrimStart('/')
        }
    }
    catch {
        Write-Host "ERROR: Invalid DATABASE_URL format" -ForegroundColor Red
        exit 1
    }
}

$db = Parse-DatabaseUrl $DatabaseUrl

# 创建报告目录
if (-not (Test-Path $ReportDir)) {
    New-Item -ItemType Directory -Path $ReportDir -Force | Out-Null
}

# 初始化报告
function Init-Report {
    $content = @"
# 灾备演练报告

- **演练时间**: $(Get-Date -Format "yyyy-MM-ddTHH:mm:ssZ")
- **测试数据库**: $TestDb
- **源数据库**: $($db.DbName)
- **数据库主机**: $($db.Host):$($db.Port)
- **备份文件**: $BackupFile

## 演练步骤

"@
    Set-Content -Path $ReportFile -Value $content -Encoding UTF8
}

function Append-Report {
    param([string]$Content)
    Add-Content -Path $ReportFile -Value $Content -Encoding UTF8
}

function Step-Result {
    param(
        [string]$Step,
        [string]$Status,
        [string]$Detail
    )
    Append-Report "### 步骤 $Step`: $Status"
    Append-Report ""
    Append-Report $Detail
    Append-Report ""
    if ($Status -eq "FAILED") {
        Append-Report "❌ 步骤失败"
    }
    else {
        Append-Report "✅ 步骤成功"
    }
    Append-Report ""
    Write-Host "---"
}

# MySQL 执行函数
function Invoke-MySql {
    param(
        [string]$Query,
        [string]$Database = "",
        [switch]$Silent,
        [switch]$Scalar
    )

    $mysqlArgs = @("-h", $db.Host, "-P", $db.Port, "-u", $db.User, "-p$($db.Pass)")
    if (-not [string]::IsNullOrEmpty($Database)) {
        $mysqlArgs += $Database
    }
    $mysqlArgs += @("-e", $Query)

    if ($Scalar) {
        $mysqlArgs += @("-s", "-N")
    }

    if ($Silent) {
        $result = & mysql @mysqlArgs 2>$null
    }
    else {
        $result = & mysql @mysqlArgs 2>&1
    }
    return $result
}

# 检查 mysql 命令
if (-not (Get-Command mysql -ErrorAction SilentlyContinue)) {
    Write-Host "ERROR: mysql client not found in PATH" -ForegroundColor Red
    Write-Host "Please install MySQL client tools" -ForegroundColor Red
    exit 1
}

Write-Host "=== Disaster Recovery Drill ===" -ForegroundColor Cyan
Write-Host "Time:       $(Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')"
Write-Host "Test DB:    $TestDb"
Write-Host "Source DB:  $($db.DbName)@$($db.Host):$($db.Port)"
Write-Host "Report:     $ReportFile"
Write-Host ""

Init-Report
$drillStart = Get-Date
$overallStatus = "SUCCESS"

# 步骤 1: 查找备份文件
Write-Host "[1/6] Locating backup file..."
if ([string]::IsNullOrEmpty($BackupFile)) {
    $backupPattern = Join-Path $BackupDir "db_backup_*.sql.gz"
    $latestBackup = Get-ChildItem -Path $backupPattern -ErrorAction SilentlyContinue |
        Sort-Object LastWriteTime -Descending |
        Select-Object -First 1
    if ($latestBackup) {
        $BackupFile = $latestBackup.FullName
    }
    else {
        Write-Host "  FAIL: No backup file found in $BackupDir" -ForegroundColor Red
        Step-Result "1 - 定位备份文件" "FAILED" "未找到备份文件于 $BackupDir"
        Append-Report "## 演练结果: ❌ FAILED`n`n演练失败,未找到备份文件"
        exit 1
    }
}

if (-not (Test-Path $BackupFile)) {
    Write-Host "  FAIL: Backup file not found: $BackupFile" -ForegroundColor Red
    Step-Result "1 - 定位备份文件" "FAILED" "备份文件不存在: $BackupFile"
    exit 1
}

$backupSize = (Get-Item $BackupFile).Length
Write-Host "  PASS: Found backup: $BackupFile ($backupSize bytes)" -ForegroundColor Green
Step-Result "1 - 定位备份文件" "PASSED" "备份文件: ``$BackupFile`` ($backupSize bytes)"

# 步骤 2: 创建测试数据库
Write-Host "[2/6] Creating test database..."
try {
    Invoke-MySql -Query "DROP DATABASE IF EXISTS ``$TestDb``;" -Silent
    Invoke-MySql -Query "CREATE DATABASE ``$TestDb`` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" -Silent
    Write-Host "  PASS: Test database created: $TestDb" -ForegroundColor Green
    Step-Result "2 - 创建测试数据库" "PASSED" "测试数据库 ``$TestDb`` 创建成功"
}
catch {
    Write-Host "  FAIL: Could not create test database" -ForegroundColor Red
    Step-Result "2 - 创建测试数据库" "FAILED" "创建测试数据库 ``$TestDb`` 失败"
    $overallStatus = "FAILED"
    Append-Report "## 演练结果: ❌ FAILED"
    exit 1
}

# 步骤 3: 从备份恢复到测试数据库
Write-Host "[3/6] Restoring backup to test database..."
$restoreStart = Get-Date
try {
    # 使用 7z 或 gunzip 解压,然后管道到 mysql
    # Windows 上可能需要使用 7z 或 .NET GZipStream
    $tempSqlFile = [System.IO.Path]::GetTempFileName()

    # 尝试使用 gunzip(如果可用)
    if (Get-Command gunzip -ErrorAction SilentlyContinue) {
        $process = Start-Process -FilePath "gunzip" -ArgumentList "-c", "`"$BackupFile`"" -NoNewWindow -RedirectStandardOutput $tempSqlFile -Wait -PassThru
    }
    # 尝试使用 7z
    elseif (Get-Command 7z -ErrorAction SilentlyContinue) {
        & 7z e -so "$BackupFile" | Out-File -FilePath $tempSqlFile -Encoding ASCII
    }
    # 使用 .NET GZipStream
    else {
        $inStream = [System.IO.File]::OpenRead($BackupFile)
        $gzStream = New-Object System.IO.Compression.GZipStream($inStream, [System.IO.Compression.CompressionMode]::Decompress)
        $reader = New-Object System.IO.StreamReader($gzStream, [System.Text.Encoding]::UTF8)
        $content = $reader.ReadToEnd()
        $reader.Close()
        $gzStream.Close()
        $inStream.Close()
        Set-Content -Path $tempSqlFile -Value $content -Encoding UTF8 -NoNewline
    }

    # 执行恢复
    $mysqlArgs = @("-h", $db.Host, "-P", $db.Port, "-u", $db.User, "-p$($db.Pass)", $TestDb)
    Get-Content $tempSqlFile -Raw | & mysql @mysqlArgs 2>$null

    Remove-Item $tempSqlFile -Force -ErrorAction SilentlyContinue

    $restoreEnd = Get-Date
    $restoreDuration = ($restoreEnd - $restoreStart).TotalSeconds
    Write-Host "  PASS: Restore completed in $([int]$restoreDuration)s" -ForegroundColor Green
    Step-Result "3 - 从备份恢复" "PASSED" "恢复完成,耗时 $([int]$restoreDuration) 秒"
}
catch {
    Write-Host "  FAIL: Restore failed: $_" -ForegroundColor Red
    Step-Result "3 - 从备份恢复" "FAILED" "从备份恢复失败: $_"
    $overallStatus = "FAILED"
    if (-not $NoCleanup) {
        try { Invoke-MySql -Query "DROP DATABASE IF EXISTS ``$TestDb``;" -Silent } catch {}
    }
    Append-Report "## 演练结果: ❌ FAILED"
    exit 1
}

# 步骤 4: 数据完整性检查
Write-Host "[4/6] Running data integrity checks..."
$testTables = Invoke-MySql -Query "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$TestDb';" -Silent -Scalar
$sourceTables = Invoke-MySql -Query "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$($db.DbName)';" -Silent -Scalar

Write-Host "  Test DB tables:   $testTables"
Write-Host "  Source DB tables: $sourceTables"

$testRecords = Invoke-MySql -Query "SELECT SUM(table_rows) FROM information_schema.tables WHERE table_schema='$TestDb';" -Silent -Scalar
$sourceRecords = Invoke-MySql -Query "SELECT SUM(table_rows) FROM information_schema.tables WHERE table_schema='$($db.DbName)';" -Silent -Scalar

Write-Host "  Test DB records:   $testRecords"
Write-Host "  Source DB records: $sourceRecords"

$integrityDetail = @"
| 指标 | 测试库 | 源库 |
|------|--------|------|
| 表数量 | $testTables | $sourceTables |
| 记录数(近似) | $testRecords | $sourceRecords |
"@

if ([int]$testTables -ge [int]$sourceTables) {
    Write-Host "  PASS: Table count matches" -ForegroundColor Green
    Step-Result "4 - 数据完整性检查" "PASSED" $integrityDetail
}
else {
    Write-Host "  WARN: Test DB has fewer tables than source" -ForegroundColor Yellow
    Step-Result "4 - 数据完整性检查" "WARN" "$integrityDetail`n`n⚠️ 测试库表数量少于源库"
}

# 步骤 5: 冒烟测试
Write-Host "[5/6] Running smoke tests..."
$smokePassed = 0
$smokeFailed = 0
$smokeDetail = ""

# 测试 1: 检查 users 表
try {
    $userCount = Invoke-MySql -Query "SELECT COUNT(*) FROM users;" -Database $TestDb -Silent -Scalar
    $smokePassed++
    $smokeDetail += "- ✅ users 表查询成功: $userCount 条记录`n"
    Write-Host "  PASS: users table query: $userCount records" -ForegroundColor Green
}
catch {
    $smokeDetail += "- ⚠️ users 表不存在或查询失败`n"
    Write-Host "  WARN: users table not found or query failed" -ForegroundColor Yellow
}

# 测试 2: 检查 schools 表
try {
    $schoolCount = Invoke-MySql -Query "SELECT COUNT(*) FROM schools;" -Database $TestDb -Silent -Scalar
    $smokePassed++
    $smokeDetail += "- ✅ schools 表查询成功: $schoolCount 条记录`n"
    Write-Host "  PASS: schools table query: $schoolCount records" -ForegroundColor Green
}
catch {
    $smokeDetail += "- ⚠️ schools 表不存在或查询失败`n"
    Write-Host "  WARN: schools table not found or query failed" -ForegroundColor Yellow
}

# 测试 3: 基础表查询
try {
    $baseTableCount = Invoke-MySql -Query "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$TestDb' AND table_type='BASE TABLE';" -Silent -Scalar
    if ([int]$baseTableCount -gt 0) {
        $smokePassed++
        $smokeDetail += "- ✅ 基础表查询成功: $baseTableCount 个基础表`n"
        Write-Host "  PASS: Base table query: $baseTableCount tables" -ForegroundColor Green
    }
    else {
        $smokeFailed++
        $smokeDetail += "- ❌ 基础表查询失败`n"
        Write-Host "  FAIL: Base table query failed" -ForegroundColor Red
    }
}
catch {
    $smokeFailed++
    $smokeDetail += "- ❌ 基础表查询失败`n"
    Write-Host "  FAIL: Base table query failed" -ForegroundColor Red
}

Step-Result "5 - 冒烟测试" "PASSED" "通过: $smokePassed, 失败: $smokeFailed`n`n$smokeDetail"

# 步骤 6: 清理测试数据库
Write-Host "[6/6] Cleaning up test database..."
if ($NoCleanup) {
    Write-Host "  SKIP: Cleanup skipped (--NoCleanup)" -ForegroundColor Yellow
    Step-Result "6 - 清理测试数据库" "SKIPPED" "演练后保留测试数据库 ``$TestDb``"
}
else {
    try {
        Invoke-MySql -Query "DROP DATABASE IF EXISTS ``$TestDb``;" -Silent
        Write-Host "  PASS: Test database dropped: $TestDb" -ForegroundColor Green
        Step-Result "6 - 清理测试数据库" "PASSED" "测试数据库 ``$TestDb`` 已删除"
    }
    catch {
        Write-Host "  WARN: Could not drop test database (manual cleanup required)" -ForegroundColor Yellow
        Step-Result "6 - 清理测试数据库" "WARN" "⚠️ 无法删除测试数据库 ``$TestDb``,需手动清理"
    }
}

# 生成总结
$drillEnd = Get-Date
$drillDuration = ($drillEnd - $drillStart).TotalSeconds

Append-Report "## 演练结果"
Append-Report ""
if ($overallStatus -eq "SUCCESS") {
    Append-Report "**状态**: ✅ 成功"
}
else {
    Append-Report "**状态**: ❌ 失败"
}
Append-Report "**总耗时**: $([int]$drillDuration) 秒"
Append-Report "**备份文件**: ``$BackupFile``"
Append-Report "**测试数据库**: ``$TestDb``"
Append-Report ""
Append-Report "## RTO/RPO 评估"
Append-Report ""
Append-Report "- **RTO 目标**: 4 小时"
Append-Report "- **本次恢复耗时**: $([int]$restoreDuration) 秒 ($([int]($restoreDuration / 60)) 分钟)"
if ($restoreDuration -lt 14400) {
    Append-Report "- **RTO 评估**: ✅ 达标"
}
else {
    Append-Report "- **RTO 评估**: ⚠️ 需关注"
}
Append-Report "- **RPO 目标**: 24 小时(取决于备份频率)"
Append-Report ""

Write-Host ""
Write-Host "=== Drill Summary ===" -ForegroundColor Cyan
Write-Host "Status:    $overallStatus"
Write-Host "Duration:  $([int]$drillDuration)s"
Write-Host "Report:    $ReportFile"
Write-Host ""

if ($overallStatus -eq "SUCCESS") {
    exit 0
}
else {
    exit 1
}
