"""系统监控：CPU、内存、磁盘。优先使用 psutil，无可用时回退到 /proc。"""
from __future__ import annotations

import os
import socket
import subprocess
import time
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List

try:
    import psutil

    HAS_PSUTIL = True
except ImportError:
    HAS_PSUTIL = False


def _read_build_info() -> Dict[str, str]:
    """读取 BUILD_INFO 文件或从 Git 获取版本信息。"""
    info: Dict[str, str] = {}
    build_info_path = Path(__file__).parent / "BUILD_INFO"
    if build_info_path.exists():
        try:
            with open(build_info_path) as f:
                for line in f:
                    if ":" in line:
                        k, v = line.strip().split(":", 1)
                        info[k.strip().lower().replace(" ", "_")] = v.strip()
            return info
        except Exception:
            pass

    # 回退到 Git 信息
    project_dir = Path(__file__).parent
    try:
        tag = subprocess.check_output(
            ["git", "-C", str(project_dir), "describe", "--tags", "--always"],
            stderr=subprocess.DEVNULL, text=True
        ).strip()
        commit = subprocess.check_output(
            ["git", "-C", str(project_dir), "rev-parse", "--short", "HEAD"],
            stderr=subprocess.DEVNULL, text=True
        ).strip()
        branch = subprocess.check_output(
            ["git", "-C", str(project_dir), "rev-parse", "--abbrev-ref", "HEAD"],
            stderr=subprocess.DEVNULL, text=True
        ).strip()
        info["version"] = tag if tag else commit
        info["git_commit"] = commit
        info["git_branch"] = branch
        info["build_time"] = "dev"
    except Exception:
        info["version"] = "unknown"
        info["git_commit"] = "unknown"
        info["git_branch"] = "unknown"
        info["build_time"] = "unknown"
    return info


_build_info = _read_build_info()


