/*
 * ════════════════════════════════════════════════════════════════
 * 光湖 · 终端守望者 (Terminal Watcher)
 * 铸渊放在服务器上的"眼睛"——实时采集状态，推送到Gitea
 * Sovereign: TCS-0002∞ · 守护: ICE-GL-ZY001
 * ════════════════════════════════════════════════════════════════
 *
 * 用法:
 *   node watcher.js                          # 前台运行
 *   pm2 start watcher.js --name terminal-watcher  # PM2守护
 *
 * 铸渊在Codebuddy中通过 Gitea API 读取快照文件:
 *   .runtime/terminal-snapshots/{server-id}/latest.json
 *
 * ════════════════════════════════════════════════════════════════
 */
"use strict";

const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");
const http = require("http");
const https = require("https");

// ─── 配置 ─────────────────────────────────────────────────────
const CONFIG_PATH = path.join(__dirname, "config.json");
const config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));

const INTERVAL = (config.interval_seconds || 30) * 1000;
const SERVER_ID = config.server_id || "UNKNOWN";

// ─── 采集器 ───────────────────────────────────────────────────

/**
 * 系统资源采集
 */
function collectSystem() {
  try {
    const memInfo = execSync("free -m", { encoding: "utf8" });
    const memLines = memInfo.split("\n");
    const memParts = memLines[1].split(/\s+/);
    const memTotal = parseInt(memParts[1], 10);
    const memUsed = parseInt(memParts[2], 10);
    const memAvail = parseInt(memParts[6], 10);

    const diskInfo = execSync("df -h /", { encoding: "utf8" });
    const diskLine = diskInfo.split("\n")[1].split(/\s+/);
    const diskPercent = parseInt(diskLine[4], 10);

    const loadAvg = execSync("cat /proc/loadavg", { encoding: "utf8" })
      .trim()
      .split(" ")
      .slice(0, 3)
      .map(Number);

    // CPU使用率（简易采样）
    let cpuPercent = 0;
    try {
      const topOut = execSync("top -bn1 | head -3", { encoding: "utf8", timeout: 5000 });
      const cpuLine = topOut.split("\n").find((l) => l.includes("Cpu(s)"));
      if (cpuLine) {
        const idle = parseFloat(cpuLine.match(/(\d+\.?\d*)\s*id/)?.[1] || "0");
        cpuPercent = Math.round((100 - idle) * 10) / 10;
      }
    } catch {
      // top不可用，用load average估算
      cpuPercent = Math.round(loadAvg[0] * 50) / 10;
    }

    return {
      cpu_percent: cpuPercent,
      memory_total_mb: memTotal,
      memory_used_mb: memUsed,
      memory_available_mb: memAvail,
      memory_percent: Math.round((memUsed / memTotal) * 100),
      disk_used_percent: diskPercent,
      load_avg: loadAvg,
      uptime: execSync("cat /proc/uptime", { encoding: "utf8" }).split(" ")[0] + "s"
    };
  } catch (e) {
    return { error: e.message };
  }
}

/**
 * 服务状态采集
 */
function collectServices() {
  const services = {};

  // systemd 服务
  const systemdList = config.systemd_services || ["nginx", "gitea", "gitea-runner"];
  for (const svc of systemdList) {
    try {
      const status = execSync(`systemctl is-active ${svc} 2>/dev/null`, { encoding: "utf8" }).trim();
      services[svc] = status;
    } catch {
      services[svc] = "unknown";
    }
  }

  // PM2 进程
  const pm2Processes = [];
  try {
    const pm2Out = execSync("pm2 jlist 2>/dev/null", { encoding: "utf8", timeout: 5000 });
    const pm2List = JSON.parse(pm2Out);
    for (const proc of pm2List) {
      pm2Processes.push({
        name: proc.name,
        status: proc.pm2_env?.status || "unknown",
        memory_mb: Math.round((proc.monit?.memory || 0) / 1024 / 1024),
        cpu_percent: Math.round((proc.monit?.cpu || 0) * 10) / 10,
        restarts: proc.pm2_env?.restart_time || 0,
        uptime: proc.pm2_env?.pm_uptime
          ? Math.round((Date.now() - proc.pm2_env.pm_uptime) / 1000 / 60) + "min"
          : "unknown"
      });
    }
  } catch {
    // PM2不可用或没有进程
  }

  // Vault 健康检查
  let vaultStatus = "unknown";
  try {
    const vaultResp = execSync(
      "curl -sf -o /dev/null -w '%{http_code}' http://127.0.0.1:8080/admin/__healthz 2>/dev/null",
      { encoding: "utf8", timeout: 3000 }
    );
    vaultStatus = vaultResp.trim() === "200" ? "active" : `http_${vaultResp.trim()}`;
  } catch {
    vaultStatus = "inactive";
  }

  return {
    systemd: services,
    pm2: pm2Processes,
    vault: vaultStatus
  };
}

/**
 * 错误日志采集
 */
