"""VPS Agent 采集模块

采集本机资源、服务器信息与基础安全状态。
依赖 psutil + 少量系统命令（ufw/iptables/sshd_config）。
"""

import platform
import socket
import subprocess
import time

import psutil


def collect_metrics() -> dict:
    """资源使用率（CPU/内存/磁盘/网络/负载/运行时长）"""
    cpu = psutil.cpu_percent(interval=1)
    mem = psutil.virtual_memory()
    disk = psutil.disk_usage("/")
    net = psutil.net_io_counters()
    try:
        load_1m = psutil.getloadavg()[0]
    except (AttributeError, OSError):
        load_1m = None
    return {
        "cpu_pct": round(cpu, 1),
        "mem_pct": round(mem.percent, 1),
        "disk_pct": round(disk.percent, 1),
        "net_in_mb": round(net.bytes_recv / 1024 / 1024, 2),
        "net_out_mb": round(net.bytes_sent / 1024 / 1024, 2),
        "load_1m": round(load_1m, 2) if load_1m is not None else None,
        "uptime_sec": int(time.time() - psutil.boot_time()),
    }


def _get_ip() -> str:
    """获取本机出口 IP（UDP 不实际发包；设超时避免无网环境阻塞）"""
    s = None
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.settimeout(2)  # 无网络环境下 connect 可能阻塞，加超时保护
        s.connect(("8.8.8.8", 80))
        return s.getsockname()[0]
    except Exception:
        return ""
    finally:
        if s is not None:
            try:
                s.close()
            except Exception:
                pass


def collect_server_info() -> dict:
    """服务器基础信息"""
    os_name = platform.system()
    try:
        with open("/etc/os-release") as f:
            for line in f:
                if line.startswith("PRETTY_NAME="):
                    os_name = line.split("=", 1)[1].strip().strip('"')
                    break
    except Exception:
        pass
    return {
        "hostname": socket.gethostname(),
        "os": os_name,
        "kernel": platform.release(),
        "cpu_cores": psutil.cpu_count(),
        "mem_total_gb": round(psutil.virtual_memory().total / 1024**3, 1),
        "disk_total_gb": round(psutil.disk_usage("/").total / 1024**3, 1),
        "public_ip": _get_ip(),
        "status": "online",
    }


def _check_ssh_config() -> dict:
    """检查 SSH 配置（root 登录 / 密码登录）"""
    path = "/etc/ssh/sshd_config"
    permit_root = "unknown"
    password_auth = "unknown"
    try:
        with open(path) as f:
            for line in f:
                low = line.strip().lower()
                if low.startswith("permitrootlogin"):
                    parts = low.split()
                    permit_root = parts[1] if len(parts) > 1 else "unknown"
                elif low.startswith("passwordauthentication"):
                    parts = low.split()
                    password_auth = parts[1] if len(parts) > 1 else "unknown"
    except Exception:
        return {"check_item": "ssh_config", "status": "unknown",
                "detail": "无法读取 sshd_config", "suggestion": None}

    status = "pass"
    suggestions = []
    if permit_root == "yes":
        status = "warn"
        suggestions.append("设置 PermitRootLogin no 或 prohibit-password")
    if password_auth == "yes":
        status = "warn"
        suggestions.append("关闭密码登录 PasswordAuthentication no，改用密钥")
    return {
        "check_item": "ssh_config",
        "status": status,
        "detail": f"PermitRootLogin={permit_root}, PasswordAuthentication={password_auth}",
        "suggestion": "；".join(suggestions) if suggestions else None,
    }


def _check_firewall() -> dict:
    """检查防火墙状态"""
    try:
        out = subprocess.run(["ufw", "status"], capture_output=True, text=True, timeout=5)
        if "Status: active" in out.stdout:
            return {"check_item": "firewall", "status": "pass",
                    "detail": "ufw 已启用", "suggestion": None}
        return {"check_item": "firewall", "status": "warn",
                "detail": "ufw 未启用", "suggestion": "启用防火墙 ufw enable"}
    except Exception:
        try:
            out = subprocess.run(["iptables", "-L", "-n"], capture_output=True, text=True, timeout=5)
            if out.stdout.strip():
                return {"check_item": "firewall", "status": "pass",
                        "detail": "iptables 有规则", "suggestion": None}
        except Exception:
            pass
        return {"check_item": "firewall", "status": "unknown",
                "detail": "未检测到 ufw/iptables", "suggestion": None}


def _check_listening_ports() -> dict:
    """统计监听端口"""
    try:
        conns = psutil.net_connections(kind="inet")
        listening = sorted({c.laddr.port for c in conns if c.status == "LISTEN"})
        detail = f"开放端口 {len(listening)} 个: {','.join(map(str, listening[:15]))}"
        status = "pass" if len(listening) < 10 else "warn"
        suggestion = "端口偏多，建议关闭非必要端口" if status == "warn" else None
        return {"check_item": "listening_ports", "status": status,
                "detail": detail, "suggestion": suggestion}
    except Exception as e:  # noqa: BLE001
        return {"check_item": "listening_ports", "status": "unknown",
                "detail": str(e), "suggestion": None}


def collect_security() -> list:
    """基础安全检查项"""
    return [_check_ssh_config(), _check_firewall(), _check_listening_ports()]
