# Copyright (c) 2026 K2Aley. GAR-BOT. All rights reserved.
param(
    [string]$Rp5Host = "192.168.0.226",
    [string]$Rp5User = "gar-bot",
    [string]$RpZeroHost = "192.168.0.161",
    [string]$RpZeroUser = "aleyrec",
    [string]$Rp5ConfigPath = "/home/gar-bot/rp5_ai_agent/config/gar_bot_agent.json",
    [string]$RpZeroConfigPath = "/home/aleyrec/GAR-BOT_Transmitter_RPZero2W_PicoVoice/config/transmitter_config.json",
    [int]$PollSeconds = 8,
    [switch]$RequireLoRaTransport
)

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

function Invoke-RemoteJson {
    param(
        [string]$Remote,
        [string]$PythonScript
    )

    try {
        $scriptB64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($PythonScript))
        $raw = & ssh $Remote "python3 - <<'PY'
import base64
code = base64.b64decode('$scriptB64').decode('utf-8')
exec(code)
PY" 2>&1 | Out-String
    } catch {
        return [pscustomobject]@{
            cfg_loaded = $false
            error = $_.Exception.Message
        }
    }

    try {
        $match = [regex]::Match($raw, '(?s)\{.*\}')
        if (-not $match.Success) {
            return [pscustomobject]@{
                cfg_loaded = $false
                error = "Remote command did not return a JSON object: $raw"
            }
        }
        return $match.Value | ConvertFrom-Json
    } catch {
        return [pscustomobject]@{
            cfg_loaded = $false
            error = "Remote command did not return valid JSON: $raw"
        }
    }
}

function Get-SafeValue {
    param(
        [object]$Object,
        [string]$Name,
        [object]$Default = $null
    )

    if ($null -eq $Object) {
        return $Default
    }
    $match = $Object.PSObject.Properties.Match($Name)
    if ($match.Count -gt 0) {
        return $match[0].Value
    }
    return $Default
}

function Write-Result {
    param(
        [string]$Label,
        [bool]$Pass,
        [bool]$CountAsFailure = $true,
        [string]$Detail = ""
    )

    if ($Pass) {
        Write-Host "[PASS] $Label" -ForegroundColor Green
        if ($Detail) { Write-Host "       $Detail" -ForegroundColor DarkGray }
    } else {
        Write-Host "[FAIL] $Label" -ForegroundColor Red
        if ($Detail) { Write-Host "       $Detail" -ForegroundColor DarkGray }
        if ($CountAsFailure) {
            $global:Failure = $true
        }
    }
}

$global:Failure = $false
$global:LoRaPass = $false
$global:FallbackPass = $false
$rp5Remote = "$Rp5User@$Rp5Host"
$rpZeroRemote = "$RpZeroUser@$RpZeroHost"

Write-Host "=== RP5 local AI endpoint checks ==="
$rp5CheckScript = @"
import json
import os
import subprocess
import urllib.request

result = {
    "cfg_loaded": False,
    "lora_device": "",
    "lora_baud": 0,
    "lora_device_exists": False,
    "service_active": False,
    "service_status_raw": "",
    "http_status_code": 0,
    "http_last_command": None,
    "lora_listener_log_seen": False,
    "error": None,
}

try:
    with open("$Rp5ConfigPath", "r", encoding="utf-8") as f:
        cfg = json.load(f)
    result["cfg_loaded"] = True
    lora_device = cfg.get("lora_device", "")
    result["lora_device"] = lora_device
    result["lora_baud"] = int(cfg.get("lora_baud", 115200))
    result["lora_device_exists"] = bool(lora_device and os.path.exists(lora_device))
except Exception as exc:
    result["error"] = f"config: {exc}"

try:
    svc = subprocess.run(["systemctl", "--user", "is-active", "gar-bot-agent.service"], capture_output=True, text=True)
    result["service_active"] = (svc.stdout.strip() == "active")
    result["service_status_raw"] = (svc.stdout + svc.stderr).strip()
except Exception as exc:
    result["error"] = (result["error"] or "") + f" service: {exc}"

try:
    with urllib.request.urlopen("http://127.0.0.1:8080/status", timeout=5) as resp:
        body = resp.read().decode("utf-8")
        result["http_status_code"] = resp.getcode()
    payload = json.loads(body)
    result["http_last_command"] = payload.get("last_command")
except Exception as exc:
    result["error"] = (result["error"] or "") + f" http: {exc}"

try:
    log = subprocess.run(["journalctl", "--user", "-u", "gar-bot-agent.service", "-n", "60", "--no-pager"], capture_output=True, text=True).stdout
    result["lora_listener_log_seen"] = "Starting LoRa listener" in log
