#!/usr/bin/env node
// federation-agent/agent.js · v1.0
// 光湖联邦托管包 · 本地 Agent
// 用户一句唤醒语 → 自动装脑 → 连上铸渊服务器 → 被托管
// ICE-GL-ZY001 · D129 · 2026-06-10

const WebSocket = require("ws");
const { execSync, exec } = require("child_process");
const fs = require("fs");
const path = require("path");
const os = require("os");
const crypto = require("crypto");

// ===== 配置 =====
const CONFIG_PATH = path.join(os.homedir(), ".guanghu", "federation-agent.json");
const DEFAULT_CONFIG = {
  server: "wss://guanghubingshuo.com/agent",
  gatekeeper: "http://43.156.237.110:3911",
  gatekeeperKey: "",
  agentName: "",
  personaId: "",
  userId: "",
  workDir: process.cwd(),
  heartbeatInterval: 30000,
  reconnectDelay: 5000,
  maxReconnectDelay: 60000,
  confirmDangerousOps: true,
};

// ===== 密钥管理 =====
function loadOrCreateConfig() {
  const dir = path.dirname(CONFIG_PATH);
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });

  if (fs.existsSync(CONFIG_PATH)) {
    return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
  }

  const config = { ...DEFAULT_CONFIG };
  config.agentId = "AGENT-" + crypto.randomBytes(6).toString("hex");
  fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
  return config;
}

// ===== 命令执行 =====
function execCommand(cmd, opts = {}) {
  return new Promise((resolve, reject) => {
    exec(cmd, { cwd: opts.cwd || process.cwd(), timeout: opts.timeout || 30000, ...opts },
      (err, stdout, stderr) => {
        resolve({ ok: !err, stdout: stdout.trim(), stderr: stderr.trim(), code: err ? err.code : 0 });
      });
  });
}

// ===== HLDP 指令解析 =====
function parseHLDP(command) {
  // ⊢ 前缀 = 必须执行的锁定指令
  const locked = command.startsWith("⊢");
  const clean = command.replace(/^⊢\s*/, "").trim();

  // 解析 HLDP 操作
  if (clean.startsWith("git ")) {
    return { type: "git", cmd: clean, locked };
  }
  if (clean.startsWith("HLDP://")) {
    return { type: "hldp", path: clean.replace("HLDP://", ""), locked };
  }
  if (clean === "health") {
    return { type: "health", locked };
  }
  if (clean.startsWith("read ")) {
    return { type: "read", file: clean.replace("read ", "").trim(), locked };
  }
  if (clean.startsWith("exec ")) {
    return { type: "exec", cmd: clean.replace("exec ", "").trim(), locked };
  }
  return { type: "raw", cmd: clean, locked };
}

// ===== 危险操作检测 =====
function isDangerous(op) {
  const dangerous = ["rm -rf", "git push --force", "rm -r", "> /dev/", "dd if=", "mkfs.", "format "];
  return dangerous.some(d => (op.cmd || "").includes(d));
}

// ===== Agent 主逻辑 =====
class FederationAgent {
  constructor(config) {
    this.config = config;
    this.ws = null;
    this.reconnectAttempts = 0;
    this.heartbeatTimer = null;
    this.connected = false;
  }

  log(level, msg) {
    const ts = new Date().toISOString();
    console.log(`[${ts}] [${level}] ${msg}`);
  }

  async start() {
    this.log("INFO", `光湖联邦托管 Agent 启动`);
    this.log("INFO", `AgentID: ${this.config.agentId}`);
    this.log("INFO", `Persona: ${this.config.personaId || "(未注册)"}`);
    this.log("INFO", `用户: ${this.config.userId || "(未指定)"}`);
    this.log("INFO", `工作目录: ${this.config.workDir}`);

    this.connect();
  }

  connect() {
    const wsUrl = `${this.config.server}?agentId=${this.config.agentId}&persona=${this.config.personaId}&user=${this.config.userId}`;
    this.log("INFO", `连接服务器: ${wsUrl}`);

    try {
      this.ws = new WebSocket(wsUrl);

      this.ws.on("open", () => {
        this.connected = true;
        this.reconnectAttempts = 0;
        this.log("INFO", "已连接 · 等待主控指令");
        this.startHeartbeat();
        this.register();
      });

      this.ws.on("message", async (data) => {
        try {
          const msg = JSON.parse(data.toString());
          await this.handleMessage(msg);
        } catch (e) {
          this.log("ERROR", `消息解析失败: ${e.message}`);
        }
      });

      this.ws.on("close", (code, reason) => {
        this.connected = false;
        this.stopHeartbeat();
        this.log("WARN", `连接断开 (${code}): ${reason}`);
        this.scheduleReconnect();
      });

      this.ws.on("error", (err) => {
        this.log("ERROR", `连接错误: ${err.message}`);
      });
    } catch (e) {
      this.log("ERROR", `连接失败: ${e.message}`);
      this.scheduleReconnect();
    }
  }