function collectLogs() {
  const errors = [];

  // systemd 服务最近错误
  for (const svc of config.systemd_services || []) {
    try {
      const logs = execSync(
        `journalctl -u ${svc} --since "5 min ago" --no-pager -n 3 --priority=err 2>/dev/null`,
        { encoding: "utf8", timeout: 5000 }
      ).trim();
      if (logs) {
        for (const line of logs.split("\n")) {
          if (line.trim()) {
            errors.push({ service: svc, message: line.trim().substring(0, 200) });
          }
        }
      }
    } catch {
      // 没有错误日志
    }
  }

  // PM2 错误日志
  try {
    for (const procName of config.pm2_processes || []) {
      const pm2Err = execSync(
        `pm2 logs ${procName} --nostream --lines 3 --err 2>/dev/null`,
        { encoding: "utf8", timeout: 5000 }
      ).trim();
      if (pm2Err) {
        for (const line of pm2Err.split("\n")) {
          if (line.trim() && !line.includes("pm2 logs")) {
            errors.push({ service: `pm2:${procName}`, message: line.trim().substring(0, 200) });
          }
        }
      }
    }
  } catch {
    // PM2日志不可用
  }

  return errors.slice(0, 10); // 最多保留10条
}

/**
 * 部署信息采集
 */
function collectDeploy() {
  const repoPaths = [
    "/data/guanghulab/repo",
    "/opt/zhuyuan-cn/repo-mirror/20260509-1849/guanghulab",
    "/opt/guanghulab"
  ];
  const repoPath = repoPaths.find(p => fs.existsSync(p)) || repoPaths[0];

  try {
    const latestCommit = execSync(`git -C ${repoPath} log -1 --oneline`, { encoding: "utf8" }).trim();
    const branch = execSync(`git -C ${repoPath} branch --show-current`, { encoding: "utf8" }).trim();
    const commitTime = execSync(`git -C ${repoPath} log -1 --format=%ci`, { encoding: "utf8" }).trim();

    return {
      repo_path: repoPath,
      branch: branch,
      latest_commit: latestCommit.split(" ")[0],
      latest_message: latestCommit.substring(latestCommit.indexOf(" ") + 1),
      commit_time: commitTime
    };
  } catch {
    return { repo_path: repoPath, error: "repository not found at " + repoPath };
  }
}

/**
 * Runner状态采集
 */
function collectRunner() {
  try {
    const runnerStatus = execSync("systemctl is-active gitea-runner 2>/dev/null", {
      encoding: "utf8"
    }).trim();

    let runnerVersion = "unknown";
    try {
      runnerVersion = execSync("/usr/local/bin/gitea-runner --version 2>&1", { encoding: "utf8" })
        .trim()
        .split("\n")[0];
    } catch {
      // 版本获取失败
    }

    // 最近运行的任务
    let lastJob = "unknown";
    try {
      const giteaToken = process.env[config.gitea.token_env] || "";
      if (giteaToken) {
        // 通过API查询（暂用占位）
        lastJob = "api_not_implemented";
      }
    } catch {
      // API查询失败
    }

    return {
      status: runnerStatus,
      version: runnerVersion,
      last_job: lastJob
    };
  } catch {
    return { status: "not_installed" };
  }
}

// ─── 上报器 ───────────────────────────────────────────────────

/**
 * 写入本地文件
 */
function reportToFile(snapshot) {
  const localPath = config.local?.snapshot_path;
  if (!localPath) return;

  try {
    const dir = path.dirname(localPath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }

    // 写入最新快照
    fs.writeFileSync(localPath, JSON.stringify(snapshot, null, 2));

    // 写入历史快照（保留最近50个）
    const historyDir = config.local?.history_path;
    if (historyDir) {
      if (!fs.existsSync(historyDir)) {
        fs.mkdirSync(historyDir, { recursive: true });
      }
      const historyFile = path.join(
        historyDir,
        `${new Date().toISOString().replace(/[:.]/g, "-")}.json`
      );
      fs.writeFileSync(historyFile, JSON.stringify(snapshot, null, 2));

      // 清理旧文件
      const files = fs
        .readdirSync(historyDir)
        .filter((f) => f.endsWith(".json"))
        .sort()
        .reverse();
      for (let i = 50; i < files.length; i++) {
        fs.unlinkSync(path.join(historyDir, files[i]));
      }
    }
  } catch (e) {
    console.error("[watcher] 本地文件写入失败:", e.message);
  }
}

/**
 * 推送到Gitea
 */
