import os
import signal
import subprocess
import sys
import time

import psutil

from ldnode.runner.events import Ping


def load_ping_info() -> Ping:
    """加载心跳信息"""
    return {
        "report_time": int(time.time()),
        "cpu_usage": psutil.cpu_percent(),
        "memory_usage": psutil.virtual_memory().percent,
        "disk_usage": psutil.disk_usage("/").percent,
        "load_average": psutil.getloadavg()[0],
    }


def _sanitize_fds() -> None:
    """确保 fd 0/1/2 有效——nohup 后台运行时这些 fd 可能已关闭，
    导致子进程 Python 解释器 init_sys_streams 失败"""
    for fd in (0, 1, 2):
        try:
            os.fstat(fd)
        except OSError:
            os.open(os.devnull, os.O_RDWR)  # 自动分配到最小可用 fd


def restart() -> None:
    """重启进程
    LaunchDaemon 模式：以非0退出码退出，由 launchd 自动重拉起
    非 LaunchDaemon 模式：fork + execl 自行拉起新进程
    """
    if os.environ.get("LAUNCHD_MANAGED"):
        os.kill(os.getpid(), signal.SIGTERM)
        time.sleep(5)
        # 优雅关闭超时，强制以非0退出码退出让 launchd 重启
        sys.exit(1)
    else:
        pid = os.fork()
        if pid == 0:
            _sanitize_fds()
            time.sleep(3)  # 等待3秒后重启
            python_path = sys.executable
            os.execl(python_path, python_path, *sys.argv)
        else:
            # 结束当前主进程，走既有优雅关闭流程（释放工作子进程）
            os.kill(os.getpid(), signal.SIGTERM)


def upgrade(version: str | None = None) -> tuple[bool, str]:
    """升级 ldnode"""
    package = "ldnode" if not version else f"ldnode=={version}"
    cmd = [sys.executable, "-m", "pip", "install", "-U", package]
    try:
        process = subprocess.run(
            cmd,
            stdin=subprocess.DEVNULL,
            capture_output=True,
            text=True,
            timeout=900,
            check=False,
        )
    except Exception as e:
        return False, f"执行升级命令失败: {e}"
    output = (process.stdout or "") + (process.stderr or "")
    output = output[-4096:]
    if process.returncode == 0:
        return True, output
    return False, output
