"""Nexus Agent remote diagnosis — central Redis/MySQL snapshot + optional SSH probe."""

from __future__ import annotations

import json
import re
from typing import Any

REDIS_KEY_PREFIX = "heartbeat:"

REMOTE_AGENT_DIAG_SHELL = r"""
INSTALL_DIR=/opt/nexus-agent
echo "### systemctl ###"
(systemctl is-active nexus-agent 2>/dev/null || echo inactive)
systemctl show nexus-agent -p ActiveState,SubState,ActiveEnterTimestamp --value 2>/dev/null | tr '\n' ' '
echo
echo "### config_redacted ###"
if [ -f "$INSTALL_DIR/config.json" ]; then
  python3 -c "import json; c=json.load(open('$INSTALL_DIR/config.json')); k=c.get('api_key',''); c['api_key']=((k[:8]+'...') if k else ''); print(json.dumps(c, ensure_ascii=False))"
else
  echo missing_config
fi
echo "### log_errors ###"
for f in /var/log/nexus-agent.log "$INSTALL_DIR/agent.log"; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    tail -40 "$f" | grep -E '401|Heartbeat rejected|Stopping heartbeat|Heartbeat error|discarded' || echo "(no matching lines)"
  fi
done
if command -v journalctl >/dev/null 2>&1; then
  echo "--- journalctl ---"
  journalctl -u nexus-agent --no-pager -n 30 2>/dev/null | grep -E '401|Heartbeat rejected|Stopping heartbeat|Heartbeat error|discarded' || echo "(no matching lines)"
fi
echo "### central_health ###"
CU=$(python3 -c "import json; c=json.load(open('$INSTALL_DIR/config.json')); print((c.get('central') or {}).get('url','') or c.get('central_url',''))" 2>/dev/null || true)
if [ -n "$CU" ]; then
  curl -fsS -o /dev/null -w "http_code=%{http_code}\n" "${CU%/}/health" --connect-timeout 8 2>&1 || echo curl_failed
else
  echo no_central_url
fi
""".strip()


def has_agent_monitoring(server: Any) -> bool:
    return bool((server.agent_api_key or "").strip() or (server.agent_version or "").strip())


def central_status(has_agent: bool, heartbeat: dict[str, str] | None) -> str:
    if heartbeat:
        return "online" if heartbeat.get("is_online") == "True" else "offline"
    if has_agent:
        return "offline"
    return "unknown"


async def probe_remote_agent(server: Any, *, timeout: int = 45) -> dict[str, Any]:
    from server.application.server_batch_common import sudo_wrap
    from server.infrastructure.ssh.asyncssh_pool import exec_ssh_command

    ssh_user = (server.username or "root").strip()
    cmd = sudo_wrap(REMOTE_AGENT_DIAG_SHELL, ssh_user)
    try:
        r = await exec_ssh_command(server, cmd, timeout=timeout)
        stdout = (r.get("stdout") or "").strip()
        parsed = parse_remote_probe(stdout)
        return {
            "ssh_ok": r.get("exit_code") == 0,
            "exit_code": r.get("exit_code"),
            "stderr": (r.get("stderr") or "").strip()[:500],
            "error": "",
            **parsed,
        }
    except Exception as exc:
        return {
            "ssh_ok": False,
            "exit_code": -1,
            "stderr": "",
            "error": str(exc)[:500],
            "systemctl_active": None,
            "systemctl_detail": "",
            "config": None,
            "log_errors": "",
            "central_health_http_code": None,
        }


def parse_remote_probe(stdout: str) -> dict[str, Any]:
    sections: dict[str, str] = {}
    current = ""
    buf: list[str] = []
    for line in stdout.splitlines():
        if line.startswith("### ") and line.endswith(" ###"):
            if current:
                sections[current] = "\n".join(buf).strip()
            current = line[4:-4]
            buf = []
        else:
            buf.append(line)
    if current:
        sections[current] = "\n".join(buf).strip()

    systemctl_lines = [ln for ln in sections.get("systemctl", "").splitlines() if ln.strip()]
    systemctl_active = systemctl_lines[0].strip() if systemctl_lines else ""
    systemctl_detail = systemctl_lines[1].strip() if len(systemctl_lines) > 1 else ""

    config_raw = sections.get("config_redacted", "")
    config: dict[str, Any] | None = None
    if config_raw and config_raw != "missing_config":
        try:
            config = json.loads(config_raw)
        except json.JSONDecodeError:
            config = {"raw": config_raw[:500]}

    health_raw = sections.get("central_health", "")
    http_code: int | None = None
    m = re.search(r"http_code=(\d+)", health_raw)
    if m:
        http_code = int(m.group(1))

    return {
        "systemctl_active": systemctl_active or None,
        "systemctl_detail": systemctl_detail,
        "config": config,
        "log_errors": sections.get("log_errors", ""),
        "central_health_http_code": http_code,
    }


def build_diagnose_errors(row: dict[str, Any]) -> list[str]:
    errors: list[str] = []
    if row.get("has_agent") and row.get("central_status") == "offline":
        errors.append("中心侧显示 Agent 离线（Redis 无新鲜心跳）")
    remote = row.get("remote") or {}
    if remote.get("error"):
        errors.append(f"SSH 连接失败: {remote['error']}")
    elif remote and not remote.get("ssh_ok"):
        stderr = (remote.get("stderr") or "").strip()
        errors.append(f"SSH 命令异常（退出码 {remote.get('exit_code')}）{': ' + stderr if stderr else ''}")
    active = remote.get("systemctl_active")
    if active and active != "active":
        errors.append(f"子机 nexus-agent 服务状态: {active}")
    log_errors = (remote.get("log_errors") or "").strip()
    if log_errors and "(no matching lines)" not in log_errors:
        snippet = log_errors.replace("\n", " | ")[:400]
        errors.append(f"近期 Agent 错误日志: {snippet}")
    code = remote.get("central_health_http_code")
    if code is not None and code != 200:
        errors.append(f"子机访问中心 /health 返回 HTTP {code}")
    elif remote.get("config") and code is None and "no_central_url" not in (remote.get("log_errors") or ""):
        if remote.get("ssh_ok") and not remote.get("config"):
            pass
    return errors


async def diagnose_server_record(
    server: Any,
    heartbeat: dict[str, str] | None,
    *,
    ssh: bool = True,
    timeout: int = 45,
) -> dict[str, Any]:
    has_agent = has_agent_monitoring(server)
    status = central_status(has_agent, heartbeat)
    row: dict[str, Any] = {
        "server_id": server.id,
        "server_name": server.name,
        "server_domain": server.domain,
        "central_status": status,
        "has_agent": has_agent,
        "agent_version_db": (server.agent_version or "").strip(),
        "last_heartbeat_db": str(server.last_heartbeat or ""),
        "last_heartbeat_redis": (heartbeat or {}).get("last_heartbeat", ""),
        "redis_online": (heartbeat or {}).get("is_online") == "True",
    }
    if ssh:
        row["remote"] = await probe_remote_agent(server, timeout=timeout)
    row["errors"] = build_diagnose_errors(row)
    return row
