param(
  [string]$SourceDb = '',
  [int]$ProbePort = 18087,
  [string]$EvidenceDate = (Get-Date -Format 'yyyy-MM-dd')
)

$ErrorActionPreference = 'Stop'

$projectRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
if ([string]::IsNullOrWhiteSpace($SourceDb)) {
  $SourceDb = Join-Path $projectRoot 'data\user_management.db'
}

$evidenceRoot = Join-Path $projectRoot "docs\evidence\ops\$EvidenceDate\rollback"
$goCacheRoot = Join-Path $projectRoot '.cache'
$goBuildCache = Join-Path $goCacheRoot 'go-build'
$goModCache = Join-Path $goCacheRoot 'gomod'
$goPath = Join-Path $goCacheRoot 'gopath'
$timestamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$drillRoot = Join-Path $evidenceRoot $timestamp
$stableDb = Join-Path $drillRoot 'user_management.stable.db'
$stableConfig = Join-Path $drillRoot 'config.stable.yaml'
$candidateConfig = Join-Path $drillRoot 'config.candidate.yaml'
$serverExe = Join-Path $drillRoot 'server-rollback.exe'
$stableInitialStdOut = Join-Path $drillRoot 'stable-initial.stdout.log'
$stableInitialStdErr = Join-Path $drillRoot 'stable-initial.stderr.log'
$candidateStdOut = Join-Path $drillRoot 'candidate.stdout.log'
$candidateStdErr = Join-Path $drillRoot 'candidate.stderr.log'
$stableRollbackStdOut = Join-Path $drillRoot 'stable-rollback.stdout.log'
$stableRollbackStdErr = Join-Path $drillRoot 'stable-rollback.stderr.log'
$reportPath = Join-Path $drillRoot 'ROLLBACK_DRILL.md'

New-Item -ItemType Directory -Force $evidenceRoot, $drillRoot, $goBuildCache, $goModCache, $goPath | Out-Null

function Test-UrlReady {
  param(
    [Parameter(Mandatory = $true)][string]$Url
  )

  try {
    $response = Invoke-WebRequest $Url -UseBasicParsing -TimeoutSec 2
    return $response.StatusCode -ge 200 -and $response.StatusCode -lt 500
  } catch {
    return $false
  }
}

function Wait-UrlReady {
  param(
    [Parameter(Mandatory = $true)][string]$Url,
    [Parameter(Mandatory = $true)][string]$Label,
    [int]$RetryCount = 120,
    [int]$DelayMs = 500
  )

  for ($i = 0; $i -lt $RetryCount; $i++) {
    if (Test-UrlReady -Url $Url) {
      return
    }
    Start-Sleep -Milliseconds $DelayMs
  }

  throw "$Label did not become ready: $Url"
}

function Stop-TreeProcess {
  param(
    [Parameter(Mandatory = $false)]$Process
  )

  if (-not $Process) {
    return
  }

  if (-not $Process.HasExited) {
    try {
      taskkill /PID $Process.Id /T /F *> $null
    } catch {
      Stop-Process -Id $Process.Id -Force -ErrorAction SilentlyContinue
    }
  }
}

function Build-Config {
  param(
    [Parameter(Mandatory = $true)][string]$TemplatePath,
    [Parameter(Mandatory = $true)][string]$OutputPath,
    [Parameter(Mandatory = $true)][string]$DbPath,
    [Parameter(Mandatory = $true)][int]$Port
  )

  $content = Get-Content $TemplatePath -Raw
  $dbPathForYaml = ($DbPath -replace '\\', '/')
  $content = $content -replace '(?m)^  port: \d+$', "  port: $Port"
  $content = [regex]::Replace(
    $content,
    '(?ms)(sqlite:\s*\r?\n\s*path:\s*).+?(\r?\n)',
    "`$1`"$dbPathForYaml`"`$2"
  )
  Set-Content -Path $OutputPath -Value $content -Encoding UTF8
}

function Build-BadCandidateConfig {
  param(
    [Parameter(Mandatory = $true)][string]$StableConfigPath,
    [Parameter(Mandatory = $true)][string]$OutputPath
  )

  $content = Get-Content $StableConfigPath -Raw
  $content = [regex]::Replace(
    $content,
    '(?ms)(allowed_origins:\s*\r?\n)(?:\s*-\s*.+\r?\n)+',
    "`$1    - ""*""`r`n"
  )
  Set-Content -Path $OutputPath -Value $content -Encoding UTF8
}

if (-not (Test-Path $SourceDb)) {
  throw "source db not found: $SourceDb"
}

Copy-Item $SourceDb $stableDb -Force
Build-Config `
  -TemplatePath (Join-Path $projectRoot 'configs\config.yaml') `
  -OutputPath $stableConfig `
  -DbPath $stableDb `
  -Port $ProbePort
Build-BadCandidateConfig `
  -StableConfigPath $stableConfig `
  -OutputPath $candidateConfig

Push-Location $projectRoot
try {
  $env:GOCACHE = $goBuildCache
  $env:GOMODCACHE = $goModCache
  $env:GOPATH = $goPath
  & go build -o $serverExe .\cmd\server
  if ($LASTEXITCODE -ne 0) {
    throw 'build rollback server failed'
  }
} finally {
  Pop-Location
  Remove-Item Env:GOCACHE, Env:GOMODCACHE, Env:GOPATH -ErrorAction SilentlyContinue
}

$previousConfigPath = $env:UMS_CONFIG_PATH
$stableInitialProcess = $null
$candidateProcess = $null
$stableRollbackProcess = $null

