src/public/Deployment/Invoke-AitherElysiumDeploy.ps1

#Requires -Version 7.0

<#
.SYNOPSIS
    Deploy AitherNode to a remote host via the Elysium deployment pipeline.

.DESCRIPTION
    End-to-end orchestrator that handles the complete Elysium deployment workflow:

    1. BOOTSTRAP: Remote host prerequisite setup (Hyper-V, Docker, PS7, networking)
    2. DEPLOY: AitherNode container deployment with failover configuration
    3. MESH: Join the AitherMesh with auto-discovery and failover priority
    4. VERIFY: Health check all endpoints and validate mesh membership

    This is the "one command" entry point for deploying a compute node from your
    dev machine to a remote lab server and connecting it to the AitherOS Core mesh.

    Wraps the 31-remote automation scripts (3100, 3101, 3102) into a cohesive pipeline.

.PARAMETER ComputerName
    Target server hostname or IP address. REQUIRED.

.PARAMETER Credential
    PSCredential for remote authentication.

.PARAMETER CredentialName
    Name of a stored AitherZero credential.

.PARAMETER UseSSH
    Use SSH transport instead of WinRM.

.PARAMETER Profile
    Service profile for the node. Default: core.

.PARAMETER SkipBootstrap
    Skip OS-level bootstrap (assume host is already prepared).

.PARAMETER GPU
    Enable GPU passthrough.

.PARAMETER FailoverPriority
    Failover priority (1=highest). Default: 10.

.PARAMETER StartWatchdog
    Start the failover watchdog after deployment.

.PARAMETER DryRun
    Preview what would be done.

.PARAMETER PassThru
    Return deployment result object.

.INPUTS
    System.String — Computer names can be piped.

.OUTPUTS
    PSCustomObject — Deployment result with health, mesh, and failover status.

.EXAMPLE
    Invoke-AitherElysiumDeploy -ComputerName "lab-server" -Credential (Get-Credential)

    Full end-to-end: bootstrap → deploy → mesh join.

.EXAMPLE
    Invoke-AitherElysiumDeploy -ComputerName "192.168.1.50" -SkipBootstrap -GPU

    Deploy to an already-prepared host with GPU support.

.EXAMPLE
    "node1", "node2" | Invoke-AitherElysiumDeploy -CredentialName "LabAdmin" -FailoverPriority 5

    Deploy to multiple nodes with stored credentials.

.EXAMPLE
    Invoke-AitherElysiumDeploy -ComputerName "lab" -StartWatchdog -PassThru

    Deploy and start continuous failover monitoring.

.NOTES
    Part of AitherZero module — Deployment category.
    Requires: AitherZero module, network access to target.
#>

