<#
.SYNOPSIS
    Registers the two v2 Windows services on a node: OtOpcUa (main server, net10) and
    OtOpcUaGalaxyHost (out-of-process Galaxy COM host, net48 x86).

.DESCRIPTION
    Phase 2 Stream D.2 — replaces the v1 single-service install (TopShelf-based OtOpcUa.Host).
    Installs both services with the correct service-account SID + per-process shared secret
    provisioning per `driver-stability.md §"IPC Security"`. Galaxy.Host depends on OtOpcUa
    (Galaxy.Host must be reachable when OtOpcUa starts; service dependency wiring + retry
    handled by OtOpcUa.Server NodeBootstrap).

.PARAMETER InstallRoot
    Where the binaries live (typically C:\Program Files\OtOpcUa).

.PARAMETER ServiceAccount
    Service account SID or DOMAIN\name. Both services run under this account; the
    Galaxy.Host pipe ACL only allows this SID to connect (decision #76).

.PARAMETER GalaxySharedSecret
    Per-process secret passed to Galaxy.Host via env var. Generated freshly per install.

.PARAMETER ZbConnection
    Galaxy ZB SQL connection string (passed to Galaxy.Host via env var).

.EXAMPLE
    .\Install-Services.ps1 -InstallRoot 'C:\Program Files\OtOpcUa' -ServiceAccount 'OTOPCUA\svc-otopcua'
#>
[CmdletBinding()]
param(
    [Parameter(Mandatory)] [string]$InstallRoot,
    [Parameter(Mandatory)] [string]$ServiceAccount,
    [string]$GalaxySharedSecret,
    [string]$ZbConnection = 'Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;',
    [string]$GalaxyClientName = 'OtOpcUa-Galaxy.Host',
    [string]$GalaxyPipeName = 'OtOpcUaGalaxy'
)

$ErrorActionPreference = 'Stop'

if (-not (Test-Path "$InstallRoot\OtOpcUa.Server.exe")) {
    Write-Error "OtOpcUa.Server.exe not found at $InstallRoot — copy the publish output first"
    exit 1
}
if (-not (Test-Path "$InstallRoot\Galaxy\OtOpcUa.Driver.Galaxy.Host.exe")) {
    Write-Error "OtOpcUa.Driver.Galaxy.Host.exe not found at $InstallRoot\Galaxy — copy the publish output first"
    exit 1
}

# Generate a fresh shared secret per install if not supplied. Stored in DPAPI-protected file
# rather than the registry so the service account can read it but other local users cannot.
if (-not $GalaxySharedSecret) {
    $bytes = New-Object byte[] 32
    [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
    $GalaxySharedSecret = [Convert]::ToBase64String($bytes)
}

# Resolve the SID — the IPC ACL needs the SID, not the down-level name.
$sid = if ($ServiceAccount.StartsWith('S-1-')) {
    $ServiceAccount
} else {
    (New-Object System.Security.Principal.NTAccount $ServiceAccount).Translate([System.Security.Principal.SecurityIdentifier]).Value
}

# --- Install OtOpcUaGalaxyHost first (OtOpcUa starts after, depends on it being up).
$galaxyEnv = @(
    "OTOPCUA_GALAXY_PIPE=$GalaxyPipeName"
    "OTOPCUA_ALLOWED_SID=$sid"
    "OTOPCUA_GALAXY_SECRET=$GalaxySharedSecret"
    "OTOPCUA_GALAXY_BACKEND=mxaccess"
    "OTOPCUA_GALAXY_ZB_CONN=$ZbConnection"
    "OTOPCUA_GALAXY_CLIENT_NAME=$GalaxyClientName"
) -join "`0"
$galaxyEnv += "`0`0"

Write-Host "Installing OtOpcUaGalaxyHost..."
& sc.exe create OtOpcUaGalaxyHost binPath= "`"$InstallRoot\Galaxy\OtOpcUa.Driver.Galaxy.Host.exe`"" `
    DisplayName= 'OtOpcUa Galaxy Host (out-of-process MXAccess)' `
    start= auto `
    obj= $ServiceAccount | Out-Null

# Set per-service environment variables via the registry — sc.exe doesn't expose them directly.
$svcKey = "HKLM:\SYSTEM\CurrentControlSet\Services\OtOpcUaGalaxyHost"
$envValue = $galaxyEnv.Split("`0") | Where-Object { $_ -ne '' }
Set-ItemProperty -Path $svcKey -Name 'Environment' -Type MultiString -Value $envValue

# --- Install OtOpcUa (depends on Galaxy host being installed; doesn't strictly require it
#     started — OtOpcUa.Server NodeBootstrap retries on the IPC connect path).
Write-Host "Installing OtOpcUa..."
& sc.exe create OtOpcUa binPath= "`"$InstallRoot\OtOpcUa.Server.exe`"" `
    DisplayName= 'OtOpcUa Server' `
    start= auto `
    depend= 'OtOpcUaGalaxyHost' `
    obj= $ServiceAccount | Out-Null

Write-Host ""
Write-Host "Installed. Start with:"
Write-Host "  sc.exe start OtOpcUaGalaxyHost"
Write-Host "  sc.exe start OtOpcUa"
Write-Host ""
Write-Host "Galaxy shared secret (record this offline — required for service rebinding):"
Write-Host "  $GalaxySharedSecret"
