#!/bin/bash
# =============================================================
#  sysdiag.sh — 系统快速诊断（完整版）
#  用法: bash sysdiag.sh [-q] [-o /path/to/output.txt] [-n]
#    -q  静默模式（不输出到终端，仅写文件）
#    -o  同时将结果写入指定文件
#    -n  跳过自动安装依赖（no-install）
# =============================================================

set -uo pipefail

# ---------- 参数解析 ----------
QUIET=0
OUTPUT_FILE=""
NO_INSTALL=0
while getopts "qo:n" opt; do
  case $opt in
    q) QUIET=1 ;;
    o) OUTPUT_FILE="$OPTARG" ;;
    n) NO_INSTALL=1 ;;
    *) echo "用法: $0 [-q] [-o output.txt] [-n]" >&2; exit 1 ;;
  esac
done

# ---------- 颜色定义 ----------
if [[ -t 1 && $QUIET -eq 0 ]]; then
  C_RESET='\033[0m'; C_BOLD='\033[1m'; C_CYAN='\033[0;36m'
  C_GREEN='\033[0;32m'; C_YELLOW='\033[0;33m'; C_RED='\033[0;31m'
  C_DIM='\033[2m'; C_BG_RED='\033[41m'; C_BG_YELLOW='\033[43m'
  C_BG_GREEN='\033[42m'
else
  C_RESET='' C_BOLD='' C_CYAN='' C_GREEN='' C_YELLOW='' C_RED=''
  C_DIM='' C_BG_RED='' C_BG_YELLOW='' C_BG_GREEN=''
fi

# ---------- 输出辅助函数 ----------
_print() {
  if [[ $QUIET -eq 0 ]]; then printf "%b" "$*"; fi
  if [[ -n "$OUTPUT_FILE" ]]; then
    printf "%b" "$*" | sed 's/\x1b\[[0-9;]*[mK]//g' >> "$OUTPUT_FILE"
  fi
}

section() { _print "\n${C_CYAN}${C_BOLD}┌─── $1 ───${C_RESET}\n"; }

warn_if_high() {
  local val="$1" warn="$2" crit="$3"
  local int_val="${val%%.*}"
  if   (( int_val >= crit )); then _print "${C_RED}${val}${C_RESET}"
  elif (( int_val >= warn )); then _print "${C_YELLOW}${val}${C_RESET}"
  else                              _print "${C_GREEN}${val}${C_RESET}"
  fi
}

# ---------- 初始化输出文件 ----------
if [[ -n "$OUTPUT_FILE" ]]; then
  mkdir -p "$(dirname "$OUTPUT_FILE")"
  > "$OUTPUT_FILE"
fi

# ═══════════════════════════════════════════════════════════════
# 阶段 0：依赖检测 & 自动安装
# ═══════════════════════════════════════════════════════════════

# 工具列表：[命令]=包名（apt/yum 通用）
declare -A TOOL_PKG=(
  [iostat]="sysstat"
  [iotop]="iotop"
  [nethogs]="nethogs"
  [ss]="iproute2"
)

detect_pkg_manager() {
  if   command -v apt-get &>/dev/null; then echo "apt"
  elif command -v dnf     &>/dev/null; then echo "dnf"
  elif command -v yum     &>/dev/null; then echo "yum"
  elif command -v zypper  &>/dev/null; then echo "zypper"
  elif command -v pacman  &>/dev/null; then echo "pacman"
  else echo "unknown"
  fi
}

install_pkg() {
  local pkg="$1"
  local pm
  pm=$(detect_pkg_manager)
  case $pm in
    apt)    DEBIAN_FRONTEND=noninteractive apt-get install -y -q "$pkg" &>/dev/null ;;
    yum)    yum install -y -q "$pkg" &>/dev/null ;;
    dnf)    dnf install -y -q "$pkg" &>/dev/null ;;
    zypper) zypper install -y "$pkg" &>/dev/null ;;
    pacman) pacman -S --noconfirm "$pkg" &>/dev/null ;;
    *)      return 1 ;;
  esac
}