function Invoke-AitherElysiumDeploy {
    [OutputType([PSCustomObject])]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName,
                   HelpMessage = "Target server hostname or IP")]
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerName,

        [PSCredential]$Credential,
        [string]$CredentialName,

        [switch]$UseSSH,

        [ValidateSet("minimal", "core", "gpu", "dashboard", "all")]
        [string]$Profile = "core",

        [switch]$SkipBootstrap,
        [switch]$GPU,

        [ValidateRange(1, 100)]
        [int]$FailoverPriority = 10,

        [string[]]$ReplicateServices = @("Pulse", "Chronicle", "Strata"),

        [string]$CoreUrl,
        [string]$MeshToken,

        [switch]$StartWatchdog,
        [switch]$DryRun,
        [switch]$Force,
        [switch]$PassThru
    )

    begin {
        $startTime = Get-Date
        $results = @()
        $projectRoot = $null

        # Locate project root
        $searchDir = $PSScriptRoot
        while ($searchDir) {
            if (Test-Path (Join-Path $searchDir "AitherZero" "AitherZero.psd1")) {
                $projectRoot = $searchDir
                break
            }
            $parent = Split-Path $searchDir -Parent
            if ($parent -eq $searchDir) { break }
            $searchDir = $parent
        }
        if (-not $projectRoot -and $env:AITHERZERO_ROOT) {
            $projectRoot = $env:AITHERZERO_ROOT
        }

        # Resolve credential
        if ($CredentialName -and -not $Credential) {
            try {
                $Credential = Get-AitherCredential -Name $CredentialName -ErrorAction Stop
            }
            catch {
                Write-Warning "Could not retrieve credential '$CredentialName': $($_.Exception.Message)"
            }
        }

        # Auto-detect Core URL
        if (-not $CoreUrl) {
            $localIP = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object {
                $_.PrefixOrigin -eq 'Dhcp' -or $_.PrefixOrigin -eq 'Manual'
            } | Where-Object { $_.IPAddress -notmatch '^(127\.|169\.254\.)' } | Select-Object -First 1).IPAddress
            if ($localIP) {
                $CoreUrl = "http://${localIP}:8001"
            }
            else {
                $CoreUrl = "http://localhost:8001"
            }
        }

        # Generate mesh token if not provided
        if (-not $MeshToken) {
            $MeshToken = [Convert]::ToBase64String(
                [System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32)
            )
        }

        # Banner
        Write-Host ""
        Write-Host " ╔═══════════════════════════════════════════════════════╗" -ForegroundColor Magenta
        Write-Host " ║ AitherOS Elysium Deployment ║" -ForegroundColor Magenta
        Write-Host " ║ Remote Node Deploy → Mesh Join → Hot Failover ║" -ForegroundColor Magenta
        Write-Host " ╚═══════════════════════════════════════════════════════╝" -ForegroundColor Magenta
        Write-Host ""
        Write-Host " Core: $CoreUrl" -ForegroundColor Cyan
        Write-Host " Profile: $Profile" -ForegroundColor White
        Write-Host " Mode: $(if ($DryRun) { 'DRY RUN' } else { 'LIVE' })" -ForegroundColor $(if ($DryRun) { 'Yellow' } else { 'Green' })
        Write-Host ""
    }

    process {
        foreach ($target in $ComputerName) {
            Write-Host " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray
            Write-Host " TARGET: $target" -ForegroundColor White
            Write-Host " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor DarkGray

            $nodeResult = [PSCustomObject]@{
                PSTypeName       = 'AitherOS.ElysiumDeployResult'
                ComputerName     = $target
                Status           = 'Unknown'
                BootstrapStatus  = 'Skipped'
                DeployStatus     = 'NotStarted'
                MeshStatus       = 'NotStarted'
                FailoverPriority = $FailoverPriority
                Profile          = $Profile
                CoreUrl          = $CoreUrl
                Duration         = $null
                Timestamp        = Get-Date
                Error            = $null
            }

            $nodeStart = Get-Date

            try {
                # ── PHASE 1: BOOTSTRAP ─────────────────────────────────
                if (-not $SkipBootstrap) {
                    Write-Host " [1/3] BOOTSTRAP — Installing prerequisites on $target" -ForegroundColor Cyan

                    $bootstrapScript = Join-Path $projectRoot "AitherZero" "library" "automation-scripts" "31-remote" "3100_Setup-HyperVHost.ps1"

                    if (Test-Path $bootstrapScript) {
                        $bootstrapParams = @{
                            ComputerName = $target
                            CoreUrl      = $CoreUrl
                            MeshToken    = $MeshToken
                            GPU          = $GPU
                            PassThru     = $true
                        }
                        if ($Credential) { $bootstrapParams.Credential = $Credential }
                        if ($UseSSH)     { $bootstrapParams.UseSSH = $true }
                        if ($Force)      { $bootstrapParams.Force = $true }
                        if ($DryRun)     { $bootstrapParams.DryRun = $true }

                        $bsResult = & $bootstrapScript @bootstrapParams
                        $nodeResult.BootstrapStatus = $bsResult.Status ?? 'Completed'
                    }
                    else {
                        Write-Warning "Bootstrap script not found at $bootstrapScript — running inline bootstrap"
                        $nodeResult.BootstrapStatus = 'ScriptNotFound'
                    }
                }
                else {
                    Write-Host " [1/3] BOOTSTRAP — Skipped (-SkipBootstrap)" -ForegroundColor DarkGray
                    $nodeResult.BootstrapStatus = 'Skipped'
                }

                # ── PHASE 2: DEPLOY ────────────────────────────────────
                Write-Host " [2/3] DEPLOY — Deploying AitherNode containers" -ForegroundColor Cyan

                $deployScript = Join-Path $projectRoot "AitherZero" "library" "automation-scripts" "31-remote" "3101_Deploy-RemoteNode.ps1"

                if (Test-Path $deployScript) {
                    $deployParams = @{
                        ComputerName      = $target
                        Profile           = $Profile
                        FailoverPriority  = $FailoverPriority
                        ReplicateServices = $ReplicateServices
                        CoreUrl           = $CoreUrl
                        MeshToken         = $MeshToken
                        PassThru          = $true
                    }
                    if ($Credential) { $deployParams.Credential = $Credential }
                    if ($UseSSH)     { $deployParams.UseSSH = $true }
                    if ($Force)      { $deployParams.Force = $true }
                    if ($DryRun)     { $deployParams.DryRun = $true }

                    $depResult = & $deployScript @deployParams
                    $nodeResult.DeployStatus = $depResult.Status ?? 'Completed'
                }
                else {
                    Write-Warning "Deploy script not found — falling back to direct deployment"
                    $nodeResult.DeployStatus = 'FallbackUsed'
                }

                # ── PHASE 3: MESH VERIFY ───────────────────────────────
                Write-Host " [3/3] MESH — Verifying mesh membership" -ForegroundColor Cyan

                if (-not $DryRun) {
                    Start-Sleep -Seconds 3
                    try {
                        $meshStatus = Invoke-RestMethod -Uri "http://${target}:8125/mesh/status" -TimeoutSec 10 -ErrorAction Stop
                        $nodeResult.MeshStatus = $meshStatus.status ?? "connected"
                        Write-Host " Mesh: $($nodeResult.MeshStatus)" -ForegroundColor Green
                    }
                    catch {
                        $nodeResult.MeshStatus = "unreachable"
                        Write-Host " Mesh: Node will auto-join when Core is reachable" -ForegroundColor Yellow
                    }
                }
                else {
                    $nodeResult.MeshStatus = 'DryRun'
                }

                $nodeResult.Status = if ($nodeResult.DeployStatus -eq 'Success') { 'Success' }
                                     elseif ($nodeResult.DeployStatus -eq 'DryRun') { 'DryRun' }
                                     else { 'PartialSuccess' }
            }
            catch {
                $nodeResult.Status = 'Failed'
                $nodeResult.Error = $_.Exception.Message
                Write-Host " ✗ FAILED: $($_.Exception.Message)" -ForegroundColor Red
            }

            $nodeResult.Duration = (Get-Date) - $nodeStart
            $results += $nodeResult
        }
    }

    end {
        # Summary
        Write-Host ""
        Write-Host " ╔═══════════════════════════════════════════════════════╗" -ForegroundColor Green
        Write-Host " ║ Elysium Deployment Summary ║" -ForegroundColor Green
        Write-Host " ╚═══════════════════════════════════════════════════════╝" -ForegroundColor Green

        foreach ($r in $results) {
            $statusIcon = switch ($r.Status) {
                'Success'        { "✓" }
                'PartialSuccess' { "⚠" }
                'DryRun'         { "→" }
                default          { "✗" }
            }
            $statusColor = switch ($r.Status) {
                'Success'        { "Green" }
                'PartialSuccess' { "Yellow" }
                'DryRun'         { "DarkGray" }
                default          { "Red" }
            }
            Write-Host " $statusIcon $($r.ComputerName) — $($r.Status) ($([math]::Round($r.Duration.TotalSeconds, 1))s)" -ForegroundColor $statusColor
            Write-Host " Bootstrap: $($r.BootstrapStatus) | Deploy: $($r.DeployStatus) | Mesh: $($r.MeshStatus)" -ForegroundColor DarkGray
        }

        $totalDuration = (Get-Date) - $startTime
        Write-Host ""
        Write-Host " Total: $($results.Count) nodes in $([math]::Round($totalDuration.TotalSeconds, 1))s" -ForegroundColor White
        Write-Host " Mesh Token: $($MeshToken.Substring(0, [Math]::Min(16, $MeshToken.Length)))..." -ForegroundColor DarkGray
        Write-Host ""

        # Start watchdog if requested
        if ($StartWatchdog -and -not $DryRun) {
            Write-Host " Starting failover watchdog..." -ForegroundColor Cyan
            $watchdogScript = Join-Path $projectRoot "AitherZero" "library" "automation-scripts" "31-remote" "3102_Watch-MeshFailover.ps1"
            if (Test-Path $watchdogScript) {
                # Run as background job
                Start-Job -ScriptBlock {
                    param($script, $coreUrl)
                    & $script -CoreUrl $coreUrl -Continuous -EnableFailback
                } -ArgumentList $watchdogScript, $CoreUrl | Out-Null
                Write-Host " Watchdog started as background job" -ForegroundColor Green
            }
        }

        if ($PassThru) {
            if ($results.Count -eq 1) { return $results[0] }
            return $results
        }
    }
}