try {
  $env:UMS_CONFIG_PATH = $stableConfig
  Remove-Item $stableInitialStdOut, $stableInitialStdErr -Force -ErrorAction SilentlyContinue
  $stableInitialProcess = Start-Process `
    -FilePath $serverExe `
    -WorkingDirectory $projectRoot `
    -PassThru `
    -WindowStyle Hidden `
    -RedirectStandardOutput $stableInitialStdOut `
    -RedirectStandardError $stableInitialStdErr

  Wait-UrlReady -Url "http://127.0.0.1:$ProbePort/health" -Label 'stable initial health endpoint'
  Wait-UrlReady -Url "http://127.0.0.1:$ProbePort/health/ready" -Label 'stable initial readiness endpoint'
  $stableInitialCapabilities = Invoke-RestMethod "http://127.0.0.1:$ProbePort/api/v1/auth/capabilities" -TimeoutSec 5
} finally {
  Stop-TreeProcess $stableInitialProcess
}

try {
  $env:UMS_CONFIG_PATH = $candidateConfig
  Remove-Item $candidateStdOut, $candidateStdErr -Force -ErrorAction SilentlyContinue
  $candidateProcess = Start-Process `
    -FilePath $serverExe `
    -WorkingDirectory $projectRoot `
    -PassThru `
    -WindowStyle Hidden `
    -RedirectStandardOutput $candidateStdOut `
    -RedirectStandardError $candidateStdErr

  Start-Sleep -Seconds 3
  $candidateHealthReady = Test-UrlReady -Url "http://127.0.0.1:$ProbePort/health"
  $candidateExited = $candidateProcess.HasExited
  $candidateStdErrText = if (Test-Path $candidateStdErr) { Get-Content $candidateStdErr -Raw } else { '' }
  $candidateStdOutText = if (Test-Path $candidateStdOut) { Get-Content $candidateStdOut -Raw } else { '' }
} finally {
  Stop-TreeProcess $candidateProcess
}

if ($candidateHealthReady) {
  throw 'candidate release unexpectedly became healthy; rollback drill invalid'
}
if (-not $candidateExited) {
  throw 'candidate release did not exit after invalid release configuration'
}
if ($candidateStdErrText -notmatch 'cors\.allowed_origins cannot contain \* in release mode' -and $candidateStdOutText -notmatch 'cors\.allowed_origins cannot contain \* in release mode') {
  throw 'candidate release did not expose the expected release validation failure'
}

try {
  $env:UMS_CONFIG_PATH = $stableConfig
  Remove-Item $stableRollbackStdOut, $stableRollbackStdErr -Force -ErrorAction SilentlyContinue
  $stableRollbackProcess = Start-Process `
    -FilePath $serverExe `
    -WorkingDirectory $projectRoot `
    -PassThru `
    -WindowStyle Hidden `
    -RedirectStandardOutput $stableRollbackStdOut `
    -RedirectStandardError $stableRollbackStdErr

  Wait-UrlReady -Url "http://127.0.0.1:$ProbePort/health" -Label 'rollback health endpoint'
  Wait-UrlReady -Url "http://127.0.0.1:$ProbePort/health/ready" -Label 'rollback readiness endpoint'
  $stableRollbackCapabilities = Invoke-RestMethod "http://127.0.0.1:$ProbePort/api/v1/auth/capabilities" -TimeoutSec 5
} finally {
  Stop-TreeProcess $stableRollbackProcess

  if ([string]::IsNullOrWhiteSpace($previousConfigPath)) {
    Remove-Item Env:UMS_CONFIG_PATH -ErrorAction SilentlyContinue
  } else {
    $env:UMS_CONFIG_PATH = $previousConfigPath
  }
}

$reportLines = @(
  '# Rollback Drill',
  '',
  "- Generated at: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss zzz')",
  "- Source DB: $SourceDb",
  "- Stable DB copy: $stableDb",
  "- Probe port: $ProbePort",
  '',
  '## Drill Result',
  '',
  '- Stable release started successfully before rollback gate evaluation.',
  '- Candidate release was rejected by release-mode runtime validation before becoming healthy.',
  '- Rollback to the previous stable config/artifact path completed successfully on the same probe port.',
  "- Candidate rejection evidence: $(if ($candidateStdErrText -match 'cors\.allowed_origins cannot contain \* in release mode') { 'stderr matched release validation failure' } elseif ($candidateStdOutText -match 'cors\.allowed_origins cannot contain \* in release mode') { 'stdout matched release validation failure' } else { 'missing' })",
  "- Stable capabilities before rollback: $(($stableInitialCapabilities.data | ConvertTo-Json -Compress))",
  "- Stable capabilities after rollback: $(($stableRollbackCapabilities.data | ConvertTo-Json -Compress))",
  '',
  '## Scope Note',
  '',
  '- This local drill validates rollback operational steps and health gates for the current artifact/config path.',
  '- It does not prove cross-version schema downgrade compatibility between distinct historical releases.',
  '',
  '## Evidence Files',
  '',
  "- $(Split-Path $stableConfig -Leaf)",
  "- $(Split-Path $candidateConfig -Leaf)",
  "- $(Split-Path $stableInitialStdOut -Leaf)",
  "- $(Split-Path $stableInitialStdErr -Leaf)",
  "- $(Split-Path $candidateStdOut -Leaf)",
  "- $(Split-Path $candidateStdErr -Leaf)",
  "- $(Split-Path $stableRollbackStdOut -Leaf)",
  "- $(Split-Path $stableRollbackStdErr -Leaf)",
  ''
)

Set-Content -Path $reportPath -Value ($reportLines -join [Environment]::NewLine) -Encoding UTF8
Get-Content $reportPath