_print "${C_BOLD}${C_CYAN}"
_print "╔══════════════════════════════════════════════════╗\n"
_print "║          系统快速诊断  sysdiag.sh                ║\n"
_print "╚══════════════════════════════════════════════════╝${C_RESET}\n"
_print "  主机: ${C_BOLD}$(hostname -f 2>/dev/null || hostname)${C_RESET}\n"
_print "  时间: $(date '+%Y-%m-%d %H:%M:%S %Z')\n"
_print "  内核: $(uname -r)\n"

section "0. 依赖检查 & 自动安装"

MISSING_TOOLS=()
for cmd in "${!TOOL_PKG[@]}"; do
  if command -v "$cmd" &>/dev/null; then
    _print "  ${C_GREEN}✔${C_RESET} ${cmd}\n"
  else
    pkg="${TOOL_PKG[$cmd]}"
    _print "  ${C_YELLOW}✘ ${cmd}${C_RESET}（包: ${pkg}）"
    if [[ $NO_INSTALL -eq 1 ]]; then
      _print "  ${C_DIM}跳过（-n 模式）${C_RESET}\n"
      MISSING_TOOLS+=("$cmd")
    elif [[ $EUID -ne 0 ]]; then
      _print "  ${C_DIM}→ 需要 root 权限，跳过自动安装${C_RESET}\n"
      _print "    ${C_DIM}手动安装: $(detect_pkg_manager) install ${pkg}${C_RESET}\n"
      MISSING_TOOLS+=("$cmd")
    else
      _print "  ${C_DIM}→ 安装中...${C_RESET}"
      if install_pkg "$pkg"; then
        _print " ${C_GREEN}✔ 完成${C_RESET}\n"
      else
        _print " ${C_RED}✘ 失败${C_RESET}\n"
        MISSING_TOOLS+=("$cmd")
      fi
    fi
  fi
done

START_TS=$(date +%s)

# ═══════════════════════════════════════════════════════════════
# 健康指标收集（供末尾总结使用）
# ═══════════════════════════════════════════════════════════════
HEALTH_ISSUES=()
HEALTH_WARNS=()
HEALTH_OK=()

add_issue() { HEALTH_ISSUES+=("$1"); }
add_warn()  { HEALTH_WARNS+=("$1"); }
add_ok()    { HEALTH_OK+=("$1"); }

# ─── 1. CPU / 负载 ───────────────────────────────────────────
section "1. CPU / 系统负载"

UPTIME_RAW=$(uptime)
UPTIME_STR=$(echo "$UPTIME_RAW" | sed 's/.*up /up /' | cut -d',' -f1-2)
LOAD=$(echo "$UPTIME_RAW" | awk -F'load average:' '{print $2}' | xargs)
CPU_CORES=$(nproc)
LOAD1=$(echo "$LOAD" | awk -F'[ ,]+' '{print $1}')
LOAD1_INT="${LOAD1%%.*}"

_print "  运行时长: ${C_BOLD}${UPTIME_STR}${C_RESET}\n"
_print "  CPU 核数: ${CPU_CORES}\n"
_print "  负载均值(1/5/15m): "
warn_if_high "$LOAD1" "$CPU_CORES" "$((CPU_CORES * 2))"
_print "  $(echo "$LOAD" | cut -d',' -f2-)\n"

# CPU 使用率（/proc/stat 双采样，比 top -bn1 更准）
read -ra CPU_STAT1 < <(awk '/^cpu /{print $2,$3,$4,$5,$6,$7,$8}' /proc/stat)
sleep 0.8
read -ra CPU_STAT2 < <(awk '/^cpu /{print $2,$3,$4,$5,$6,$7,$8}' /proc/stat)
TOTAL1=$(( CPU_STAT1[0]+CPU_STAT1[1]+CPU_STAT1[2]+CPU_STAT1[3]+CPU_STAT1[4]+CPU_STAT1[5]+CPU_STAT1[6] ))
TOTAL2=$(( CPU_STAT2[0]+CPU_STAT2[1]+CPU_STAT2[2]+CPU_STAT2[3]+CPU_STAT2[4]+CPU_STAT2[5]+CPU_STAT2[6] ))
IDLE1=${CPU_STAT1[3]}; IDLE2=${CPU_STAT2[3]}
DTOTAL=$(( TOTAL2 - TOTAL1 )); DIDLE=$(( IDLE2 - IDLE1 ))
CPU_USAGE=$(awk -v dt="$DTOTAL" -v di="$DIDLE" \
  'BEGIN{if(dt>0) printf "%.1f",(1-di/dt)*100; else print "N/A"}')