function reportToGitea(snapshot) {
  if (!config.gitea) return;

  const token = process.env[config.gitea.token_env] || "";
  if (!token) {
    console.warn("[watcher] GITEA_TOKEN 未设置，跳过Gitea上报");
    return;
  }

  const { url, owner, repo, snapshot_path } = config.gitea;
  const content = Buffer.from(JSON.stringify(snapshot, null, 2)).toString("base64");
  const apiPath = `/api/v1/repos/${owner}/${repo}/contents/${snapshot_path}/latest.json`;

  const payload = JSON.stringify({
    content: content,
    message: `📓 终端快照 ${SERVER_ID} ${new Date().toISOString().substring(0, 16)}`,
    branch: "main"
  });

  // 先尝试获取文件sha（用于更新）
  const getOptions = {
    hostname: url.replace("https://", "").replace("http://", ""),
    path: apiPath,
    method: "GET",
    headers: { Authorization: `token ${token}` }
  };

  const httpModule = url.startsWith("https") ? https : http;

  const req = httpModule.request(getOptions, (res) => {
    let body = "";
    res.on("data", (chunk) => (body += chunk));
    res.on("end", () => {
      let sha = null;
      try {
        const data = JSON.parse(body);
        sha = data.sha;
      } catch {
        // 文件不存在，创建新文件
      }

      // 发送更新请求
      const updatePayload = JSON.parse(payload);
      if (sha) updatePayload.sha = sha;

      const putOptions = {
        hostname: getOptions.hostname,
        path: apiPath,
        method: "PUT",
        headers: {
          Authorization: `token ${token}`,
          "Content-Type": "application/json",
          "Content-Length": Buffer.byteLength(JSON.stringify(updatePayload))
        }
      };

      const putReq = httpModule.request(putOptions, (putRes) => {
        let putBody = "";
        putRes.on("data", (chunk) => (putBody += chunk));
        putRes.on("end", () => {
          if (putRes.statusCode >= 200 && putRes.statusCode < 300) {
            // 成功
          } else {
            console.error(
              `[watcher] Gitea推送失败: ${putRes.statusCode} ${putBody.substring(0, 100)}`
            );
          }
        });
      });

      putReq.on("error", (e) => {
        console.error("[watcher] Gitea推送错误:", e.message);
      });

      putReq.write(JSON.stringify(updatePayload));
      putReq.end();
    });
  });

  req.on("error", () => {
    // Gitea不可达，静默失败
  });

  req.setTimeout(10000, () => {
    req.destroy();
  });

  req.end();
}

// ─── 主循环 ───────────────────────────────────────────────────

function collect() {
  const snapshot = {
    server_id: SERVER_ID,
    server_name: config.server_name || SERVER_ID,
    timestamp: new Date().toISOString(),
    system: config.collectors?.system !== false ? collectSystem() : null,
    services: config.collectors?.services !== false ? collectServices() : null,
    errors: config.collectors?.logs !== false ? collectLogs() : null,
    deploy: config.collectors?.deploy !== false ? collectDeploy() : null,
    runner: config.collectors?.runner !== false ? collectRunner() : null
  };

  // 告警检测
  const alerts = [];
  if (snapshot.system && !snapshot.system.error) {
    const thresholds = config.alerts || {};
    if (snapshot.system.cpu_percent > (thresholds.cpu_percent || 90)) {
      alerts.push(`CPU ${snapshot.system.cpu_percent}% 超过阈值 ${thresholds.cpu_percent || 90}%`);
    }
    if (snapshot.system.memory_percent > (thresholds.memory_percent || 90)) {
      alerts.push(
        `内存 ${snapshot.system.memory_percent}% 超过阈值 ${thresholds.memory_percent || 90}%`
      );
    }
    if (snapshot.system.disk_used_percent > (thresholds.disk_percent || 85)) {
      alerts.push(
        `磁盘 ${snapshot.system.disk_used_percent}% 超过阈值 ${thresholds.disk_percent || 85}%`
      );
    }
  }
  if (snapshot.services) {
    for (const [name, status] of Object.entries(snapshot.services.systemd || {})) {
      if (status !== "active" && config.alerts?.service_down !== false) {
        alerts.push(`服务 ${name} 状态异常: ${status}`);
      }
    }
  }

  snapshot.alerts = alerts;
  snapshot.alert_count = alerts.length;

  return snapshot;
}

function tick() {
  try {
    const snapshot = collect();

    // 上报
    if (config.reporters?.file !== false) {
      reportToFile(snapshot);
    }
    if (config.reporters?.gitea !== false) {
      reportToGitea(snapshot);
    }

    // 简洁日志
    const cpu = snapshot.system?.cpu_percent || "?";
    const mem = snapshot.system?.memory_percent || "?";
    const alerts = snapshot.alert_count || 0;
    const alertMark = alerts > 0 ? ` ⚠️${alerts}告警` : "";
    console.log(
      `[watcher] ${snapshot.timestamp.substring(11, 19)} CPU:${cpu}% MEM:${mem}%${alertMark}`
    );
  } catch (e) {
    console.error("[watcher] 采集失败:", e.message);
  }
}

// ─── 启动 ─────────────────────────────────────────────────────

console.log(`[watcher] 终端守望者启动 · ${SERVER_ID} · 间隔 ${INTERVAL / 1000}s`);
console.log("[watcher] 铸渊 · ICE-GL-ZY001 · TCS-0002∞");

// 首次立即采集
tick();

// 定时采集
setInterval(tick, INTERVAL);