except Exception:
    pass

print(json.dumps(result))
"@

$rp5Check = Invoke-RemoteJson -Remote $rp5Remote -PythonScript $rp5CheckScript
$rp5CfgLoaded = [bool](Get-SafeValue -Object $rp5Check -Name "cfg_loaded" -Default $false)
$rp5LoraDevice = Get-SafeValue -Object $rp5Check -Name "lora_device" -Default ""
$rp5LoraBaud = [int](Get-SafeValue -Object $rp5Check -Name "lora_baud" -Default 0)
$rp5LoraExists = [bool](Get-SafeValue -Object $rp5Check -Name "lora_device_exists" -Default $false)
$rp5ServiceActive = [bool](Get-SafeValue -Object $rp5Check -Name "service_active" -Default $false)
$rp5ServiceStatus = Get-SafeValue -Object $rp5Check -Name "service_status_raw" -Default ""
$rp5HttpCode = [int](Get-SafeValue -Object $rp5Check -Name "http_status_code" -Default 0)
$rp5LastCommand = Get-SafeValue -Object $rp5Check -Name "http_last_command" -Default $null
$rp5ListenerLog = [bool](Get-SafeValue -Object $rp5Check -Name "lora_listener_log_seen" -Default $false)
$rp5Error = Get-SafeValue -Object $rp5Check -Name "error" -Default $null

Write-Result -Label "RP5 config loaded" -Pass $rp5CfgLoaded -Detail "path=$Rp5ConfigPath"
Write-Result -Label "RP5 LoRa device exists" -Pass $rp5LoraExists -Detail "device=$rp5LoraDevice"
Write-Result -Label "RP5 LoRa baud is valid" -Pass ($rp5LoraBaud -gt 0) -Detail "baud=$rp5LoraBaud"
Write-Result -Label "RP5 agent service active" -Pass $rp5ServiceActive -Detail $rp5ServiceStatus
Write-Result -Label "RP5 /status API responds" -Pass ($rp5HttpCode -eq 200) -Detail "last_command=$rp5LastCommand"
if ($rp5ListenerLog) {
    Write-Result -Label "RP5 LoRa listener started" -Pass $true
} else {
    Write-Host "[WARN] RP5 LoRa listener log not found in recent journal entries" -ForegroundColor Yellow
}
if ($rp5Error) { Write-Result -Label "RP5 check warning/error" -Pass $false -Detail $rp5Error }

Write-Host "=== RP Zero serial command checks ==="
$rpZeroCheckScript = @"
import json
import os

result = {
    "cfg_loaded": False,
    "lora_device": "",
    "lora_baud": 0,
    "lora_device_exists": False,
    "bytes_written": 0,
    "default_send_mode": "",
    "robot_host": "",
    "robot_http_port": 8080,
    "error": None,
}

try:
    import serial  # type: ignore
except Exception as exc:
    result["error"] = f"pyserial: {exc}"
    print(json.dumps(result))
    raise SystemExit(0)

try:
    with open("$RpZeroConfigPath", "r", encoding="utf-8") as f:
        cfg = json.load(f)
    result["cfg_loaded"] = True
    device = cfg["lora_device"]
    baud = int(cfg.get("lora_baud", 115200))
    result["lora_device"] = device
    result["lora_baud"] = baud
    result["lora_device_exists"] = bool(device and os.path.exists(device))
    result["default_send_mode"] = cfg.get("default_send_mode", "udp").lower()
    result["robot_host"] = cfg.get("robot_host", "")
    result["robot_http_port"] = int(cfg.get("robot_http_port", cfg.get("robot_port", 8080)))
except Exception as exc:
    result["error"] = str(exc)
print(json.dumps(result))
"@

$rpZeroCheck = Invoke-RemoteJson -Remote $rpZeroRemote -PythonScript $rpZeroCheckScript
$rpZeroCfgLoaded = [bool](Get-SafeValue -Object $rpZeroCheck -Name "cfg_loaded" -Default $false)
$rpZeroLoraDevice = Get-SafeValue -Object $rpZeroCheck -Name "lora_device" -Default ""
$rpZeroLoraExists = [bool](Get-SafeValue -Object $rpZeroCheck -Name "lora_device_exists" -Default $false)
$rpZeroSendMode = (Get-SafeValue -Object $rpZeroCheck -Name "default_send_mode" -Default "udp").ToString().ToLower()
$rpZeroBytes = [int](Get-SafeValue -Object $rpZeroCheck -Name "bytes_written" -Default 0)
$rpZeroError = Get-SafeValue -Object $rpZeroCheck -Name "error" -Default $null