_print "  CPU 使用率: "
warn_if_high "$CPU_USAGE" 70 90
_print "%\n"

CPU_INT="${CPU_USAGE%%.*}"
if   (( CPU_INT >= 90 ));               then add_issue "CPU 使用率过高 ${CPU_USAGE}%（≥90%）"
elif (( CPU_INT >= 70 ));               then add_warn  "CPU 使用率偏高 ${CPU_USAGE}%（≥70%）"
elif (( LOAD1_INT >= CPU_CORES * 2 ));  then add_issue "系统负载过高 load=${LOAD1}，核数=${CPU_CORES}"
elif (( LOAD1_INT >= CPU_CORES ));      then add_warn  "系统负载偏高 load=${LOAD1}，核数=${CPU_CORES}"
else                                         add_ok    "CPU 正常（使用率 ${CPU_USAGE}%，负载 ${LOAD1}）"
fi

# ─── 2. 内存 ─────────────────────────────────────────────────
section "2. 内存"

MEM_RAW=$(free -m)
MEM_TOTAL=$(echo "$MEM_RAW" | awk '/^Mem:/{print $2}')
MEM_USED=$(echo "$MEM_RAW"  | awk '/^Mem:/{print $3}')
MEM_AVAIL=$(echo "$MEM_RAW" | awk '/^Mem:/{print $7}')
MEM_PCT=$(awk -v u="$MEM_USED" -v t="$MEM_TOTAL" 'BEGIN{printf "%.1f",u/t*100}')
SWAP_TOTAL=$(echo "$MEM_RAW" | awk '/^Swap:/{print $2}')
SWAP_USED=$(echo "$MEM_RAW"  | awk '/^Swap:/{print $3}')

_print "  物理内存: ${MEM_TOTAL} MiB 总量 | 使用 "
warn_if_high "$MEM_PCT" 75 90
_print "% (${MEM_USED} MiB) | 可用 ${MEM_AVAIL} MiB\n"

MEM_INT="${MEM_PCT%%.*}"
if   (( MEM_INT >= 90 )); then add_issue "内存使用率过高 ${MEM_PCT}%（≥90%）"
elif (( MEM_INT >= 75 )); then add_warn  "内存使用率偏高 ${MEM_PCT}%（≥75%）"
else                           add_ok    "内存正常（使用率 ${MEM_PCT}%）"
fi

if [[ "$SWAP_TOTAL" -gt 0 ]]; then
  SWAP_PCT=$(awk -v u="$SWAP_USED" -v t="$SWAP_TOTAL" 'BEGIN{printf "%.1f",u/t*100}')
  _print "  Swap: ${SWAP_TOTAL} MiB 总量 | 使用 "
  warn_if_high "$SWAP_PCT" 50 80
  _print "% (${SWAP_USED} MiB)\n"
  SWAP_INT="${SWAP_PCT%%.*}"
  if   (( SWAP_INT >= 80 )); then add_issue "Swap 使用率过高 ${SWAP_PCT}%（物理内存严重不足）"
  elif (( SWAP_INT >= 50 )); then add_warn  "Swap 使用偏高 ${SWAP_PCT}%（建议检查内存泄漏）"
  fi
else
  _print "  Swap: 未启用\n"
fi

# ─── 3. 磁盘空间 ─────────────────────────────────────────────
section "3. 磁盘空间"

