#Requires -Version 5.1
# Installs BridgeLink WebAdmin as a Windows Service via NSSM.
# Runs as administrator from the [Run] section of the Inno Setup installer.
param([string]$AppDir = "", [string]$Port = "8444", [string]$BridgeLinkUrl = "")
if (-not $AppDir) { $AppDir = $PSScriptRoot }
$ErrorActionPreference = "Stop"

$ServiceName = "BridgeLinkWebAdmin"
$NodeVersion = "22.22.2"
$DataDir     = Join-Path $env:ProgramData "BridgeLink"
$NodeDataDir = Join-Path $DataDir ".node"
$NodeExe     = Join-Path $NodeDataDir "node.exe"
$CertsDir    = Join-Path $DataDir "certs"
$CertFile    = Join-Path $CertsDir "server.crt"
$KeyFile     = Join-Path $CertsDir "server.key"
$NssmExe     = Join-Path $AppDir "tools\nssm.exe"
$ServicePs1  = Join-Path $AppDir "service-start.ps1"
$LogDir      = Join-Path $AppDir "logs"

Write-Host "[install-service] AppDir: $AppDir"

# ── 1. Download Node.js to ProgramData (accessible by LocalSystem service) ──
$BundledNode = Join-Path $AppDir "node-runtime\node.exe"
$BundledHash = Join-Path $AppDir "node-runtime\node.exe.sha256"
if ((-not (Test-Path -LiteralPath $NodeExe)) -and (Test-Path -LiteralPath $BundledNode)) {
    $expectedHash = (Get-Content $BundledHash -Raw).Trim().ToLower()
    $actualHash   = (Get-FileHash -Algorithm SHA256 -Path $BundledNode).Hash.ToLower()
    if ($actualHash -eq $expectedHash) {
        New-Item -ItemType Directory -Force -Path $NodeDataDir | Out-Null
        Copy-Item $BundledNode $NodeExe -Force
        Write-Host "[install-service] Using bundled Node.js runtime (offline installer) - no download."
    } else {
        Write-Warning "[install-service] Bundled Node.js runtime failed integrity check - falling back to download."
    }
}
if (-not (Test-Path -LiteralPath $NodeExe)) {
    Write-Host "[install-service] Downloading Node.js $NodeVersion for Windows Service..."
    New-Item -ItemType Directory -Force -Path $NodeDataDir | Out-Null
    $ZipPath    = Join-Path $env:TEMP "bl-node-svc-$PID.zip"
    $ExtractDir = Join-Path $env:TEMP "bl-node-svc-$PID"
    try {
        Invoke-WebRequest `
            -Uri "https://nodejs.org/dist/v$NodeVersion/node-v$NodeVersion-win-x64.zip" `
            -OutFile $ZipPath -UseBasicParsing
        # Verify SHA256 integrity against official SHASUMS256.txt
        $Sha256Url = "https://nodejs.org/dist/v$NodeVersion/SHASUMS256.txt"
        $Shasums   = (Invoke-WebRequest -Uri $Sha256Url -UseBasicParsing).Content
        $Expected  = ($Shasums -split "`n" | Where-Object { $_ -match "node-v$NodeVersion-win-x64\.zip" }) -replace '\s.*', ''
        $Actual    = (Get-FileHash -Algorithm SHA256 -Path $ZipPath).Hash.ToLower()
        if ($Actual -ne $Expected.Trim().ToLower()) {
            throw "Node.js download integrity check FAILED. Expected: $Expected  Got: $Actual"
        }
        Write-Host "[install-service] Node.js download integrity verified."
        Expand-Archive -Path $ZipPath -DestinationPath $ExtractDir -Force
        Copy-Item (Join-Path $ExtractDir "node-v$NodeVersion-win-x64\node.exe") $NodeExe -Force
        Write-Host "[install-service] Node.js ready at: $NodeExe"
    } catch {
        Write-Warning "[install-service] Node.js download failed: $($_.Exception.Message)"
        Write-Warning "[install-service] Service is installed but will not start until Node.js is available at: $NodeExe"
    } finally {
        Remove-Item $ZipPath    -Force -ErrorAction SilentlyContinue
        Remove-Item $ExtractDir -Recurse -Force -ErrorAction SilentlyContinue
    }
} else {
    Write-Host "[install-service] Node.js already present at: $NodeExe"
}