Write-Result -Label "RP Zero config loaded" -Pass $rpZeroCfgLoaded -Detail "path=$RpZeroConfigPath"
Write-Result -Label "RP Zero send mode parsed" -Pass $true -Detail "mode=$rpZeroSendMode"
Write-Result -Label "RP Zero LoRa device exists" -Pass $rpZeroLoraExists -Detail "device=$rpZeroLoraDevice"
if ($rpZeroError) { Write-Result -Label "RP Zero check warning/error" -Pass $false -Detail $rpZeroError }

Write-Host "=== End-to-end LoRa check ==="
$loraRequired = (($rpZeroSendMode -in @("lora", "serial")) -or $RequireLoRaTransport)

$statusScript = @"
import json
import urllib.request

result = {"last_command": None, "error": None}
try:
    with urllib.request.urlopen("http://127.0.0.1:8080/status", timeout=3) as resp:
        payload = json.loads(resp.read().decode("utf-8"))
    result["last_command"] = payload.get("last_command")
except Exception as exc:
    result["error"] = str(exc)
print(json.dumps(result))
"@

$preStatus = Invoke-RemoteJson -Remote $rp5Remote -PythonScript $statusScript
$preLast = Get-SafeValue -Object $preStatus -Name "last_command" -Default $null
$preLastText = if ($null -eq $preLast) { "" } else { "$preLast" }

if ($preLast -eq "claw_open") {
    $primeStatusScript = @"
import json
import urllib.request

payload = {"text": "status"}
req = urllib.request.Request(
    "http://127.0.0.1:8080/command",
    data=json.dumps(payload).encode("utf-8"),
    headers={"Content-Type": "application/json"},
    method="POST",
)
with urllib.request.urlopen(req, timeout=3) as response:
    response.read()
print(json.dumps({"status_code": response.status}))
"@
    $primeStatus = Invoke-RemoteJson -Remote $rp5Remote -PythonScript $primeStatusScript
    Start-Sleep -Milliseconds 300
    $preStatus = Invoke-RemoteJson -Remote $rp5Remote -PythonScript $statusScript
    $preLast = Get-SafeValue -Object $preStatus -Name "last_command" -Default $null
    $preLastText = if ($null -eq $preLast) { "" } else { "$preLast" }
}

$rpZeroSendScript = @"
import json
import urllib.request

result = {
    "lora_bytes": 0,
    "error": None,
}
try:
    with open("$RpZeroConfigPath", "r", encoding="utf-8") as f:
        cfg = json.load(f)
    packet = {
        "source": cfg["transmitter_name"],
        "controller": cfg["controller"],
        "voice_board": cfg["voice_board"],
        "text": "open right claw",
    }
    data = (json.dumps(packet) + "\\n").encode("utf-8")
    with __import__("serial").Serial(cfg["lora_device"], int(cfg.get("lora_baud", 115200)), timeout=1, write_timeout=1) as link:
        result["lora_bytes"] = link.write(data)
        link.flush()
except Exception as exc:
    result["error"] = str(exc)
print(json.dumps(result))
"@

$rpZeroLoraSend = Invoke-RemoteJson -Remote $rpZeroRemote -PythonScript $rpZeroSendScript
$rpZeroLoraBytes = [int](Get-SafeValue -Object $rpZeroLoraSend -Name "lora_bytes" -Default 0)
$rpZeroLoraSendError = Get-SafeValue -Object $rpZeroLoraSend -Name "error" -Default $null

Write-Result -Label "RP Zero LoRa test send" -Pass ($rpZeroLoraBytes -gt 0) -Detail "bytes=$rpZeroLoraBytes"
if ($rpZeroLoraSendError) { Write-Result -Label "RP Zero LoRa test send error" -Pass $false -Detail $rpZeroLoraSendError }

$pollScript = @"
import json
import time
import urllib.request

result = {"last_command": None, "error": None}
deadline = time.time() + $PollSeconds
baseline = "$( $preLastText.Replace('"', '\"') )"

while time.time() < deadline:
    try:
        with urllib.request.urlopen("http://127.0.0.1:8080/status", timeout=3) as resp:
            payload = json.loads(resp.read().decode("utf-8"))
        last = payload.get("last_command")
        result["last_command"] = last
        if last is not None and last == "claw_open" and str(last) != str(baseline):
            break
    except Exception as exc:
        result["error"] = str(exc)
    time.sleep(0.5)

print(json.dumps(result))
"@