DISK_ISSUE=0
while IFS= read -r line; do
  pct=$(echo "$line" | awk '{for(i=1;i<=NF;i++) if($i~/^[0-9]+%$/) print $i}' | tr -d '%')
  mnt=$(echo "$line" | awk '{print $NF}')
  if [[ -n "$pct" && "$line" != *"Filesystem"* ]]; then
    if   (( pct >= 90 )); then
      _print "  ${C_RED}${line}${C_RESET}\n"
      add_issue "磁盘 ${mnt} 使用率过高 ${pct}%（≥90%）"
      DISK_ISSUE=1
    elif (( pct >= 75 )); then
      _print "  ${C_YELLOW}${line}${C_RESET}\n"
      add_warn "磁盘 ${mnt} 使用率偏高 ${pct}%（≥75%）"
      DISK_ISSUE=1
    else
      _print "  ${line}\n"
    fi
  else
    _print "  ${C_DIM}${line}${C_RESET}\n"
  fi
done < <(df -hPT 2>/dev/null \
  | awk 'NR==1{printf "%-20s %-8s %6s %6s %6s  %s\n",$1,$2,$3,$4,$6,$NF; next}
         /^(\/dev|tmpfs|overlay)/{printf "%-20s %-8s %6s %6s %6s  %s\n",$1,$2,$3,$4,$6,$NF}')
[[ $DISK_ISSUE -eq 0 ]] && add_ok "所有磁盘使用率正常"

# ─── 4. 磁盘 IO ──────────────────────────────────────────────
section "4. 磁盘 IO"

if command -v iostat &>/dev/null; then
  IO_OUT=$(iostat -xhd 1 1 2>/dev/null \
    | awk '/Device|^[a-z]/{if(!/loop/)print $0}' | head -12)
  echo "$IO_OUT" | while IFS= read -r l; do _print "  ${l}\n"; done
  HIGH_IO=$(echo "$IO_OUT" | awk 'NR>1{util=$NF; gsub(/%/,"",util); if(util+0>=80) print $1" util="util"%"}')
  if [[ -n "$HIGH_IO" ]]; then add_warn "磁盘 IO 负载较高: ${HIGH_IO}"
  else add_ok "磁盘 IO 正常"; fi
elif command -v iotop &>/dev/null; then
  iotop -b -n1 -o 2>/dev/null | head -10 | while IFS= read -r l; do _print "  ${l}\n"; done
else
  _print "  ${C_YELLOW}⚠ iostat / iotop 均未安装，跳过${C_RESET}\n"
fi

# ─── 5. 进程 TOP ─────────────────────────────────────────────
section "5. 进程 TOP（CPU & 内存）"

_print "  ${C_BOLD}--- CPU TOP 8 ---${C_RESET}\n"
ps axo pid,user:12,%cpu,%mem,vsz,rss,stat,comm --sort=-%cpu 2>/dev/null \
  | head -9 | awk '{printf "  %-7s %-12s %5s %5s %9s %8s  %-6s  %s\n",$1,$2,$3,$4,$5,$6,$7,$8}' \
  | while IFS= read -r l; do _print "${l}\n"; done

_print "\n  ${C_BOLD}--- 内存 TOP 8 ---${C_RESET}\n"
ps axo pid,user:12,%cpu,%mem,vsz,rss,stat,comm --sort=-%mem 2>/dev/null \
  | head -9 | awk '{printf "  %-7s %-12s %5s %5s %9s %8s  %-6s  %s\n",$1,$2,$3,$4,$5,$6,$7,$8}' \
  | while IFS= read -r l; do _print "${l}\n"; done

# ─── 6. 网络接口流量 ──────────────────────────────────────────
section "6. 网络接口流量"

_print "  ${C_DIM}$(printf '%-12s %14s %14s %10s %10s' 接口 接收字节 发送字节 接收包 发送包)${C_RESET}\n"
awk 'NR>2 && !/lo:/{gsub(/:/,"",$1); printf "  %-12s %14s %14s %10s %10s\n",$1,$2,$10,$3,$11}' \
  /proc/net/dev

# ─── 7. 连接数 TOP IP ────────────────────────────────────────
section "7. 连接数 TOP IP（全状态）"

ss -antu 2>/dev/null \
  | awk 'NR>1 && $5!~/^(\*|127\.|::1)/{
      ip=$5
      if(ip~/:[0-9]+$/) sub(/:[^:]*$/,"",ip)
      gsub(/[\[\]]/,"",ip); print ip}' \
  | sort | uniq -c | sort -rn | head -10 \
  | awk '{printf "  %6s 次  %s\n",$1,$2}' \
  | while IFS= read -r l; do _print "${l}\n"; done

