$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"

function Get-Win10UpgradeDir {
    foreach ($d in @("$env:ProgramData\SiteKings", "$env:ProgramData\LCL")) {
        if (Test-Path "$d\config.json") { return $d }
    }
    return "$env:ProgramData\SiteKings"
}

$upgradeDir = Get-Win10UpgradeDir
$logFile = "$upgradeDir\win10_upgrade.log"
$statusFile = "$upgradeDir\win10_upgrade.status.json"
$mediaDir = Join-Path $upgradeDir "win10_media"
$null = New-Item -ItemType Directory -Path $mediaDir -Force

function Escape-JsonString {
    param([string]$Value)
    if ($null -eq $Value) { return "" }
    $s = [string]$Value
    $s = $s.Replace("\", "\\")
    $s = $s.Replace('"', '\"')
    $s = $s.Replace("`r", "\r")
    $s = $s.Replace("`n", "\n")
    $s = $s.Replace("`t", "\t")
    return $s
}

function Write-Status {
    param([string]$Stage, [int]$Percent, [string]$Message)
    $updatedAt = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
    $payload = '{"stage":"' + (Escape-JsonString $Stage) + '","percent":' + $Percent + ',"message":"' + (Escape-JsonString $Message) + '","updated_at":"' + (Escape-JsonString $updatedAt) + '","path":"media","probe_verdict":"' + (Escape-JsonString $script:ProbeVerdict) + '"}'
    try {
        $utf8 = New-Object System.Text.UTF8Encoding($false)
        [System.IO.File]::WriteAllText($statusFile, $payload, $utf8)
    } catch {}
}

function Log {
    param([string]$Text)
    try { Add-Content -LiteralPath $logFile -Value "$(Get-Date) - [win10-media] $Text" } catch {}
}

function Redact-MediaUrlForLog {
    param([string]$Value)
    try {
        $builder = New-Object System.UriBuilder($Value)
        if ($builder.Query) { $builder.Query = "<redacted>" }
        if ($builder.Fragment) { $builder.Fragment = "<redacted>" }
        return $builder.Uri.AbsoluteUri
    } catch {
        return "<redacted>"
    }
}

function Fail-Out {
    param([string]$Message)
    Log "FAILED: $Message"
    Write-Status -Stage "failed" -Percent 0 -Message $Message
    Write-Output "Win10 bridge upgrade FAILED: $Message"
    exit 1
}

function Get-SHA256 {
    param([string]$Path)
    if (Get-Command Get-FileHash -ErrorAction SilentlyContinue) {
        return (Get-FileHash -Algorithm SHA256 -LiteralPath $Path).Hash
    }
    $sha = New-Object System.Security.Cryptography.SHA256Managed
    $stream = [System.IO.File]::OpenRead($Path)
    try {
        $hash = $sha.ComputeHash($stream)
        return ([BitConverter]::ToString($hash) -replace "-", "")
    } finally {
        $stream.Dispose()
        $sha.Dispose()
    }
}

function Require-BridgeAcknowledgement {
    param([string]$EnvName, [string]$Label)
    $value = [Environment]::GetEnvironmentVariable($EnvName)
    $normalized = ""
    if ($value) { $normalized = $value.Trim().ToLowerInvariant() }
    if (@("1", "true", "yes", "on", "checked") -notcontains $normalized) {
        Fail-Out "Required Windows 10 bridge acknowledgement missing: $Label"
    }
}

function Require-BridgeEvidenceNote {
    param([string]$EnvName, [string]$Label)
    $value = [Environment]::GetEnvironmentVariable($EnvName)
    if (-not $value -or -not $value.Trim()) {
        Fail-Out "Required Windows 10 bridge evidence note missing: $Label"
    }
}

$url = [Environment]::GetEnvironmentVariable("SK_WIN10_MEDIA_URL")
$sha256 = [Environment]::GetEnvironmentVariable("SK_WIN10_MEDIA_SHA256")
$probeVerdict = [Environment]::GetEnvironmentVariable("SK_WIN10_PROBE_VERDICT")
if ($probeVerdict) { $probeVerdict = $probeVerdict.Trim().ToLowerInvariant() }
$script:ProbeVerdict = $probeVerdict
if (-not $url) { Fail-Out "SK_WIN10_MEDIA_URL is empty. Configure win10_media_url or pass a URL in the command payload." }
if (-not $url.ToLowerInvariant().StartsWith("https://")) { Fail-Out "Windows 10 media URL must use HTTPS for the guarded bridge upgrade." }
if (-not $sha256 -or $sha256 -notmatch "^[A-Fa-f0-9]{64}$") { Fail-Out "SK_WIN10_MEDIA_SHA256 must be a 64-character SHA256 checksum." }
if ($probeVerdict -ne "candidate") { Fail-Out "Windows 10 bridge requires latest probe verdict candidate before media launch." }
Log "Probe verdict candidate confirmed before media launch."

try { [Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([Net.SecurityProtocolType], 3072) } catch {}
Write-Status -Stage "preflight" -Percent 1 -Message "Checking Windows 10 bridge operator acknowledgements..."
$safeUrl = Redact-MediaUrlForLog $url
Log "Starting Windows 10 bridge upgrade. URL=$safeUrl"
Require-BridgeAcknowledgement "SK_WIN10_ACK_BACKUP" "backup evidence or machine image"
Require-BridgeAcknowledgement "SK_WIN10_ACK_ROLLBACK" "rollback plan"
Require-BridgeAcknowledgement "SK_WIN10_ACK_VENDOR_SOFTWARE" "vendor software compatibility"
Require-BridgeAcknowledgement "SK_WIN10_ACK_CLINICAL_EQUIPMENT" "clinical equipment peripherals"
Require-BridgeEvidenceNote "SK_WIN10_CUSTOMER_SCOPED_ENABLEMENT" "customer-scoped enablement"
Require-BridgeEvidenceNote "SK_WIN10_BACKUP_EVIDENCE" "backup evidence"
Require-BridgeEvidenceNote "SK_WIN10_ROLLBACK_PLAN" "rollback plan"
Require-BridgeEvidenceNote "SK_WIN10_VENDOR_SOFTWARE_REVIEW" "vendor software review"
Require-BridgeEvidenceNote "SK_WIN10_CLINICAL_EQUIPMENT_REVIEW" "clinical equipment review"
Log "Operator acknowledgements recorded: backup, rollback, vendor software, clinical equipment."
Log "Operator evidence notes recorded: customer-scoped enablement, backup, rollback, vendor software, clinical equipment."
Write-Status -Stage "preflight" -Percent 2 -Message "Checking Windows 7 Service Pack 1 readiness..."

try {
    $os = Get-WmiObject Win32_OperatingSystem -ErrorAction Stop
    $caption = [string]$os.Caption
    $sp = [string]$os.CSDVersion
    if ($caption -match "Windows 10") {
        Write-Status -Stage "completed" -Percent 100 -Message "System already reports Windows 10."
        Write-Output "Already on Windows 10."
        exit 0
    }
    if ($caption -match "Server 2008 R2") {
        Fail-Out "Windows Server 2008 R2 upgrades need a separate server migration lane."
    }
    if ($caption -notmatch "Windows 7") {
        Fail-Out "OS is not Windows 7. Detected: $caption $sp"
    }
    if ($sp -notmatch "Service Pack 1") {
        Fail-Out "Windows 7 Service Pack 1 is required before the Windows 10 bridge upgrade."
    }
} catch { Fail-Out "Could not query Win32_OperatingSystem: $_" }

try {
    $sys = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'" -ErrorAction Stop
    $freeGb = [math]::Round($sys.FreeSpace / 1GB, 1)
    if ($freeGb -lt 30) {
        Fail-Out "C: only has $freeGb GB free; need at least 30 GB before staging Windows 10 media."
    }
} catch { Fail-Out "Could not query C: free space: $_" }

try {
    $batt = Get-WmiObject Win32_Battery -ErrorAction SilentlyContinue
    if ($batt) {
        $offline = @($batt | Where-Object { @(1, 4, 5) -contains $_.BatteryStatus })
        if ($offline.Count -gt 0) { Fail-Out "Device appears to be running on battery. Plug into AC before starting." }
    }
} catch {}

Log "Clinical equipment caution: verify medical device drivers, vendor applications, and connected peripherals before upgrade."
Write-Status -Stage "downloading" -Percent 10 -Message "Downloading Windows 10 media..."

$fileName = Split-Path ([uri]$url).AbsolutePath -Leaf
if (-not $fileName) { $fileName = "win10-media.zip" }
$mediaPath = Join-Path $mediaDir $fileName
$webClient = New-Object System.Net.WebClient
try {
    $webClient.DownloadFile($url, $mediaPath)
} catch {
    Fail-Out "Download failed: $_"
} finally {
    try { $webClient.Dispose() } catch {}
}
if (-not (Test-Path $mediaPath)) { Fail-Out "Download finished but media file is missing." }
if ((Get-Item $mediaPath).Length -lt 100MB) { Fail-Out "Downloaded media is suspiciously small. It may be an HTML error page." }

if ($sha256) {
    Write-Status -Stage "verifying" -Percent 25 -Message "Verifying SHA256..."
    $actual = Get-SHA256 $mediaPath
    if ($actual -ne $sha256.ToUpperInvariant()) {
        Fail-Out "SHA256 mismatch. Expected $sha256, got $actual."
    }
}

$stageDir = Join-Path $mediaDir "stage"
if (Test-Path $stageDir) { Remove-Item -LiteralPath $stageDir -Recurse -Force }
New-Item -ItemType Directory -Path $stageDir -Force | Out-Null

if ($mediaPath.ToLowerInvariant().EndsWith(".zip")) {
    Write-Status -Stage "extracting" -Percent 35 -Message "Extracting Windows 10 media ZIP..."
    $shell = New-Object -ComObject Shell.Application
    $zip = $shell.NameSpace($mediaPath)
    $dest = $shell.NameSpace($stageDir)
    if (-not $zip -or -not $dest) { Fail-Out "Could not open ZIP media. Provide a ZIP of extracted Windows 10 setup files." }
    $dest.CopyHere($zip.Items(), 0x14)
    Start-Sleep -Seconds 10
} elseif ($mediaPath.ToLowerInvariant().EndsWith(".iso")) {
    if (-not (Get-Command Mount-DiskImage -ErrorAction SilentlyContinue)) {
        Fail-Out "Windows 7 cannot mount ISO files natively. Provide a ZIP of extracted Windows 10 media or pre-stage setup files."
    }
    Write-Status -Stage "mounting" -Percent 35 -Message "Mounting Windows 10 ISO..."
    $disk = Mount-DiskImage -ImagePath $mediaPath -PassThru -ErrorAction Stop
    Start-Sleep -Seconds 2
    $vol = Get-DiskImage -ImagePath $mediaPath | Get-Volume
    if (-not $vol -or -not $vol.DriveLetter) { Fail-Out "ISO mounted but no drive letter was assigned." }
    & robocopy "$($vol.DriveLetter):" $stageDir /MIR /NFL /NDL /NJH /NJS /NP /R:2 /W:1 | Out-Null
    if ($LASTEXITCODE -ge 8) { Fail-Out "robocopy failed mirroring media to stage (exit $LASTEXITCODE)." }
    try { Dismount-DiskImage -ImagePath $mediaPath -ErrorAction SilentlyContinue } catch {}
} else {
    Fail-Out "Unsupported media type. Provide .zip extracted Windows 10 media or .iso on hosts with Mount-DiskImage."
}

$setupExe = Join-Path $stageDir "setup.exe"
if (-not (Test-Path $setupExe)) {
    $setupExe = Get-ChildItem -Path $stageDir -Filter setup.exe -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName
}
if (-not $setupExe -or -not (Test-Path $setupExe)) { Fail-Out "Staged media does not contain setup.exe." }

Write-Status -Stage "launching_setup" -Percent 55 -Message "Launching Windows 10 setup. Do not power off this clinical equipment workstation."
Log "Launching setup.exe from $setupExe"
$args = "/auto upgrade /quiet /dynamicupdate enable /noreboot"
$p = Start-Process -FilePath $setupExe -ArgumentList $args -WorkingDirectory (Split-Path $setupExe) -WindowStyle Hidden -PassThru
Write-Status -Stage "setup_running" -Percent 60 -Message "Windows 10 setup launched. Reboot will be required when setup reaches that phase."
Write-Output "Win10 bridge upgrade launched (pid=$($p.Id)). Monitor win10_upgrade.status.json and setup logs."