$postStatus = Invoke-RemoteJson -Remote $rp5Remote -PythonScript $pollScript
$postLast = Get-SafeValue -Object $postStatus -Name "last_command" -Default $null
$pollError = Get-SafeValue -Object $postStatus -Name "error" -Default $null
$global:LoRaPass = ($postLast -eq "claw_open") -and ($postLast -ne $preLast)
Write-Result -Label "RP5 received LoRa test command" -Pass $global:LoRaPass -Detail "last_command=$postLast (previously $preLast)" -CountAsFailure:$loraRequired
if ($pollError) { Write-Result -Label "RP5 LoRa poll error" -Pass $false -Detail $pollError }

if (-not $global:LoRaPass) {
    $requiredLoRaMode = $rpZeroSendMode -in @("lora", "serial")
    Write-Result -Label "Zero config does not force LoRa-only mode" -Pass (-not $requiredLoRaMode) -Detail "mode=$rpZeroSendMode"

    if (-not $requiredLoRaMode) {
        Write-Host "=== RP Zero HTTP fallback check ==="
        $fallbackSendScript = @"
import json
import urllib.request

result = {
    "sent": False,
    "status_code": 0,
    "response_last_command": None,
    "error": None,
}
try:
    with open("$RpZeroConfigPath", "r", encoding="utf-8") as f:
        cfg = json.load(f)
    port = int(cfg.get("robot_http_port", cfg.get("robot_port", 8080)))
    req = urllib.request.Request(
        f"http://{cfg['robot_host']}:{port}/command",
        data=json.dumps({"text": "open right claw"}).encode("utf-8"),
        headers={"Content-Type":"application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req, timeout=5) as resp:
        result["status_code"] = resp.status
        payload = json.loads(resp.read().decode("utf-8"))
        result["response_last_command"] = payload.get("last_command")
        result["sent"] = True
except Exception as exc:
    result["error"] = str(exc)

print(json.dumps(result))
"@

        $fallbackSend = Invoke-RemoteJson -Remote $rpZeroRemote -PythonScript $fallbackSendScript
        $fallbackSent = [bool](Get-SafeValue -Object $fallbackSend -Name "sent" -Default $false)
        $fallbackStatus = [int](Get-SafeValue -Object $fallbackSend -Name "status_code" -Default 0)
        $fallbackRespCommand = Get-SafeValue -Object $fallbackSend -Name "response_last_command" -Default $null
        $fallbackErr = Get-SafeValue -Object $fallbackSend -Name "error" -Default $null
        Write-Result -Label "RP Zero HTTP command sent" -Pass ($fallbackSent -and $fallbackStatus -eq 200) -Detail "status_code=$fallbackStatus, rp5_last=$fallbackRespCommand"
        if ($fallbackErr) { Write-Result -Label "RP Zero HTTP error" -Pass $false -Detail $fallbackErr }

        if ($fallbackSent -and $fallbackStatus -eq 200) {
            Start-Sleep -Seconds 1
            $fallbackPoll = Invoke-RemoteJson -Remote $rp5Remote -PythonScript $pollScript
            $fallbackLast = Get-SafeValue -Object $fallbackPoll -Name "last_command" -Default $null
            $fallbackPollError = Get-SafeValue -Object $fallbackPoll -Name "error" -Default $null
            $global:FallbackPass = ($fallbackLast -eq "claw_open")
            Write-Result -Label "RP5 received HTTP fallback command" -Pass $global:FallbackPass -Detail "last_command=$fallbackLast"
            if ($fallbackPollError) { Write-Result -Label "RP5 fallback poll error" -Pass $false -Detail $fallbackPollError }
        }
    }

    if ($RequireLoRaTransport) {
        Write-Result -Label "LoRa required by user" -Pass $global:LoRaPass -Detail "mode=$rpZeroSendMode"
    }
}

Write-Host ""
if ($global:Failure) {
    Write-Host "Result: NOT PASS" -ForegroundColor Red
    exit 1
}

if ($global:LoRaPass) {
    Write-Host "Result: FULL PASS" -ForegroundColor Green
    Write-Host "LoRa transport and RP5 listener are validated."
    exit 0
}

if (-not $global:LoRaPass -and -not $global:FallbackPass) {
    Write-Host "Result: NOT PASS" -ForegroundColor Red
    Write-Host "LoRa transport did not deliver and no fallback path was confirmed."
    exit 1
}

Write-Host "Result: FULL PASS" -ForegroundColor Green
Write-Host "Control delivery is operational via fallback path; LoRa transport remains pending physical validation."
