"""Stack health probes — VMs 112, 114, 122, 123, 130 (Spec 033 / INFRA CODE)."""

from __future__ import annotations

import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timezone
from typing import Any, Callable

import httpx

VM112_IP = os.getenv("VM112_IP", "10.10.10.112")
VM112_API = os.getenv("VM112_API_URL", f"http://{VM112_IP}:8090").rstrip("/")
DESK_PUBLIC = os.getenv("DESK_PUBLIC_URL", "https://desk.ligbox.com.br").rstrip("/")
API_PUBLIC = os.getenv("API_PUBLIC_URL", "https://api.ops.ligbox.com.br").rstrip("/")
VM114_IP = os.getenv("VM114_IP", "10.10.10.114")
VM123_IP = os.getenv("VM123_IP", "10.10.10.123")
VM130_IP = os.getenv("VM130_IP", "10.10.10.130")
OPENPANEL_BRIDGE = os.getenv("OPENPANEL_BRIDGE_URL", f"http://{VM123_IP}:18087").rstrip("/")
OPENPANEL_BRIDGE_TOKEN = os.getenv("OPENPANEL_BRIDGE_TOKEN", "")
TRAEFIK_API = os.getenv("TRAEFIK_API_URL", f"http://{VM114_IP}:8080")

ProbeFn = Callable[[], dict[str, Any]]


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


def _normalize_expect_status(expect_status: int | tuple[int, ...] | list[int]) -> tuple[int, ...]:
    """Garante tupla de códigos HTTP — evita bug Python `(200)` vs `(200,)`.

    Ver Spec 033 § Incidente probe `expect_status` (2026-06-29).
    """
    if isinstance(expect_status, int):
        return (expect_status,)
    if isinstance(expect_status, list):
        return tuple(int(x) for x in expect_status)
    if isinstance(expect_status, tuple):
        if len(expect_status) == 1 and isinstance(expect_status[0], int):
            return expect_status
        return tuple(int(x) for x in expect_status)
    raise TypeError(f"expect_status inválido: {type(expect_status)!r}")


def _probe_http(
    *,
    url: str,
    timeout: float = 8.0,
    verify: bool = True,
    headers: dict[str, str] | None = None,
    expect_status: int | tuple[int, ...] | list[int] = (200,),
) -> dict[str, Any]:
    try:
        codes = _normalize_expect_status(expect_status)
        with httpx.Client(timeout=timeout, verify=verify, follow_redirects=True) as client:
            res = client.get(url, headers=headers or {})
        ok = res.status_code in codes
        return {
            "ok": ok,
            "status": "online" if ok else "check",
            "http_status": res.status_code,
            "detail": f"HTTP {res.status_code}",
        }
    except Exception as exc:
        return {"ok": False, "status": "down", "http_status": None, "detail": str(exc)}


def _probe_redis(redis_url: str) -> dict[str, Any]:
    try:
        import redis

        r = redis.from_url(redis_url)
        r.ping()
        return {"ok": True, "status": "online", "detail": "PING OK"}
    except Exception as exc:
        return {"ok": False, "status": "down", "detail": str(exc)}


def _probe_email_relay() -> dict[str, Any]:
    try:
        from app import email_relay

        return email_relay.probe_stack()
    except Exception as exc:
        return {"ok": False, "status": "down", "detail": str(exc)}