# ── 2. Generate TLS certificate to ProgramData (shared between service and bat) ─
# Pre-Phase-8 certs cover localhost only. The Start Menu/desktop shortcuts now
# open https://<COMPUTERNAME>:<port>, so a cert whose SAN lacks the computer
# name is deleted here and regenerated below with both names.
if ((Test-Path -LiteralPath $CertFile) -and (Test-Path -LiteralPath $KeyFile)) {
    try {
        $existing = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $CertFile
        $sanExt = $existing.Extensions | Where-Object { $_.Oid.Value -eq '2.5.29.17' }
        if (-not $sanExt -or ($sanExt.Format($false) -notmatch [regex]::Escape($env:COMPUTERNAME))) {
            Write-Host "[install-service] Existing certificate lacks the $env:COMPUTERNAME SAN - regenerating..."
            Remove-Item $CertFile, $KeyFile -Force
        }
    } catch {
        Write-Warning "[install-service] Could not inspect existing certificate: $($_.Exception.Message)"
    }
}
if (-not (Test-Path -LiteralPath $CertFile) -or -not (Test-Path -LiteralPath $KeyFile)) {
    Write-Host "[install-service] Generating TLS certificate..."
    New-Item -ItemType Directory -Force -Path $CertsDir | Out-Null
    try {
        $cert = New-SelfSignedCertificate `
            -DnsName "localhost", $env:COMPUTERNAME `
            -CertStoreLocation "Cert:\LocalMachine\My" `
            -NotAfter (Get-Date).AddYears(10) `
            -KeyAlgorithm RSA -KeyLength 2048 -HashAlgorithm SHA256 `
            -KeyExportPolicy Exportable

        $certDer = $cert.Export(
            [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)
        $certB64 = [Convert]::ToBase64String(
            $certDer, [System.Base64FormattingOptions]::InsertLineBreaks)
        "-----BEGIN CERTIFICATE-----`r`n$certB64`r`n-----END CERTIFICATE-----" |
            Set-Content -Path $CertFile -Encoding ASCII

        $rsa    = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
        $keyDer = $rsa.Key.Export([System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob)
        $keyB64 = [Convert]::ToBase64String(
            $keyDer, [System.Base64FormattingOptions]::InsertLineBreaks)
        "-----BEGIN PRIVATE KEY-----`r`n$keyB64`r`n-----END PRIVATE KEY-----" |
            Set-Content -Path $KeyFile -Encoding ASCII

        Write-Host "[install-service] Certificate generated at: $CertsDir"

        # Trust the cert machine-wide so Chrome/Edge do not show ERR_CERT_AUTHORITY_INVALID.
        # LocalMachine\My (Personal) is NOT a trusted root — must also import into Root.
        try {
            Import-Certificate -FilePath $CertFile -CertStoreLocation "Cert:\LocalMachine\Root" | Out-Null
            Write-Host "[install-service] Certificate added to LocalMachine\Root (trusted by all users/browsers)."
        } catch {
            Write-Warning "[install-service] Could not add cert to trusted root: $($_.Exception.Message)"
            Write-Warning "[install-service] Browser will show a security warning until the cert is trusted manually."
        }
    } catch {
        Write-Warning "[install-service] Certificate generation failed: $($_.Exception.Message)"
        Write-Warning "[install-service] Service is installed but HTTPS will not work until a certificate is placed at: $CertsDir"
    }
} else {
    Write-Host "[install-service] TLS certificate already present at: $CertsDir"

    # Also ensure existing cert is trusted (handles re-installs where cert already existed)
    if (-not (Get-ChildItem "Cert:\LocalMachine\Root" | Where-Object { $_.Subject -like "*CN=localhost*" })) {
        try {
            Import-Certificate -FilePath $CertFile -CertStoreLocation "Cert:\LocalMachine\Root" | Out-Null
            Write-Host "[install-service] Existing certificate added to LocalMachine\Root."
        } catch {
            Write-Warning "[install-service] Could not add existing cert to trusted root: $($_.Exception.Message)"
        }
    }
}

# ── 3. Ensure log directory ───────────────────────────────────────────────────
New-Item -ItemType Directory -Force -Path $LogDir | Out-Null

# ── 4. Remove existing service if present ────────────────────────────────────
# Pre-Phase-8 installs registered the service under the spaced legacy name.
# Remove it first so it does not keep holding the port (the new service would
# crash-loop) and does not race the new service for the port at boot.
$LegacyName = "BridgeLink WebAdmin"
if (Get-Service -Name $LegacyName -ErrorAction SilentlyContinue) {
    Write-Host "[install-service] Removing legacy service '$LegacyName'..."
    & $NssmExe stop $LegacyName
    if ($LASTEXITCODE -ne 0) { Write-Warning "[install-service] NSSM stop (legacy) returned exit code $LASTEXITCODE" }
    Start-Sleep -Seconds 3
    & $NssmExe remove $LegacyName confirm
    if ($LASTEXITCODE -ne 0) { Write-Warning "[install-service] NSSM remove (legacy) returned exit code $LASTEXITCODE" }
}

$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if ($svc) {
    Write-Host "[install-service] Removing existing service..."
    & $NssmExe stop $ServiceName
    if ($LASTEXITCODE -ne 0) { Write-Warning "[install-service] NSSM stop returned exit code $LASTEXITCODE" }
    Start-Sleep -Seconds 3
    & $NssmExe remove $ServiceName confirm
    if ($LASTEXITCODE -ne 0) { throw "NSSM remove failed with exit code $LASTEXITCODE" }
}

# ── 5. Install service via NSSM ───────────────────────────────────────────────
Write-Host "[install-service] Installing service: $ServiceName"
$PoshExe = "$env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe"
$ServicePs1Full = Join-Path $AppDir "service-start.ps1"
$EscapedAppDir  = $AppDir -replace '"', '\"'
$EscapedPs1Full = $ServicePs1Full -replace '"', '\"'
& $NssmExe install $ServiceName $PoshExe
if ($LASTEXITCODE -ne 0) { throw "NSSM install failed with exit code $LASTEXITCODE" }
# PS 5.1 strips embedded double-quotes when passing to a native exe.
# Write AppParameters directly to the NSSM registry key so path quoting is preserved.
$NssmParamsKey = "HKLM:\SYSTEM\CurrentControlSet\Services\$ServiceName\Parameters"
Set-ItemProperty -Path $NssmParamsKey -Name "AppParameters" `
    -Value "-NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$EscapedPs1Full`" -AppDir `"$EscapedAppDir`""
Write-Host "[install-service] AppParameters written to registry with path quoting preserved."
& $NssmExe set $ServiceName AppDirectory $AppDir
& $NssmExe set $ServiceName DisplayName "BridgeLinkWebAdmin"
& $NssmExe set $ServiceName Description "BridgeLink Web Administrator HTTPS Server (Innovar Healthcare)"
& $NssmExe set $ServiceName Start SERVICE_AUTO_START
& $NssmExe set $ServiceName AppStdout (Join-Path $LogDir "service.log")
& $NssmExe set $ServiceName AppStderr (Join-Path $LogDir "service-error.log")
& $NssmExe set $ServiceName AppRotateFiles 1
& $NssmExe set $ServiceName AppRotateBytes 5242880
& $NssmExe set $ServiceName AppRotateOnline 1
& $NssmExe set $ServiceName AppRestartDelay 5000

# ── 6. Write webadmin.conf before starting the service ─────────────────────
# Must happen before nssm start so the service reads the correct PORT and
# BRIDGELINK_SERVER_URL on its very first launch.
$ConfPath = Join-Path $AppDir "webadmin.conf"
$SafePort = $Port -replace '[\r\n]', ''
$SafeUrl  = $BridgeLinkUrl -replace '[\r\n]', ''
if ([string]::IsNullOrWhiteSpace($SafeUrl)) {
    # Same Host: no server URL supplied - write PORT only (never a key with an empty value)
    "PORT=$SafePort`r`n" |
        Set-Content -Path $ConfPath -Encoding ASCII
    Write-Host "[install-service] webadmin.conf written (same-host): PORT=$SafePort - no server URL; WebAdmin auto-authorizes the local BridgeLink server"
} elseif ($SafeUrl -like '*,*') {
    # Multi-server: write BRIDGELINK_ALLOWED_SERVERS only (omit BRIDGELINK_SERVER_URL per D-06)
    "PORT=$SafePort`r`nBRIDGELINK_ALLOWED_SERVERS=$SafeUrl`r`n" |
        Set-Content -Path $ConfPath -Encoding ASCII
    Write-Host "[install-service] webadmin.conf written (multi): PORT=$SafePort  ALLOWED_SERVERS=$SafeUrl"
} else {
    # Single server or localhost: write BRIDGELINK_SERVER_URL as before
    "PORT=$SafePort`r`nBRIDGELINK_SERVER_URL=$SafeUrl`r`n" |
        Set-Content -Path $ConfPath -Encoding ASCII
    Write-Host "[install-service] webadmin.conf written: PORT=$SafePort  URL=$SafeUrl"
}

# ── 7. Windows Firewall rule for HTTPS port ───────────────────────────────────
$FwRuleName = "BridgeLink WebAdmin HTTPS"
try {
    Remove-NetFirewallRule -DisplayName $FwRuleName -ErrorAction SilentlyContinue
    New-NetFirewallRule -DisplayName $FwRuleName `
        -Direction Inbound -Protocol TCP -LocalPort $Port -Action Allow | Out-Null
    Write-Host "[install-service] Firewall rule added: TCP inbound port $Port"
} catch {
    Write-Warning "[install-service] Could not add firewall rule: $($_.Exception.Message)"
    Write-Warning "[install-service] Add manually: New-NetFirewallRule -DisplayName '$FwRuleName' -Direction Inbound -Protocol TCP -LocalPort $Port -Action Allow"
}

# ── 8. Start the service ──────────────────────────────────────────────────────
Write-Host "[install-service] Starting service..."
& $NssmExe start $ServiceName
if ($LASTEXITCODE -ne 0) { Write-Warning "[install-service] NSSM start returned exit code $LASTEXITCODE" }
Start-Sleep -Seconds 5
$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue
if ($svc -and $svc.Status -eq 'Running') {
    Write-Host "[install-service] Service is RUNNING. BridgeLink WebAdmin is ready."
} else {
    Write-Warning "[install-service] Service did not start within 5 seconds."
    Write-Warning "[install-service] Check logs for errors:"
    Write-Warning "[install-service]   Stdout: $(Join-Path $LogDir 'service.log')"
    Write-Warning "[install-service]   Stderr: $(Join-Path $LogDir 'service-error.log')"
    Write-Warning "[install-service] Common causes: Node.js download failed, port already in use, or cert generation error."
}
Write-Host "[install-service] Done. '$ServiceName' installed as auto-start Windows Service."
Write-Host "[install-service] Logs: $LogDir"
