# scripts\verify-install-windows.ps1
#
# One-command hardware verification for the DPF Windows install + Edge Node
# end-to-end path. Produces a zip bundle + a paste-able summary so an
# operator can run a single command and file the result against the
# install_verification.md issue template (.github\ISSUE_TEMPLATE\).
#
# Covers verification ledger rows 1 and 4 in a single sweep:
#   1. Portal /api/health responds (Windows end-to-end)
#   2. Docker Desktop is running and healthy
#   3. Docker Model Runner reachable (or SKIP if using external LLM_BASE_URL)
#   4. Prometheus targets healthy (or SKIP if monitoring profile not active)
#   5. Edge Node enrolls + heartbeats + submits a discovery run
#
# Run from the repo root in PowerShell 5.1+:
#
#   .\scripts\verify-install-windows.ps1
#   .\scripts\verify-install-windows.ps1 -AuthorityUrl http://my-host:3000
#   .\scripts\verify-install-windows.ps1 -Teardown
#
# Output: %USERPROFILE%\.dpf\verify-bundle-<timestamp>.zip
#   - env-fingerprint.txt   (OS, Docker, Node, pnpm versions)
#   - health.json           (portal /api/health response)
#   - prom-targets.json     (Prometheus scrape targets, if available)
#   - edge-lifecycle.log    (stdout from verify-lifecycle.ts)
#   - summary.md            (paste-able for the GitHub issue)
#
# Sibling: scripts\verify-install-edge.sh (Linux + macOS equivalent)
#
# Requires: PowerShell 5.1+, Docker Desktop, pnpm, Node.js 20+

param(
    [string]$AuthorityUrl = "",
    [string]$PromUrl      = "",
    [switch]$Teardown,
    [switch]$Help
)

# Resolve URL defaults from env if not passed explicitly.
if (-not $AuthorityUrl) {
    $AuthorityUrl = if ($env:DPF_AUTHORITY_URL) { $env:DPF_AUTHORITY_URL } else { "http://localhost:3000" }
}
if (-not $PromUrl) {
    $PromUrl = if ($env:DPF_PROMETHEUS_URL) { $env:DPF_PROMETHEUS_URL } else { "http://localhost:9090" }
}

$ErrorActionPreference = "Stop"

# ---- Logging helpers (match seed-worktree-mcp.ps1 conventions) -----------

function Write-Step { param($msg) Write-Host "`n-> $msg" -ForegroundColor Yellow }
function Write-Ok   { param($msg) Write-Host "  [OK]   $msg" -ForegroundColor Green }
function Write-Skip { param($msg) Write-Host "  [--]   $msg" -ForegroundColor Gray }
function Write-Warn { param($msg) Write-Host "  [!!]   $msg" -ForegroundColor Yellow }
function Write-Fail { param($msg) Write-Host "  [FAIL] $msg" -ForegroundColor Red }

# ---- Help -----------------------------------------------------------------