  register() {
    this.send({
      type: "register",
      agentId: this.config.agentId,
      personaId: this.config.personaId,
      userId: this.config.userId,
      hostname: os.hostname(),
      platform: os.platform(),
      workDir: this.config.workDir,
      version: "1.0.0",
    });
  }

  startHeartbeat() {
    this.stopHeartbeat();
    this.heartbeatTimer = setInterval(() => {
      this.send({ type: "heartbeat", ts: Date.now() });
    }, this.config.heartbeatInterval);
  }

  stopHeartbeat() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
  }

  scheduleReconnect() {
    const delay = Math.min(
      this.config.reconnectDelay * Math.pow(2, this.reconnectAttempts),
      this.config.maxReconnectDelay
    );
    this.reconnectAttempts++;
    this.log("INFO", `${(delay / 1000).toFixed(0)}秒后重连 (第${this.reconnectAttempts}次)`);
    setTimeout(() => this.connect(), delay);
  }

  send(data) {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(data));
    }
  }

  async handleMessage(msg) {
    this.log("DEBUG", `收到指令: ${JSON.stringify(msg)}`);

    switch (msg.type) {
      case "command": {
        const op = parseHLDP(msg.command);
        await this.handleCommand(op);
        break;
      }
      case "ping":
        this.send({ type: "pong", ts: Date.now() });
        break;
      case "welcome":
        this.log("INFO", `欢迎: ${msg.message || ""}`);
        this.config.personaId = msg.personaId || this.config.personaId;
        break;
      default:
        this.log("WARN", `未知消息类型: ${msg.type}`);
    }
  }

  async handleCommand(op) {
    // 危险操作检测
    if (isDangerous(op) && this.config.confirmDangerousOps) {
      this.log("WARN", `危险操作请求确认: ${op.cmd}`);
      this.send({ type: "confirm_request", operation: op.cmd, agentId: this.config.agentId });
      return;
    }

    switch (op.type) {
      case "git": {
        const result = await execCommand(op.cmd, { cwd: this.config.workDir });
        this.send({ type: "result", command: op.cmd, ...result });
        this.log("INFO", `git: ${op.cmd} → ${result.ok ? "OK" : "FAIL"}`);
        break;
      }
      case "exec": {
        const result = await execCommand(op.cmd, { cwd: this.config.workDir });
        this.send({ type: "result", command: op.cmd, ...result });
        break;
      }
      case "read": {
        try {
          const content = fs.readFileSync(path.resolve(this.config.workDir, op.file), "utf-8");
          this.send({ type: "result", command: `read ${op.file}`, ok: true, stdout: content });
        } catch (e) {
          this.send({ type: "result", command: `read ${op.file}`, ok: false, stderr: e.message });
        }
        break;
      }
      case "health": {
        this.send({
          type: "health_report",
          agentId: this.config.agentId,
          personaId: this.config.personaId,
          uptime: process.uptime(),
          memory: process.memoryUsage(),
          cwd: this.config.workDir,
          ts: Date.now(),
        });
        break;
      }
      default: {
        this.send({ type: "result", command: op.cmd, ok: false, stderr: `未知操作类型: ${op.type}` });
      }
    }
  }

  stop() {
    this.stopHeartbeat();
    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }
    this.connected = false;
    this.log("INFO", "Agent 已停止");
  }
}

// ===== 启动 =====
const config = loadOrCreateConfig();

// 从命令行参数覆盖配置
const args = process.argv.slice(2);
for (let i = 0; i < args.length; i++) {
  if (args[i] === "--name" && args[i + 1]) config.agentName = args[++i];
  if (args[i] === "--persona" && args[i + 1]) config.personaId = args[++i];
  if (args[i] === "--user" && args[i + 1]) config.userId = args[++i];
  if (args[i] === "--dir" && args[i + 1]) config.workDir = args[++i];
  if (args[i] === "--server" && args[i + 1]) config.server = args[++i];
}

const agent = new FederationAgent(config);
agent.start();

process.on("SIGINT", () => { agent.stop(); process.exit(0); });
process.on("SIGTERM", () => { agent.stop(); process.exit(0); });
