src/public/Docker/Test-AitherContainer.ps1

#Requires -Version 7.0

<#
.SYNOPSIS
    Run health checks on AitherOS Docker infrastructure.

.DESCRIPTION
    Comprehensive diagnostic tool that checks:
    - Docker Engine health
    - Compose file validity
    - Container health status
    - Orphaned containers
    - Port conflicts
    - Resource usage (CPU, memory)
    - Service endpoint reachability
    - Volume mount status

.PARAMETER Name
    Check a specific service only.

.PARAMETER Deep
    Run deep checks including HTTP health endpoint probing for every running service.

.PARAMETER Quick
    Quick mode: just container status + orphan detection.

.PARAMETER Fix
    Attempt to auto-fix common issues (restart unhealthy, repair orphans).

.EXAMPLE
    Test-AitherContainer
    # Standard health check of all services

.EXAMPLE
    Test-AitherContainer -Deep
    # Deep check including HTTP endpoint probing

.EXAMPLE
    Test-AitherContainer -Name moltbook
    # Check specific service

.EXAMPLE
    Test-AitherContainer -Fix
    # Check and auto-fix common issues

.NOTES
    Part of the AitherZero Docker management module.
    Copyright © 2025 Aitherium Corporation
#>

function Test-AitherContainer {
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Position = 0)]
        [string]$Name,

        [Parameter()]
        [switch]$Deep,

        [Parameter()]
        [switch]$Quick,

        [Parameter()]
        [switch]$Fix
    )

    $results = [ordered]@{
        Timestamp     = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
        Healthy       = $false
        DockerOk      = $false
        ComposeOk     = $false
        Running       = 0
        Total         = 0
        Orphaned      = 0
        Containers    = @{ Total = 0; Running = 0; Stopped = 0; Unhealthy = 0; Orphaned = 0 }
        Issues        = @()
        FixesApplied  = @()
    }

    Write-Host ''
    Write-Host ' AitherOS Docker Health Check' -ForegroundColor Cyan
    Write-Host ' ════════════════════════════' -ForegroundColor DarkCyan
    Write-Host ''

    # ── Docker Engine ──
    Write-Host '[Docker Engine]' -ForegroundColor White
    $dockerVersion = docker version --format '{{.Server.Version}}' 2>$null
    if ($dockerVersion) {
        Write-Host " Engine: v$dockerVersion" -ForegroundColor Green
        $results.DockerOk = $true

        $dockerInfo = docker info --format '{{.ContainersRunning}} running, {{.ContainersStopped}} stopped, {{.Images}} images' 2>$null
        Write-Host " Status: $dockerInfo" -ForegroundColor DarkGray
    }
    else {
        Write-Host ' Engine: NOT RUNNING' -ForegroundColor Red
        $results.Issues += 'Docker Engine is not running'
        return [PSCustomObject]$results
    }

    # ── Compose File ──
    Write-Host '[Compose File]' -ForegroundColor White
    $cfg = Get-AitherComposeConfig -Profile 'all'
    if ($cfg) {
        $validateArgs = @('compose') + $cfg.BaseArgs + @('config', '--quiet')
        & docker @validateArgs 2>$null
        if ($LASTEXITCODE -eq 0) {
            Write-Host " File: Valid ($($cfg.ComposeFile))" -ForegroundColor Green
            $results.ComposeOk = $true

            $svcCount = (docker compose @($cfg.BaseArgs) config --services 2>$null | Measure-Object).Count
            Write-Host " Services defined: $svcCount" -ForegroundColor DarkGray
        }
        else {
            Write-Host ' File: INVALID' -ForegroundColor Red
            $results.Issues += 'Compose file has syntax errors'
        }
    }

    # ── Container Status ──
    Write-Host '[Containers]' -ForegroundColor White
    $containers = Get-AitherContainer -Raw

    if ($Name) {
        $containers = $containers | Where-Object { $_.Service -like $Name }
    }

    $running = ($containers | Where-Object { $_.Status -eq 'running' }).Count
    $stopped = ($containers | Where-Object { $_.Status -in @('exited', 'created') }).Count
    $unhealthy = ($containers | Where-Object { $_.Health -in @('unhealthy', 'restarting') }).Count
    $orphaned = ($containers | Where-Object { $_.Orphaned }).Count
    $total = $containers.Count

    $results.Containers.Total = $total
    $results.Containers.Running = $running
    $results.Containers.Stopped = $stopped
    $results.Containers.Unhealthy = $unhealthy
    $results.Containers.Orphaned = $orphaned
    # Top-level convenience properties
    $results.Running = $running
    $results.Total = $total
    $results.Orphaned = $orphaned
    $results.Healthy = ($unhealthy -eq 0 -and $orphaned -eq 0 -and $running -gt 0)

    Write-Host " Total: $total | Running: $running | Stopped: $stopped | Unhealthy: $unhealthy | Orphaned: $orphaned" -ForegroundColor $(
        if ($unhealthy -gt 0 -or $orphaned -gt 0) { 'Yellow' } else { 'Green' }
    )

    # List unhealthy
    $unhealthyContainers = $containers | Where-Object { $_.Health -in @('unhealthy', 'restarting') }
    foreach ($uc in $unhealthyContainers) {
        Write-Host " !! $($uc.Service): $($uc.Health)" -ForegroundColor Red
        $results.Issues += "Service $($uc.Service) is $($uc.Health)"
    }

    # List orphaned
    $orphanedContainers = $containers | Where-Object { $_.Orphaned }
    foreach ($oc in $orphanedContainers) {
        Write-Host " !! $($oc.ContainerName): ORPHANED" -ForegroundColor Red
        $results.Issues += "Container $($oc.ContainerName) is orphaned (hash-prefixed)"
    }

    if ($Quick) {
        Write-Host ''
        Write-Host " Issues found: $($results.Issues.Count)" -ForegroundColor $(if ($results.Issues.Count -gt 0) { 'Yellow' } else { 'Green' })
        return [PSCustomObject]$results
    }

    # ── Port Conflicts ──
    Write-Host '[Port Conflicts]' -ForegroundColor White
    $portMap = @{}
    foreach ($c in ($containers | Where-Object { $_.Status -eq 'running' -and $_.Ports })) {
        if ($c.Ports -match '(\d+)->') {
            $port = $Matches[1]
            if ($portMap.ContainsKey($port)) {
                Write-Host " !! Port ${port}: $($c.Service) AND $($portMap[$port])" -ForegroundColor Red
                $results.Issues += "Port conflict on ${port} between $($c.Service) and $($portMap[$port])"
            }
            else {
                $portMap[$port] = $c.Service
            }
        }
    }
    if ($results.Issues.Count -eq 0 -or -not ($results.Issues | Where-Object { $_ -match 'Port conflict' })) {
        Write-Host ' No port conflicts' -ForegroundColor Green
    }

    # ── Deep Health Probing ──
    if ($Deep) {
        Write-Host '[Service Endpoints]' -ForegroundColor White
        $runningContainers = $containers | Where-Object { $_.Status -eq 'running' -and $_.Ports -match '(\d+)->' }

        foreach ($c in $runningContainers) {
            if ($c.Ports -match '(\d+)->') {
                $port = $Matches[1]
                $url = "http://localhost:$port/health"
                try {
                    $null = Invoke-RestMethod -Uri $url -Method Get -TimeoutSec 3 -ErrorAction Stop
                    Write-Host " $($c.Service.PadRight(25)) :${port} OK" -ForegroundColor Green
                }
                catch {
                    Write-Host " $($c.Service.PadRight(25)) :${port} UNREACHABLE" -ForegroundColor Yellow
                }
            }
        }
    }

    # ── Auto-Fix ──
    if ($Fix) {
        Write-Host '[Auto-Fix]' -ForegroundColor White

        # Fix orphaned containers
        if ($orphaned -gt 0) {
            Write-Host " Repairing $orphaned orphaned containers..." -ForegroundColor Cyan
            Repair-AitherContainer -Repair -Force
            $results.FixesApplied += "Repaired $orphaned orphaned containers"
        }

        # Restart unhealthy containers
        foreach ($uc in $unhealthyContainers) {
            Write-Host " Restarting unhealthy: $($uc.Service)..." -ForegroundColor Cyan
            Restart-AitherContainer -Name $uc.Service -ViaDocker
            $results.FixesApplied += "Restarted $($uc.Service)"
        }

        if ($results.FixesApplied.Count -gt 0) {
            Write-Host " Applied $($results.FixesApplied.Count) fixes" -ForegroundColor Green
        }
        else {
            Write-Host ' Nothing to fix' -ForegroundColor Green
        }
    }

    # ── Summary ──
    Write-Host ''
    $issueCount = $results.Issues.Count
    if ($issueCount -eq 0) {
        Write-Host ' All checks passed!' -ForegroundColor Green
    }
    else {
        Write-Host " $issueCount issue(s) found." -ForegroundColor Yellow
        if (-not $Fix) {
            Write-Host ' Run with -Fix to auto-repair.' -ForegroundColor DarkGray
        }
    }
    Write-Host ''

    return [PSCustomObject]$results
}