# ─── 8. ESTABLISHED 连接 TOP IP ──────────────────────────────
section "8. ESTABLISHED 连接 TOP IP"

ss -ant state established 2>/dev/null \
  | awk 'NR>1 && $5!~/^(\*|127\.|::1)/{
      ip=$5
      if(ip~/:[0-9]+$/) sub(/:[^:]*$/,"",ip)
      gsub(/[\[\]]/,"",ip); print ip}' \
  | sort | uniq -c | sort -rn | head -10 \
  | awk '{printf "  %6s 次  %s\n",$1,$2}' \
  | while IFS= read -r l; do _print "${l}\n"; done

TOTAL_CONNS=$(ss -ant 2>/dev/null | tail -n+2 | wc -l)
ESTAB_CONNS=$(ss -ant state established 2>/dev/null | tail -n+2 | wc -l)
TIMEWAIT_CONNS=$(ss -ant state time-wait 2>/dev/null | tail -n+2 | wc -l)
_print "\n  合计: 总 ${C_BOLD}${TOTAL_CONNS}${C_RESET} | ESTABLISHED ${C_BOLD}${ESTAB_CONNS}${C_RESET} | TIME_WAIT ${C_BOLD}${TIMEWAIT_CONNS}${C_RESET}\n"

if   (( TOTAL_CONNS >= 10000 )); then add_issue "连接数异常 ${TOTAL_CONNS}（可能遭受攻击或连接泄漏）"
elif (( TOTAL_CONNS >= 5000  )); then add_warn  "连接数较多 ${TOTAL_CONNS}"
else                                  add_ok    "网络连接数正常（总 ${TOTAL_CONNS}）"
fi
(( TIMEWAIT_CONNS >= 3000 )) && add_warn "TIME_WAIT 连接数过多 ${TIMEWAIT_CONNS}（建议调优 tcp_tw_reuse）"

# ─── 9. 监听端口 ──────────────────────────────────────────────
section "9. 监听端口（含进程）"
ss -lntpu 2>/dev/null | awk '{print "  "$0}' | head -16 \
  | while IFS= read -r l; do _print "${l}\n"; done

# ─── 10. 高流量进程 ───────────────────────────────────────────
section "10. 高流量进程"
if command -v nethogs &>/dev/null; then
  timeout 5 nethogs -t 2>/dev/null | grep -v "^$\|Refreshing" | head -12 \
    | while IFS= read -r l; do _print "  ${l}\n"; done
else
  _print "  ${C_YELLOW}⚠ nethogs 未安装，无法采集进程级网络流量${C_RESET}\n"
fi

# ─── 11. 近期内核 / OOM 日志 ─────────────────────────────────
section "11. 近期内核 / OOM 日志"

OOM_FOUND=0; LOG_ISSUE=0
if command -v journalctl &>/dev/null; then
  OOM_LINES=$(journalctl -k --no-pager -n 200 --since "1 hour ago" 2>/dev/null \
    | grep -iE "oom|killed process|out of memory|segfault" | tail -10 || true)
  ERR_LINES=$(journalctl -k --no-pager -n 200 --since "1 hour ago" 2>/dev/null \
    | grep -iE "error|fail|critical" | grep -viE "firmware|microcode" | tail -10 || true)
else
  OOM_LINES=$(dmesg --time-format iso 2>/dev/null \
    | grep -iE "oom|killed process|out of memory" | tail -10 || true)
  ERR_LINES=$(dmesg --time-format iso 2>/dev/null \
    | grep -iE "error|fail|critical" | grep -viE "firmware|microcode" | tail -10 || true)
fi

if [[ -n "$OOM_LINES" ]]; then
  _print "  ${C_RED}${C_BOLD}⚠ 发现 OOM / 进程被杀记录：${C_RESET}\n"
  echo "$OOM_LINES" | while IFS= read -r l; do _print "  ${C_RED}${l}${C_RESET}\n"; done
  add_issue "发现 OOM Kill 记录，内存曾严重不足"
  OOM_FOUND=1; LOG_ISSUE=1