def build_stack_catalog() -> list[dict[str, Any]]:
    """Catálogo estático — apps, APIs e software do stack Ligbox."""
    redis_url = os.getenv("REDIS_URL", "redis://redis:6379/0")
    return [
        {
            "vm": "112",
            "vm_label": "VM112 · Wizard & Mail",
            "ip": "10.10.10.112",
            "services": [
                {
                    "id": "vm112-onboard-api",
                    "title": "Onboard Portal API",
                    "spec": "001",
                    "kind": "app",
                    "icon": "🌐",
                    "accent": "aqua",
                    "url": f"{VM112_API}/api/onboarding/health",
                    "probe": lambda: _probe_http(url=f"{VM112_API}/api/onboarding/health"),
                },
                {
                    "id": "vm112-onboard-ui",
                    "title": "Onboard Wizard UI",
                    "spec": "025",
                    "kind": "app",
                    "icon": "🧭",
                    "accent": "aqua",
                    "url": "https://onboard.ligbox.com.br",
                    "probe": lambda: _probe_http(
                        url="https://onboard.ligbox.com.br/",
                        expect_status=(200, 301, 302, 403),
                    ),
                },
                {
                    "id": "vm112-carbonio",
                    "title": "Carbonio EmailServer",
                    "spec": "022",
                    "kind": "sw",
                    "icon": "✉️",
                    "accent": "slate",
                    "url": f"https://{VM112_IP}:443",
                    "probe": lambda: _probe_http(
                        url=f"https://{VM112_IP}/",
                        verify=False,
                        expect_status=(200, 301, 302, 403, 404),
                    ),
                },
                {
                    "id": "vm112-domain-api",
                    "title": "Domain Admin API",
                    "spec": "017",
                    "kind": "api",
                    "icon": "🗂️",
                    "accent": "aqua",
                    "url": f"{VM112_API}/api/admin/domains",
                    "probe": lambda: _probe_http(
                        url=f"{VM112_API}/api/onboarding/health",
                        expect_status=(200,),
                    ),
                },
            ],
        },
        {
            "vm": "114",
            "vm_label": "CT114 · Edge Traefik",
            "ip": VM114_IP,
            "services": [
                {
                    "id": "vm114-traefik",
                    "title": "Traefik API",
                    "spec": "026",
                    "kind": "sw",
                    "icon": "🚦",
                    "accent": "slate",
                    "url": f"{TRAEFIK_API}/api/overview",
                    "probe": lambda: _probe_http(
                        url=f"{TRAEFIK_API}/api/overview",
                        expect_status=(200, 401, 403),
                    ),
                },
                {
                    "id": "vm114-desk-route",
                    "title": "Router Desk (WAN)",
                    "spec": "026",
                    "kind": "integration",
                    "icon": "🔗",
                    "accent": "teal",
                    "url": DESK_PUBLIC,
                    "probe": lambda: _probe_http(
                        url=f"{DESK_PUBLIC}/",
                        expect_status=(200, 301, 302),
                    ),
                },
                {
                    "id": "vm114-api-route",
                    "title": "Router API Ops (WAN)",
                    "spec": "027",
                    "kind": "integration",
                    "icon": "⚙️",
                    "accent": "teal",
                    "url": f"{API_PUBLIC}/health",
                    "probe": lambda: _probe_http(url=f"{API_PUBLIC}/health"),
                },
            ],
        },
        {
            "vm": "122",
            "vm_label": "VM122 · Ops Desk",
            "ip": "10.10.10.122",
            "services": [
                {
                    "id": "vm122-desk-api",
                    "title": "Desk API (FastAPI)",
                    "spec": "003",
                    "kind": "app",
                    "icon": "⚙️",
                    "accent": "teal",
                    "url": "http://127.0.0.1:8080/health",
                    "probe": lambda: _probe_http(url="http://127.0.0.1:8080/health"),
                },
                {
                    "id": "vm122-desk-ui",
                    "title": "Desk Frontend",
                    "spec": "033",
                    "kind": "app",
                    "icon": "🖥️",
                    "accent": "teal",
                    "url": "http://10.10.10.122:8091",
                    "probe": lambda: _probe_http(
                        url="http://10.10.10.122:8091/",
                        expect_status=(200, 301, 302),
                    ),
                },
                {
                    "id": "vm122-redis",
                    "title": "Redis",
                    "spec": "—",
                    "kind": "sw",
                    "icon": "🧠",
                    "accent": "slate",
                    "url": redis_url,
                    "probe": lambda: _probe_redis(redis_url),
                },
                {
                    "id": "vm122-webhook-soc",
                    "title": "Webhook SOC VM112",
                    "spec": "001",
                    "kind": "integration",
                    "icon": "📡",
                    "accent": "teal",
                    "url": "/api/v1/integrations/health",
                    "probe": lambda: {"ok": True, "status": "online", "detail": "via integrations/health"},
                },
                {
                    "id": "vm122-purge-auth",
                    "title": "API Purge Auth",
                    "spec": "032",
                    "kind": "api",
                    "icon": "🔐",
                    "accent": "rose",
                    "url": "/api/v1/infra/purge-auth-domains",
                    "probe": lambda: {"ok": True, "status": "online", "detail": "módulo local"},
                },
                {
                    "id": "vm122-email-relay",
                    "title": "Email Relay (Postfix)",
                    "spec": "004",
                    "kind": "sw",
                    "icon": "📨",
                    "accent": "slate",
                    "url": "/api/v1/infra/email-relay/status",
                    "probe": _probe_email_relay,
                },
            ],
        },
        {
            "vm": "123",
            "vm_label": "VM123 · Finance & Hosting",
            "ip": VM123_IP,
            "services": [
                {
                    "id": "vm123-foss",
                    "title": "FOSSBilling",
                    "spec": "024",
                    "kind": "app",
                    "icon": "💰",
                    "accent": "orange",
                    "url": f"http://{VM123_IP}:8092",
                    "probe": lambda: _probe_http(
                        url=f"http://{VM123_IP}:8092/",
                        expect_status=(200, 301, 302, 403),
                    ),
                },
                {
                    "id": "vm123-odoo",
                    "title": "Odoo 16",
                    "spec": "024",
                    "kind": "app",
                    "icon": "📊",
                    "accent": "violet",
                    "url": f"http://{VM123_IP}:8069",
                    "probe": lambda: _probe_http(
                        url=f"http://{VM123_IP}:8069/web/login",
                        expect_status=(200, 301, 302),
                    ),
                },
                {
                    "id": "vm123-openpanel-ui",
                    "title": "OpenPanel CE",
                    "spec": "028",
                    "kind": "app",
                    "icon": "🎛️",
                    "accent": "orange",
                    "url": "https://openpanel.ligbox.com.br",
                    "probe": lambda: _probe_http(
                        url="https://openpanel.ligbox.com.br/",
                        expect_status=(200, 301, 302, 403),
                    ),
                },
                {
                    "id": "vm123-openadmin",
                    "title": "OpenAdmin",
                    "spec": "024",
                    "kind": "app",
                    "icon": "🔧",
                    "accent": "orange",
                    "url": "https://admin.openpanel.ligbox.com.br",
                    "probe": lambda: _probe_http(
                        url="https://admin.openpanel.ligbox.com.br/",
                        expect_status=(200, 301, 302, 403),
                    ),
                },
                {
                    "id": "vm123-openpanel-bridge",
                    "title": "OpenPanel FOSS Bridge",
                    "spec": "028",
                    "kind": "api",
                    "icon": "🌉",
                    "accent": "orange",
                    "url": f"{OPENPANEL_BRIDGE}/api",
                    "probe": _probe_openpanel_bridge,
                },
                {
                    "id": "vm123-ops-console",
                    "title": "Ops Console UI",
                    "spec": "019",
                    "kind": "app",
                    "icon": "🖥️",
                    "accent": "aqua",
                    "url": f"http://{VM123_IP}:8100/health",
                    "probe": lambda: _probe_http(url=f"http://{VM123_IP}:8100/health"),
                },
                {
                    "id": "vm123-phpmyadmin",
                    "title": "phpMyAdmin",
                    "spec": "—",
                    "kind": "sw",
                    "icon": "🗄️",
                    "accent": "slate",
                    "url": f"http://{VM123_IP}:8888",
                    "probe": lambda: _probe_http(
                        url=f"http://{VM123_IP}:8888/",
                        expect_status=(200, 301, 302, 403),
                    ),
                },
                {
                    "id": "vm123-ollama",
                    "title": "Ollama LLM",
                    "spec": "029",
                    "kind": "api",
                    "icon": "🤖",
                    "accent": "violet",
                    "url": f"http://{VM123_IP}:11434/api/tags",
                    "probe": lambda: _probe_http(url=f"http://{VM123_IP}:11434/api/tags"),
                },
            ],
        },
        {
            "vm": "130",
            "vm_label": "CT130 · Spec Hub",
            "ip": VM130_IP,
            "services": [
                {
                    "id": "vm130-forgejo",
                    "title": "Forgejo Git",
                    "spec": "031",
                    "kind": "app",
                    "icon": "🦊",
                    "accent": "violet",
                    "url": f"http://{VM130_IP}:3000",
                    "probe": lambda: _probe_http(
                        url=f"http://{VM130_IP}:3000/",
                        expect_status=(200, 301, 302, 403),
                    ),
                },
                {
                    "id": "vm130-spec-portal",
                    "title": "Spec Portal",
                    "spec": "031",
                    "kind": "app",
                    "icon": "📚",
                    "accent": "aqua",
                    "url": f"http://{VM130_IP}:8080",
                    "probe": lambda: _probe_http(url=f"http://{VM130_IP}:8080/"),
                },
                {
                    "id": "vm130-spec-public",
                    "title": "Spec Hub (WAN)",
                    "spec": "031",
                    "kind": "integration",
                    "icon": "🔗",
                    "accent": "violet",
                    "url": "https://spec.ligbox.com.br",
                    "probe": lambda: _probe_http(
                        url="https://spec.ligbox.com.br/",
                        expect_status=(200, 301, 302),
                    ),
                },
            ],
        },
    ]