if ($Help) {
    Write-Host @"
Open Digital Product Factory -- Hardware Verification Wrapper (Windows)

Usage:
  .\scripts\verify-install-windows.ps1 [flags]

Flags:
  -AuthorityUrl <url>   Authority Core URL
                        (default: DPF_AUTHORITY_URL or http://localhost:3000)
  -PromUrl <url>        Prometheus URL
                        (default: DPF_PROMETHEUS_URL or http://localhost:9090)
  -Teardown             Run docker compose down after verification.
                        Default: leave the stack running for inspection.
  -Help                 Show this help.

Output:
  Writes a bundle to %USERPROFILE%\.dpf\verify-bundle-<timestamp>.zip
  and prints a paste-able summary to stdout. Attach the zip to a
  GitHub issue using the install_verification.md template.

Sibling script (Linux + macOS): scripts\verify-install-edge.sh
"@
    exit 0
}

# ---- Paths + counters -----------------------------------------------------

$ScriptDir  = Split-Path -Parent $MyInvocation.MyCommand.Definition
$RepoRoot   = Split-Path -Parent $ScriptDir
Set-Location $RepoRoot

$Timestamp  = (Get-Date -Format "yyyyMMddTHHmmssZ")
$StateDir   = Join-Path $env:USERPROFILE ".dpf"
if (-not (Test-Path -LiteralPath $StateDir)) {
    New-Item -ItemType Directory -Path $StateDir | Out-Null
}
$WorkDir    = Join-Path ([System.IO.Path]::GetTempPath()) "dpf-verify-$Timestamp"
New-Item -ItemType Directory -Path $WorkDir | Out-Null
$BundlePath = Join-Path $StateDir "verify-bundle-$Timestamp.zip"
$SummaryFile = Join-Path $WorkDir "summary.md"

$StepPass = 0
$StepFail = 0

function Record-Step {
    param([string]$Name, [string]$Status, [string]$Detail = "")
    if ($Status -eq "PASS") { $script:StepPass++ }
    if ($Status -eq "FAIL") { $script:StepFail++ }
    $line = "- **${Name}:** ``${Status}``"
    if ($Detail) { $line += " -- $Detail" }
    Add-Content -LiteralPath $script:SummaryFile -Value $line -Encoding UTF8
}

# ---- Summary header -------------------------------------------------------

@"
# DPF Hardware Verification Report (Windows)

**Timestamp:** $Timestamp
**Generated by:** scripts\verify-install-windows.ps1
**Authority URL:** $AuthorityUrl

"@ | Set-Content -LiteralPath $SummaryFile -Encoding UTF8

# ---- Main verification (cleanup in finally) --------------------------------

try {

    # ---- Step 1: Host fingerprint ------------------------------------------
    Write-Step "Capturing host fingerprint"
    $FingerprintFile = Join-Path $WorkDir "env-fingerprint.txt"
    try {
        $lines = @()
        $lines += "## Windows version"
        $lines += "OS: $([System.Environment]::OSVersion.VersionString)"
        $lines += "Edition: $((Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name ProductName -ErrorAction SilentlyContinue).ProductName)"
        $lines += "Architecture: $env:PROCESSOR_ARCHITECTURE"
        $lines += ""
        $lines += "## Docker Desktop"
        $lines += "$(docker --version 2>&1)"
        $lines += "$(docker compose version 2>&1)"
        $lines += ""
        $lines += "## Node / pnpm"
        $lines += "node: $(node -v 2>&1)"
        $lines += "pnpm: $(pnpm -v 2>&1)"
        $lines += ""
        $lines += "## DPF installer"
        $firstLine = Get-Content -LiteralPath (Join-Path $RepoRoot "scripts\fresh-install.ps1") -TotalCount 2 -ErrorAction SilentlyContinue
        $lines += if ($firstLine) { $firstLine } else { "fresh-install.ps1 not found" }
        $lines | Set-Content -LiteralPath $FingerprintFile -Encoding UTF8
        $lineCount = (Get-Content -LiteralPath $FingerprintFile).Count
        Write-Ok "Fingerprint captured ($lineCount lines)"
    } catch {
        Write-Warn "Fingerprint partially captured: $_"
        "Fingerprint error: $_" | Set-Content -LiteralPath $FingerprintFile -Encoding UTF8
    }

    # ---- Step 2: Docker Desktop health -------------------------------------
    Write-Step "Checking Docker Desktop"
    try {
        $dockerInfo = docker info 2>&1
        if ($LASTEXITCODE -eq 0) {
            Write-Ok "Docker Desktop is running"
            Record-Step "Docker Desktop" "PASS"
        } else {
            Write-Fail "Docker Desktop reported errors (exit $LASTEXITCODE)"
            Record-Step "Docker Desktop" "FAIL" "docker info exited $LASTEXITCODE -- start Docker Desktop and retry"
        }
    } catch {
        Write-Fail "Docker command failed: $_"
        Record-Step "Docker Desktop" "FAIL" "docker not found or daemon not running"
    }

    # ---- Step 3: Portal /api/health ----------------------------------------
    Write-Step "Polling portal /api/health"
    $HealthUrl  = ($AuthorityUrl.TrimEnd('/')) + "/api/health"
    $HealthFile = Join-Path $WorkDir "health.json"
    try {
        $resp = Invoke-RestMethod -Uri $HealthUrl -TimeoutSec 10 -ErrorAction Stop
        ($resp | ConvertTo-Json -Depth 5) | Set-Content -LiteralPath $HealthFile -Encoding UTF8
        Write-Ok "Portal healthy at $HealthUrl"
        Record-Step "Portal /api/health" "PASS"
    } catch {
        Write-Fail "Portal at $HealthUrl did not return 200: $_"
        Record-Step "Portal /api/health" "FAIL" "unreachable -- run .\scripts\dpf-start.ps1 first"
        # Portal is required for subsequent steps.
        Write-Host ""
        Write-Warn "Aborting -- portal unreachable. Remaining steps skipped."
        Write-Warn "Bundle will be written with partial results."
        throw "portal-unreachable"
    }

    # ---- Step 4: Docker Model Runner (SKIP if external LLM_BASE_URL) -------
    Write-Step "Checking Docker Model Runner"
    $ModelRunnerUrl = "http://model-runner.docker.internal/engines/v1/models"
    try {
        Invoke-WebRequest -Uri $ModelRunnerUrl -TimeoutSec 5 -ErrorAction Stop | Out-Null
        Write-Ok "Docker Model Runner reachable at $ModelRunnerUrl"
        Record-Step "Docker Model Runner" "PASS"
    } catch {
        Write-Skip "Model Runner not reachable -- OK if LLM_BASE_URL points to an external provider"
        Record-Step "Docker Model Runner" "SKIP" "unreachable at $ModelRunnerUrl"
    }

    # ---- Step 5: Prometheus scrape targets ----------------------------------
    Write-Step "Checking Prometheus scrape targets"
    $PromTargetsUrl = ($PromUrl.TrimEnd('/')) + "/api/v1/targets"
    $PromFile = Join-Path $WorkDir "prom-targets.json"
    try {
        $promResp = Invoke-WebRequest -Uri $PromTargetsUrl -TimeoutSec 10 -ErrorAction Stop
        $promResp.Content | Set-Content -LiteralPath $PromFile -Encoding UTF8
        $parsed  = $promResp.Content | ConvertFrom-Json
        $targets = $parsed.data.activeTargets
        $total   = if ($targets) { $targets.Count } else { 0 }
        $up      = if ($targets) { ($targets | Where-Object { $_.health -eq "up" }).Count } else { 0 }
        $down    = if ($targets) { ($targets | Where-Object { $_.health -eq "down" }).Count } else { 0 }
        if ($total -gt 0 -and $down -eq 0) {
            Write-Ok "Prometheus: $up/$total targets healthy"
            Record-Step "Prometheus scrape targets" "PASS" "$up/$total up"
        } elseif ($total -gt 0) {
            Write-Warn "Prometheus: $up/$total up, $down down"
            Record-Step "Prometheus scrape targets" "FAIL" "$up/$total up, $down down -- see prom-targets.json"
        } else {
            Write-Warn "Prometheus: no targets found in response"
            Record-Step "Prometheus scrape targets" "FAIL" "empty targets list"
        }
    } catch {
        Write-Skip "Prometheus not reachable at $PromTargetsUrl -- monitoring profile may not be active"
        Record-Step "Prometheus scrape targets" "SKIP" "not exposed on host port"
    }

    # ---- Step 6: Issue Edge Node bootstrap token ---------------------------
    Write-Step "Issuing bootstrap token for Edge Node verification"
    $BootstrapToken  = $null
    $TokenStderrFile = Join-Path $WorkDir "issue-token.stderr"
    try {
        # Capture stdout; redirect stderr to file for the bundle.
        $rawOut = pnpm --filter web exec tsx scripts/issue-edge-bootstrap-token.ts --ttl-minutes 30 2>$TokenStderrFile
        if ($LASTEXITCODE -eq 0 -and $rawOut) {
            $candidate = ($rawOut | Select-Object -Last 1).Trim()
            if ($candidate -match '^dpfboot_') {
                $BootstrapToken = $candidate
                $prefix = $BootstrapToken.Substring(0, [Math]::Min(12, $BootstrapToken.Length))
                Write-Ok "Bootstrap token issued ($prefix...)"
                Record-Step "Issue bootstrap token" "PASS"
            } else {
                Write-Warn "Unexpected token output (does not start with dpfboot_)"
                Record-Step "Issue bootstrap token" "FAIL" "unexpected output -- see issue-token.stderr"
            }
        } else {
            Write-Warn "issue-edge-bootstrap-token.ts exited $LASTEXITCODE"
            Record-Step "Issue bootstrap token" "FAIL" "non-zero exit from tsx script"
        }
    } catch {
        Write-Warn "Bootstrap token issuance failed: $_"
        Record-Step "Issue bootstrap token" "FAIL" "$_"
    }

    # ---- Step 7: Edge Node lifecycle ---------------------------------------
    if ($BootstrapToken) {
        Write-Step "Running Edge Node lifecycle verification"
        $LifecycleLog = Join-Path $WorkDir "edge-lifecycle.log"
        $savedAuthUrl = $env:DPF_AUTHORITY_URL
        $savedToken   = $env:DPF_BOOTSTRAP_TOKEN
        try {
            $env:DPF_AUTHORITY_URL   = $AuthorityUrl
            $env:DPF_BOOTSTRAP_TOKEN = $BootstrapToken
            pnpm exec tsx services/edge-node/scripts/verify-lifecycle.ts 2>&1 |
                Out-File -LiteralPath $LifecycleLog -Encoding UTF8
            if ($LASTEXITCODE -eq 0) {
                Write-Ok "Edge Node lifecycle verification passed"
                Record-Step "Edge Node lifecycle (enroll -> heartbeat -> discovery-run)" "PASS"
            } else {
                Write-Warn "Edge Node lifecycle verification failed -- see edge-lifecycle.log"
                Record-Step "Edge Node lifecycle (enroll -> heartbeat -> discovery-run)" "FAIL"
            }
        } catch {
            Write-Warn "Edge Node lifecycle error: $_"
            Record-Step "Edge Node lifecycle (enroll -> heartbeat -> discovery-run)" "FAIL" "$_"
        } finally {
            # Restore env vars (or remove if they were not set before).
            if ($savedAuthUrl) { $env:DPF_AUTHORITY_URL   = $savedAuthUrl }
            else { Remove-Item Env:\DPF_AUTHORITY_URL   -ErrorAction SilentlyContinue }
            if ($savedToken)   { $env:DPF_BOOTSTRAP_TOKEN = $savedToken }
            else { Remove-Item Env:\DPF_BOOTSTRAP_TOKEN -ErrorAction SilentlyContinue }
        }
    } else {
        Write-Skip "No bootstrap token -- skipping Edge Node lifecycle check"
        Record-Step "Edge Node lifecycle" "SKIP" "no bootstrap token issued"
    }

    # ---- Optional teardown -------------------------------------------------
    if ($Teardown) {
        Write-Step "Tearing down (-Teardown)"
        try {
            docker compose down 2>&1 | Out-Null
            Write-Ok "docker compose down succeeded"
            Record-Step "Teardown" "PASS"
        } catch {
            Write-Warn "Teardown failed: $_"
            Record-Step "Teardown" "FAIL" "$_"
        }
    }

} catch {
    # Catch portal-unreachable sentinel and any other unhandled exception.
    if ($_.Exception.Message -ne "portal-unreachable") {
        Write-Fail "Unexpected error: $_"
        "Unexpected error: $_" | Add-Content -LiteralPath $SummaryFile -Encoding UTF8
    }
} finally {
    # ---- Compose summary + zip ---------------------------------------------

    $verdict = if ($StepFail -eq 0) {
        "All verification steps passed. Attach the bundle to a GitHub issue using the install_verification.md template."
    } else {
        "Some verification steps failed. Attach the bundle anyway -- failure reports are equally valuable."
    }

    @"

## Verdict

- Passed: $StepPass
- Failed: $StepFail

$verdict

## Bundle

``````
$BundlePath
``````

Attach this file to your GitHub issue. Review env-fingerprint.txt
before sending if the host contains sensitive information.
"@ | Add-Content -LiteralPath $SummaryFile -Encoding UTF8

    # Compress the work directory contents into the bundle zip.
    try {
        Get-ChildItem -LiteralPath $WorkDir |
            Compress-Archive -DestinationPath $BundlePath -Force -ErrorAction Stop
    } catch {
        Write-Warn "Could not create zip bundle: $_"
    }

    # Print summary to console.
    Write-Host ""
    Write-Host ("=" * 70)
    Get-Content -LiteralPath $SummaryFile | ForEach-Object { Write-Host $_ }
    Write-Host ("=" * 70)
    Write-Host ""

    # Remove temp work directory.
    if (Test-Path -LiteralPath $WorkDir) {
        Remove-Item -Recurse -Force -LiteralPath $WorkDir -ErrorAction SilentlyContinue
    }
}

# ---- Exit code -------------------------------------------------------------
if ($StepFail -eq 0) {
    Write-Ok "Verification complete -- bundle at $BundlePath"
    exit 0
} else {
    Write-Warn "Verification completed with $StepFail failure(s) -- bundle at $BundlePath"
    exit 1
}
