"""ARIS Guardian — Hardware health monitoring with predictive failure prevention."""

import json
import asyncio
import socket
from typing import Any


async def _run_ssh(host: str, cmd: str, timeout: int = 5) -> str:
    """Run command via SSH with timeout."""
    try:
        proc = await asyncio.create_subprocess_exec(
            "ssh", "-o", "ConnectTimeout=3", "-o", "StrictHostKeyChecking=no",
            host, cmd,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=timeout)
        return stdout.decode().strip()
    except Exception as e:
        return f"ERROR: {e}"


async def _run_local(cmd: str) -> str:
    """Run local command."""
    try:
        proc = await asyncio.create_subprocess_shell(
            cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
        )
        stdout, _ = await proc.communicate()
        return stdout.decode().strip()
    except Exception as e:
        return f"ERROR: {e}"


def _parse_mem(raw: str) -> dict[str, Any]:
    """Parse free -m output."""
    try:
        for line in raw.strip().split("\n"):
            if line.startswith("Mem:"):
                parts = line.split()
                total = int(parts[1])
                used = int(parts[2])
                available = int(parts[6]) if len(parts) > 6 else total - used
                return {
                    "total_mb": total, "used_mb": used,
                    "available_mb": available,
                    "percent": round(used / total * 100, 1) if total > 0 else 0,
                }
    except Exception:
        pass
    return {"total_mb": 0, "used_mb": 0, "available_mb": 0, "percent": 0}


async def check_node(host: str, name: str, has_gpu: bool = False) -> dict[str, Any]:
    """Check health of a single node."""
    # Detect if target is this machine (avoid SSH to self)
    _hostname = socket.gethostname().lower()
    is_local = host.lower() in ("", "localhost", "local", _hostname)

    # For SSH aliases like "w2": try SSH first, fall back to local if it fails
    async def _smart_run(cmd: str) -> str:
        if is_local:
            return await _run_local(cmd)
        result = await _run_ssh(host, cmd)
        if not result or result.startswith("ERROR"):
            # SSH failed — might be running on the same machine with SSH alias
            return await _run_local(cmd)
        return result

    run = _smart_run

    mem_raw = await run("free -m | grep -E 'Mem|Swap'")
    cpu_raw = await run("nproc")
    load_raw = await run("cat /proc/loadavg")
    disk_raw = await run("df -h / | tail -1")
    temp_raw = await run("cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0")

    gpu_info = {}
    if has_gpu:
        gpu_raw = await run(
            "nvidia-smi --query-gpu=memory.used,memory.total,temperature.gpu,"
            "utilization.gpu --format=csv,noheader,nounits 2>/dev/null || echo ''"
        )
        if gpu_raw and "ERROR" not in gpu_raw:
            try:
                parts = gpu_raw.split(",")
                if len(parts) >= 4:
                    gpu_info = {
                        "used_mb": int(parts[0].strip()),
                        "total_mb": int(parts[1].strip()),
                        "temp_c": int(parts[2].strip()),
                        "utilization_pct": int(parts[3].strip()),
                    }
            except (ValueError, IndexError):
                pass

    mem = _parse_mem(mem_raw)
    load_parts = load_raw.split() if "ERROR" not in load_raw else []
    load_1m = float(load_parts[0]) if load_parts else 0
    cpu_count = int(cpu_raw) if cpu_raw.isdigit() else 1

    disk_parts = disk_raw.split() if "ERROR" not in disk_raw else []
    disk_used_pct = int(disk_parts[4].replace("%", "")) if len(disk_parts) >= 5 else 0

    try:
        temp_val = int(temp_raw)
        temp_c = temp_val / 1000 if temp_val > 1000 else temp_val
    except (ValueError, TypeError):
        temp_c = 0

    alerts: list[str] = []
    health = "healthy"

    if mem["percent"] > 90:
        alerts.append(f"RAM critical: {mem['percent']}%")
        health = "critical"
    elif mem["percent"] > 80:
        alerts.append(f"RAM high: {mem['percent']}%")
        health = "warning"

    if load_1m > cpu_count * 2:
        alerts.append(f"CPU overloaded: load {load_1m} ({cpu_count} cores)")
        health = "critical"
    elif load_1m > cpu_count:
        alerts.append(f"CPU busy: load {load_1m}")
        if health == "healthy":
            health = "warning"

    if disk_used_pct > 90:
        alerts.append(f"Disk critical: {disk_used_pct}%")
        health = "critical"
    elif disk_used_pct > 80:
        alerts.append(f"Disk high: {disk_used_pct}%")
        if health == "healthy":
            health = "warning"

    if temp_c > 85:
        alerts.append(f"Temperature critical: {temp_c}C")
        health = "critical"

    if gpu_info and gpu_info.get("temp_c", 0) > 85:
        alerts.append(f"GPU temp critical: {gpu_info['temp_c']}C")
        health = "critical"

    return {
        "name": name, "host": host, "status": "online", "health": health,
        "ram": mem, "cpu_cores": cpu_count, "load_1m": load_1m,
        "disk_used_pct": disk_used_pct, "temp_c": temp_c,
        "gpu": gpu_info or None, "alerts": alerts,
        "can_accept_work": health != "critical" and mem["percent"] < 85,
    }


async def guardian_scan(nodes: list[dict[str, Any]]) -> dict[str, Any]:
    """Scan all cluster nodes and return health report."""
    results = []
    for node in nodes:
        try:
            result = await check_node(
                host=node.get("ssh", node.get("host", "")),
                name=node.get("name", "unknown"),
                has_gpu=node.get("gpu", False),
            )
            results.append(result)
        except Exception as e:
            results.append({
                "name": node.get("name", "unknown"),
                "status": "offline", "health": "critical",
                "error": str(e),
            })

    healths = [r["health"] for r in results if r.get("health")]
    overall = "critical" if "critical" in healths else "warning" if "warning" in healths else "healthy"

    all_alerts = []
    for r in results:
        for a in r.get("alerts", []):
            all_alerts.append(f"[{r['name']}] {a}")

    return {
        "overall_health": overall,
        "nodes": results,
        "total_nodes": len(results),
        "online_nodes": sum(1 for r in results if r.get("status") == "online"),
        "available_for_work": [r["name"] for r in results if r.get("can_accept_work")],
        "alerts": all_alerts,
    }