fi
if [[ -n "$ERR_LINES" ]]; then
  _print "\n  ${C_YELLOW}近期内核错误/警告（过滤固件噪音后）：${C_RESET}\n"
  echo "$ERR_LINES" | while IFS= read -r l; do _print "  ${C_YELLOW}${l}${C_RESET}\n"; done
  add_warn "内核日志存在 error/fail 条目"
  LOG_ISSUE=1
fi
if [[ $LOG_ISSUE -eq 0 ]]; then
  _print "  ${C_GREEN}近 1h 内核日志无明显异常${C_RESET}\n"
  add_ok "内核日志无异常"
fi

# ═══════════════════════════════════════════════════════════════
# 健康总结报告
# ═══════════════════════════════════════════════════════════════
END_TS=$(date +%s)
ELAPSED=$(( END_TS - START_TS ))

ISSUE_COUNT=${#HEALTH_ISSUES[@]}
WARN_COUNT=${#HEALTH_WARNS[@]}
OK_COUNT=${#HEALTH_OK[@]}

_print "\n\n"
_print "${C_BOLD}${C_CYAN}"
_print "╔══════════════════════════════════════════════════════════╗\n"
_print "║                    ★ 健康状态总结 ★                     ║\n"
_print "╚══════════════════════════════════════════════════════════╝${C_RESET}\n\n"

# 整体评级
if   (( ISSUE_COUNT > 0 )); then
  _print "  整体状态: ${C_BG_RED}${C_BOLD}  ✘ 需要立即处理  ${C_RESET}  存在 ${C_RED}${ISSUE_COUNT}${C_RESET} 个严重问题\n"
elif (( WARN_COUNT  > 0 )); then
  _print "  整体状态: ${C_BG_YELLOW}${C_BOLD}  ⚡ 基本健康  ${C_RESET}     有 ${C_YELLOW}${WARN_COUNT}${C_RESET} 项警告，建议关注\n"
else
  _print "  整体状态: ${C_BG_GREEN}${C_BOLD}  ✔ 系统健康  ${C_RESET}      所有 ${C_GREEN}${OK_COUNT}${C_RESET} 项指标均正常\n"
fi
_print "  诊断耗时: ${ELAPSED}s\n"

# 严重问题
if (( ISSUE_COUNT > 0 )); then
  _print "\n  ${C_RED}${C_BOLD}【⚠ 需立即处理】${C_RESET}\n"
  for item in "${HEALTH_ISSUES[@]}"; do
    [[ -z "${item:-}" ]] && continue
    _print "  ${C_RED}  ✘  ${item}${C_RESET}\n"
  done
fi

# 警告
if (( WARN_COUNT > 0 )); then
  _print "\n  ${C_YELLOW}${C_BOLD}【⚡ 建议关注】${C_RESET}\n"
  for item in "${HEALTH_WARNS[@]}"; do
    [[ -z "${item:-}" ]] && continue
    _print "  ${C_YELLOW}  ⚡  ${item}${C_RESET}\n"
  done
fi

# 正常项
if (( OK_COUNT > 0 )); then
  _print "\n  ${C_GREEN}${C_BOLD}【✔ 正常项】${C_RESET}\n"
  for item in "${HEALTH_OK[@]}"; do
    [[ -z "${item:-}" ]] && continue
    _print "  ${C_GREEN}  ✔  ${item}${C_RESET}\n"
  done
fi

_print "\n"
_print "${C_CYAN}${C_BOLD}"
_print "╔══════════════════════════════════════════════════════════╗\n"
_print "║  诊断完成  $(date '+%Y-%m-%d %H:%M:%S')  耗时 ${ELAPSED}s            ║\n"
_print "╚══════════════════════════════════════════════════════════╝${C_RESET}\n"

[[ -n "$OUTPUT_FILE" ]] && _print "\n${C_DIM}结果已写入: ${OUTPUT_FILE}${C_RESET}\n"