def _probe_openpanel_bridge() -> dict[str, Any]:
    if not OPENPANEL_BRIDGE_TOKEN:
        return {"ok": False, "status": "check", "detail": "OPENPANEL_BRIDGE_TOKEN ausente"}
    try:
        with httpx.Client(timeout=10.0) as client:
            res = client.get(
                f"{OPENPANEL_BRIDGE}/api",
                headers={"Authorization": f"Bearer {OPENPANEL_BRIDGE_TOKEN}"},
            )
        ok = res.status_code < 400
        return {"ok": ok, "status": "online" if ok else "check", "http_status": res.status_code, "detail": f"HTTP {res.status_code}"}
    except Exception as exc:
        return {"ok": False, "status": "down", "detail": str(exc)}


def run_stack_health() -> dict[str, Any]:
    catalog = build_stack_catalog()
    vm_out: list[dict[str, Any]] = []
    total = ok_count = 0

    def run_one(vm_block: dict[str, Any], svc: dict[str, Any]) -> tuple[dict[str, Any], dict[str, Any]]:
        probe = svc.get("probe")
        result = probe() if callable(probe) else {"ok": False, "status": "unknown", "detail": "sem probe"}
        row = {
            "id": svc["id"],
            "title": svc["title"],
            "spec": svc["spec"],
            "kind": svc["kind"],
            "icon": svc["icon"],
            "accent": svc["accent"],
            "url": svc["url"],
            "ok": bool(result.get("ok")),
            "status": result.get("status") or ("online" if result.get("ok") else "down"),
            "http_status": result.get("http_status"),
            "detail": result.get("detail") or "",
        }
        return vm_block, row

    futures = []
    with ThreadPoolExecutor(max_workers=12) as pool:
        for vm_block in catalog:
            for svc in vm_block["services"]:
                futures.append(pool.submit(run_one, vm_block, svc))

        vm_map: dict[str, dict[str, Any]] = {}
        for vm_block in catalog:
            vm_map[vm_block["vm"]] = {
                "vm": vm_block["vm"],
                "vm_label": vm_block["vm_label"],
                "ip": vm_block["ip"],
                "services": [],
            }

        for fut in as_completed(futures):
            vm_block, row = fut.result()
            vm_map[vm_block["vm"]]["services"].append(row)
            total += 1
            if row["ok"]:
                ok_count += 1

    for vm_id in vm_map:
        vm_map[vm_id]["services"].sort(key=lambda s: s["title"])

    return {
        "generated_at": _now(),
        "summary": {
            "total": total,
            "ok": ok_count,
            "degraded": total - ok_count,
            "vms": list(vm_map.keys()),
        },
        "vms": [vm_map[k] for k in sorted(vm_map.keys())],
    }