class SystemMonitor:
    def __init__(self):
        self._last_cpu_times: tuple = (0, 0)
        self._last_cpu_check = 0.0
        self._process_start_time = time.time()

    def _get_system_uptime_seconds(self) -> float:
        if HAS_PSUTIL:
            return time.time() - psutil.boot_time()
        try:
            with open("/proc/uptime") as f:
                uptime_str = f.readline().split()[0]
            return float(uptime_str)
        except Exception:
            return 0.0

    def _get_process_uptime_seconds(self) -> float:
        return time.time() - self._process_start_time

    def get_cpu_percent(self) -> float:
        if HAS_PSUTIL:
            return psutil.cpu_percent(interval=None)
        # fallback: /proc/stat
        try:
            with open("/proc/stat") as f:
                line = f.readline()
            fields = list(map(int, line.split()[1:]))
            total = sum(fields)
            idle = fields[3]
            now = time.time()
            if self._last_cpu_check > 0:
                total_delta = total - self._last_cpu_times[0]
                idle_delta = idle - self._last_cpu_times[1]
                if total_delta > 0:
                    pct = round((1 - idle_delta / total_delta) * 100, 1)
                    self._last_cpu_times = (total, idle)
                    self._last_cpu_check = now
                    return pct
            self._last_cpu_times = (total, idle)
            self._last_cpu_check = now
            return 0.0
        except Exception:
            return 0.0

    def get_memory_info(self) -> Dict[str, Any]:
        if HAS_PSUTIL:
            mem = psutil.virtual_memory()
            return {
                "total": mem.total,
                "available": mem.available,
                "used": mem.used,
                "percent": mem.percent,
            }
        # fallback: /proc/meminfo
        try:
            data: Dict[str, int] = {}
            with open("/proc/meminfo") as f:
                for line in f:
                    if line.startswith("MemTotal:"):
                        data["total"] = int(line.split()[1]) * 1024
                    elif line.startswith("MemAvailable:"):
                        data["available"] = int(line.split()[1]) * 1024
                    elif line.startswith("MemFree:"):
                        data["free"] = int(line.split()[1]) * 1024
            total = data.get("total", 1)
            available = data.get("available", data.get("free", 0))
            used = total - available
            return {
                "total": total,
                "available": available,
                "used": used,
                "percent": round(used / total * 100, 1),
            }
        except Exception:
            return {"total": 1, "available": 0, "used": 0, "percent": 0.0}

    def get_network_info(self) -> List[Dict[str, Any]]:
        """返回物理网卡信息，排除 loopback 和虚拟网卡。"""
        results: List[Dict[str, Any]] = []
        if HAS_PSUTIL:
            import psutil
            addrs = psutil.net_if_addrs()
            stats = psutil.net_if_stats()
            for name, addr_list in addrs.items():
                # 排除 loopback
                if name == "lo" or name.startswith("lo"):
                    continue
                # 排除常见虚拟/容器网卡前缀
                if any(
                    name.startswith(p)
                    for p in ("docker", "veth", "br-", "tun", "virbr", "vmnet", "dummy", "sit", "bridge")
                ):
                    continue
                # 排除无实际硬件地址的虚拟接口（保留有 MAC 的）
                macs = [a.address for a in addr_list if a.family == psutil.AF_LINK]
                if not macs:
                    # 无 MAC 地址，大概率是纯虚拟接口，跳过
                    continue
                ipv4s = [a.address for a in addr_list if str(a.family) == "AddressFamily.AF_INET"]
                ipv6s = [a.address for a in addr_list if str(a.family) == "AddressFamily.AF_INET6"]
                stat = stats.get(name)
                is_up = stat.isup if stat else False
                speed = stat.speed if stat else 0
                mtu = stat.mtu if stat else 0
                results.append(
                    {
                        "name": name,
                        "mac": macs[0] if macs else "",
                        "ipv4": ipv4s,
                        "ipv6": ipv6s,
                        "is_up": is_up,
                        "speed": speed,
                        "mtu": mtu,
                    }
                )
            return results

        # fallback: ip addr show
        try:
            import subprocess, re
            output = subprocess.check_output(["ip", "addr", "show"], text=True, stderr=subprocess.DEVNULL)
            current_iface = None
            for line in output.splitlines():
                m = re.match(r"^\d+:\s+(\S+):\s+<(.+?)>.*state\s+(\S+)", line)
                if m:
                    current_iface = m.group(1)
                    state = m.group(3)
                    flags = m.group(2)
                    if current_iface == "lo" or "LOOPBACK" in flags:
                        current_iface = None
                        continue
                    if any(
                        current_iface.startswith(p)
                        for p in ("docker", "veth", "br-", "tun", "virbr", "vmnet", "dummy", "sit", "bridge")
                    ):
                        current_iface = None
                        continue
                    results.append({"name": current_iface, "state": state, "mac": "", "ipv4": [], "ipv6": []})
                    continue
                if current_iface and line.strip().startswith("link/ether"):
                    mac_m = re.search(r"link/ether\s+([0-9a-f:]+)", line)
                    if mac_m:
                        results[-1]["mac"] = mac_m.group(1)
                if current_iface and line.strip().startswith("inet "):
                    ip_m = re.search(r"inet\s+(\d+\.\d+\.\d+\.\d+)", line)
                    if ip_m:
                        results[-1]["ipv4"].append(ip_m.group(1))
                if current_iface and line.strip().startswith("inet6 "):
                    ip6_m = re.search(r"inet6\s+([0-9a-f:]+)", line)
                    if ip6_m:
                        results[-1]["ipv6"].append(ip6_m.group(1))
            # 过滤掉无 MAC 的虚拟接口
            results = [r for r in results if r["mac"]]
        except Exception:
            pass
        return results

    def get_disk_info(self) -> List[Dict[str, Any]]:
        """返回所有物理挂载点的磁盘信息，排除虚拟文件系统。"""
        results: List[Dict[str, Any]] = []
        seen: set = set()
        if HAS_PSUTIL:
            for part in psutil.disk_partitions(all=False):
                key = f"{part.device}:{part.mountpoint}"
                if key in seen:
                    continue
                # 过滤掉 squashfs / loop 等虚拟/快照挂载
                if part.fstype == "squashfs" or "/loop" in part.device:
                    continue
                # 过滤 EFI / BOOT 分区
                mp_lower = part.mountpoint.lower()
                if "/boot" in mp_lower or "/efi" in mp_lower:
                    continue
                seen.add(key)
                try:
                    usage = psutil.disk_usage(part.mountpoint)
                    # 跳过极小的挂载点（< 10MB，通常是系统伪文件系统）
                    if usage.total < 10 * 1024 * 1024:
                        continue
                    results.append(
                        {
                            "device": part.device,
                            "mountpoint": part.mountpoint,
                            "fstype": part.fstype,
                            "total": usage.total,
                            "used": usage.used,
                            "free": usage.free,
                            "percent": usage.percent,
                        }
                    )
                except Exception:
                    pass
            return results

        # fallback: /proc/mounts + os.statvfs
        try:
            with open("/proc/mounts") as f:
                for line in f:
                    parts = line.split()
                    if len(parts) < 4:
                        continue
                    device, mountpoint, fstype, opts = (
                        parts[0],
                        parts[1],
                        parts[2],
                        parts[3],
                    )
                    if fstype in (
                        "tmpfs",
                        "devtmpfs",
                        "proc",
                        "sysfs",
                        "cgroup",
                        "cgroup2",
                        "devpts",
                        "mqueue",
                        "bpf",
                        "tracefs",
                        "debugfs",
                        "configfs",
                        "fusectl",
                        "overlay",
                        "squashfs",
                    ):
                        continue
                    if "/loop" in device:
                        continue
                    if "bind" in opts:
                        continue
                    # 过滤 EFI / BOOT 分区
                    mp_lower = mountpoint.lower()
                    if "/boot" in mp_lower or "/efi" in mp_lower:
                        continue
                    key = f"{device}:{mountpoint}"
                    if key in seen:
                        continue
                    seen.add(key)
                    try:
                        stat = os.statvfs(mountpoint)
                        total = stat.f_blocks * stat.f_frsize
                        free = stat.f_bavail * stat.f_frsize
                        used = total - free
                        percent = round(used / total * 100, 1) if total > 0 else 0.0
                        results.append(
                            {
                                "device": device,
                                "mountpoint": mountpoint,
                                "fstype": fstype,
                                "total": total,
                                "used": used,
                                "free": free,
                                "percent": percent,
                            }
                        )
                    except Exception:
                        pass
        except Exception:
            pass
        return results

    def _is_flying(self) -> bool:
        return Path("/tmp/arm.lock").exists() or Path("/tmp/flight.lock").exists()

    def get_system_info(self) -> Dict[str, Any]:
        return {
            "hostname": socket.gethostname(),
            "cpu_percent": self.get_cpu_percent(),
            "memory": self.get_memory_info(),
            "disks": self.get_disk_info(),
            "network": self.get_network_info(),
            "timestamp": datetime.utcnow().isoformat(),
            "version": _build_info.get("version", "unknown"),
            "build_time": _build_info.get("build_time", _build_info.get("build_date", "unknown")),
            "git_commit": _build_info.get("git_commit", "unknown"),
            "git_branch": _build_info.get("git_branch", "unknown"),
            "is_flying": self._is_flying(),
            "system_uptime": self._get_system_uptime_seconds(),
            "process_uptime": self._get_process_uptime_seconds(),
        }
