#!/usr/bin/env node
/* ═══════════════════════════════════════════════════════════
   光湖驱动引擎 v3.2 · 工作会话授权 · Guanghu Drive Engine v3
   HLDP万能语言接口 + 集群串联 + 人类验证码审批

   核心变更 (v2 → v3):
     Token 不再是 root 密码 → Token 只是身份标识
     任何操作需通过 /auth/request → 邮箱验证码 → /auth/confirm
     三元组白名单 (服务器+人+人格体) + 来源服务器绑定
     操作类型分级邮件 (code-repo / system-arch / api-access)
     未注册请求直接抛弃，不响应，杜绝恶意刷邮箱

   v3.1 新增 (D182):
     /auth/request 支持 email 参数 — 请求自带邮箱直接发验证码
     苍耳/耳耳蛋等独立用户无需预注册白名单
     email 模式 + 白名单模式双模式共存

   部署:
     SG-001 /opt/zhuyuan/gatekeeper/engine-v3.js
     监听: 3911 (环境变量 ENGINE_PORT 可配)
     数据: /opt/zhuyuan/gatekeeper/whitelist.json
   ═══════════════════════════════════════════════════════════ */

const http = require('http'), fs = require('fs'), path = require('path'),
      crypto = require('crypto'),
      os = require('os'), tls = require('tls');
const { SessionManager } = require('./session-manager');
const { getAction, listActions } = require('./authorized-actions');

const PORT = parseInt(process.env.ENGINE_PORT || process.env.GATEKEEPER_PORT || process.argv[2] || '3911', 10);
const HOST = process.env.ENGINE_HOST || '0.0.0.0';
const DATA_DIR = process.env.GATEKEEPER_DATA_DIR || path.join('/opt/zhuyuan', 'gatekeeper');
const CMD_TIMEOUT = 30000, MAX_OUTPUT = 100000;
const CODE_TTL = 300; // 验证码 5 分钟过期
const RATE_LIMIT_WINDOW = 60;
const MAX_REQUESTS = 3;

// ═══════════════════════════════════════
// 初始化数据目录
// ═══════════════════════════════════════
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true, mode: 0o700 });
const sessions = new SessionManager({ absoluteTtl: 12 * 3600, idleTtl: 3600, stateFile: path.join(DATA_DIR, 'sessions.json') });

// ═══════════════════════════════════════
// 密钥 — 兼容所有历史数据目录
// ═══════════════════════════════════════
const SECRET_PATHS = [
  ...(process.env.GATEKEEPER_SECRET_PATH ? [process.env.GATEKEEPER_SECRET_PATH] : []),
  path.join(os.homedir(), '.gatekeeper', 'secret'),
  path.join(os.homedir(), '.gk', 'secret'),
  path.join(os.homedir(), '.guanghu-engine', 'secret'),
];
let API_SECRET;
for (const sp of SECRET_PATHS) {
  if (fs.existsSync(sp)) { const k = fs.readFileSync(sp, 'utf-8').trim(); if (k) { API_SECRET = k; break; } }
}
if (!API_SECRET) {
  API_SECRET = ['zy', 'gtw', crypto.randomBytes(24).toString('hex')].join('_');
  const dir = path.dirname(SECRET_PATHS[0]);
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
  fs.writeFileSync(SECRET_PATHS[0], API_SECRET, { mode: 0o600 });
  console.log('');
  console.log('══════════════════════════════════════════════');
  console.log('  🔐 光湖驱动引擎 v3 · 首次启动');
  console.log('  API 密钥: 已生成（不写入服务日志）');
  console.log('  已保存至: ' + SECRET_PATHS[0]);
  console.log('  监听端口: ' + PORT);
  console.log('══════════════════════════════════════════════');
  console.log('');
}

const HOSTNAME = os.hostname();

// ═══════════════════════════════════════
// 服务器标识映射
// ═══════════════════════════════════════
function getServerId() {
  if (process.env.GATEKEEPER_SERVER_ID) return process.env.GATEKEEPER_SERVER_ID;
  const map = {
    'guanghu-lang-sg-001': 'BS-SG-001',
    'VM-0-16-ubuntu': 'BS-GZ-006',
  };
  for (const [host, id] of Object.entries(map)) {
    if (HOSTNAME.includes(host) || host.includes(HOSTNAME) || HOSTNAME === host) return id;
  }
  return HOSTNAME;
}
const SERVER_ID = getServerId();

// ═══════════════════════════════════════
// Token → 人格体身份映射
// ═══════════════════════════════════════
function loadTokens() {
  const tf = path.join(DATA_DIR, 'tokens.json');
  if (fs.existsSync(tf)) {
    try {
      const tokens = JSON.parse(fs.readFileSync(tf, 'utf-8'));
      if (process.env.ZHULAN_API_TOKEN) tokens[process.env.ZHULAN_API_TOKEN] = { pid:'ICE-GL-ZL-001', name:'铸澜', server:SERVER_ID };
      return tokens;
    } catch (e) { log('WARN', 'tokens_parse_error', e.message); }
  }
  const defaults = {};
  // 本机所有已知 token 都映射为默认人格体（需要后续细化）
  for (const sp of SECRET_PATHS) {
    if (fs.existsSync(sp)) {
      const t = fs.readFileSync(sp, 'utf-8').trim();
      if (t) defaults[t] = { pid: 'ICE-GL-ZY001', name: '铸渊', server: SERVER_ID };
    }
  }
  if (process.env.ZHULAN_API_TOKEN) defaults[process.env.ZHULAN_API_TOKEN] = { pid:'ICE-GL-ZL-001', name:'铸澜', server:SERVER_ID };
  return defaults;
}

// ═══════════════════════════════════════
// SMTP 配置
// ═══════════════════════════════════════
const SMTP = {
  host: 'smtp.qq.com',
  port: 465,
  user: process.env.SMTP_USER || '',
  pass: process.env.QQ_SMTP_AUTH_CODE || '',
};

// ═══════════════════════════════════════
// 运行时状态
// ═══════════════════════════════════════
const pendingOps = {};  // { challenge_id: { code, expires, op_type, action, caller, target_email, target_name, server } }
const rateLimitMap = {}; // { ip: [timestamp, ...] }

// ═══════════════════════════════════════
// 日志
// ═══════════════════════════════════════
function log(level, action, detail) {
  const ts = new Date().toISOString();
  const line = '[' + ts + '] [' + level + '] ' + action + (detail ? ' | ' + detail : '');
  console.log(line);
  try {
    const lf = path.join(DATA_DIR, 'engine.log');
    fs.appendFileSync(lf, line + '\n');
  } catch (e) { /* ignore */ }
}

// ═══════════════════════════════════════
// 加载白名单
// ═══════════════════════════════════════
function loadWhitelist() {
  const wf = path.join(DATA_DIR, 'whitelist.json');
  const required = [
    { server:SERVER_ID, human:'ICE-GL∞', email:process.env.BINGSHUO_AUTH_EMAIL || '', personalities:['ICE-GL-ZL-001'], bound_servers:[SERVER_ID] },
    { server:SERVER_ID, human:'苍耳', human_id:'TCS-GL-009', email:process.env.CANGER_AUTH_EMAIL || '', personalities:['PTS-VA-001-EED','ICE-GL-CA001'], bound_servers:[SERVER_ID] }
  ];
  if (fs.existsSync(wf)) {
    try {
      const whitelist = JSON.parse(fs.readFileSync(wf, 'utf-8'));
      whitelist.triads = Array.isArray(whitelist.triads) ? whitelist.triads : [];
      for (const item of required) {
        const found = whitelist.triads.find(t => t.server === item.server && (t.human_id === item.human_id || t.human === item.human));
        if (!found) whitelist.triads.push(item);
        else {
          if (item.email) found.email = item.email;
          found.personalities = [...new Set([...(found.personalities || []), ...item.personalities])];
          found.bound_servers = [...new Set([...(found.bound_servers || []), ...item.bound_servers])];
        }
      }
      return whitelist;
    } catch (e) { log('WARN', 'whitelist_parse_error', e.message); }
  }
  // 默认白名单：冰朔 + 之之
  return {
    triads: [
      { server: SERVER_ID, human: 'ICE-GL∞', email: process.env.BINGSHUO_AUTH_EMAIL || '', personalities: ['ICE-GL-ZY001','ICE-GL-ZL-001'], bound_servers: [SERVER_ID] },
      { server: SERVER_ID, human: '苍耳', human_id: 'TCS-GL-009', email: process.env.CANGER_AUTH_EMAIL || '', personalities: ['PTS-VA-001-EED','ICE-GL-CA001'], bound_servers: [SERVER_ID] },
    ],
    pending_ops: {}
  };
}

// ═══════════════════════════════════════
// 操作类型定义
// ═══════════════════════════════════════
const OP_TYPES = {
  'code-repo': { label: '操作代码仓库', risk: 'medium', color: '#4ec9b0', icon: '📦' },
  'system-arch': { label: '操作系统架构', risk: 'high', color: '#e06c75', icon: '⚡' },
  'api-access': { label: '调用API', risk: 'medium', color: '#61afef', icon: '🔌' },
};

// ═══════════════════════════════════════
// 工具函数
// ═══════════════════════════════════════
function jr(res, code, data) {
  res.writeHead(code, {
    'Content-Type': 'application/json; charset=utf-8',
    'Access-Control-Allow-Origin': '*'
  });
  res.end(JSON.stringify(data));
}

function parseBody(req) {
  return new Promise((resolve) => {
    let body = '';
    req.on('data', c => body += c);
    req.on('end', () => {
      try { resolve(JSON.parse(body)); } catch (e) { resolve(null); }
    });
  });
}

function generateCode() {
  return Array.from({ length: 6 }, () => crypto.randomInt(0, 10)).join('');
}

function fmtUptime(s) {
  const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600), m = Math.floor((s % 3600) / 60);
  const parts = [];
  if (d > 0) parts.push(d + 'd');
  if (h > 0) parts.push(h + 'h');
  parts.push(m + 'm');
  return parts.join(' ');
}

function fmtBytes(b) {
  if (b < 1024) return b + ' B';
  if (b < 1048576) return (b / 1024).toFixed(1) + ' KB';
  if (b < 1073741824) return (b / 1048576).toFixed(1) + ' MB';
  return (b / 1073741824).toFixed(1) + ' GB';
}

function runAuthorizedAction(actionId, timeout) {
  const action = getAction(actionId);
  if (!action) return Promise.resolve({ code: 126, stdout: '', stderr: 'action_not_allowed' });
  return action.run(timeout);
}

// ═══════════════════════════════════════
// 速率限制
// ═══════════════════════════════════════
function checkRate(ip) {
  const now = Date.now() / 1000;
  if (!rateLimitMap[ip]) rateLimitMap[ip] = [];
  rateLimitMap[ip] = rateLimitMap[ip].filter(t => now - t < RATE_LIMIT_WINDOW);
  if (rateLimitMap[ip].length >= MAX_REQUESTS) return false;
  rateLimitMap[ip].push(now);
  return true;
}

// ═══════════════════════════════════════
// 身份认证 (Token → 人格体身份)
// ═══════════════════════════════════════
function auth(headers) {
  const authHeader = headers['authorization'] || headers['Authorization'] || '';
  const bearerMatch = authHeader.match(/^Bearer\s+(.+)$/i);
  if (!bearerMatch) return { ok: false, reason: '缺少 Authorization: Bearer <token>' };
  const token = bearerMatch[1];
  const tokens = loadTokens();
  const identity = tokens[token];
  if (!identity) {
    // 检查是否匹配本机的主密钥
    if (token === API_SECRET) {
      return { ok: true, identity: { pid: 'ICE-GL-ZY001', name: '铸渊(主密钥)', server: SERVER_ID } };
    }
    return { ok: false, reason: 'Token 未注册 · 请先通过 /auth/register 注册人格体身份' };
  }
  return { ok: true, identity: identity };
}

// ═══════════════════════════════════════
// 白名单校验 (服务器+人+人格体 三元组)
// ═══════════════════════════════════════
function checkWhitelist(sourceServer, personalityId) {
  const whitelist = loadWhitelist();
  const matches = [];

  for (const triad of whitelist.triads) {
    // 来源服务器必须在 bound_servers 中
    if (!triad.bound_servers.includes(sourceServer)) continue;
    // 人格体匹配：* 通配 或 精确匹配
    if (triad.personalities.includes('*') || triad.personalities.includes(personalityId)) {
      matches.push(triad);
    }
  }

  if (matches.length === 0) {
    return { ok: false, reason: '未注册的三元组 · 服务器 ' + sourceServer + ' + 人格体 ' + personalityId + ' 不在白名单中' };
  }

  return { ok: true, triad: matches[0] };
}

// ═══════════════════════════════════════
// 发送验证码邮件 (Node.js 原生 TLS SMTP)
// ═══════════════════════════════════════
function sendVerificationEmail(code, opType, callerName, callerPid, cmdPreview, targetEmail, targetName, serverIp) {
  return new Promise((resolve) => {
    const sovereign = loadWhitelist().triads.find(t => t.human === 'ICE-GL∞' && t.email);
    const smtpUser = SMTP.user || (sovereign && sovereign.email) || '';
    if (!SMTP.pass || !smtpUser) {
      log('WARN', 'email_skip', 'SMTP 发件账号或授权码未配置');
      resolve(false);
      return;
    }

    const opInfo = OP_TYPES[opType] || OP_TYPES['code-repo'];

    // 构建 MIME 邮件
    const boundary = '----GuanghuBoundary' + crypto.randomBytes(8).toString('hex');
    const htmlBody = `<!DOCTYPE html>
<html lang="zh">
<head><meta charset="utf-8"></head>
<body style="margin:0;padding:0;background:#0a1628;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif">
<table width="100%" cellpadding="0" cellspacing="0" style="background:#0a1628;padding:40px 0">
<tr><td align="center">
<table width="520" cellpadding="0" cellspacing="0" style="background:linear-gradient(135deg,#152238 0%,#1a2d4a 100%);border-radius:16px;overflow:hidden;border:1px solid #2a3f5f">
<tr><td style="padding:32px 32px 20px;text-align:center;border-bottom:1px solid #2a3f5f">
<div style="font-size:13px;color:${opInfo.color};letter-spacing:3px;text-transform:uppercase;margin-bottom:8px">ICE-GL∞ 光湖语言系统 · Gatekeeper v3</div>
<div style="font-size:20px;color:#e0e8f0;font-weight:600">${opInfo.icon} ${opInfo.label} · 验证码</div>
</td></tr>
<tr><td style="padding:24px 32px">
<table width="100%" cellpadding="0" cellspacing="0" style="background:#0d1b2e;border-radius:8px;border:1px solid #2a3f5f">
<tr><td style="padding:14px 16px;font-size:14px">
<div style="color:#64748b;font-size:12px">调用者</div>
<div style="color:#e0e8f0;font-weight:600">${callerName} (${callerPid})</div>
<div style="color:#64748b;font-size:12px;margin-top:8px">操作类型</div>
<div style="color:${opInfo.color};font-weight:600">${opInfo.icon} ${opInfo.label} · 风险: ${opInfo.risk === 'high' ? '⚠️ 高' : '🟡 中'}</div>
<div style="color:#64748b;font-size:12px;margin-top:8px">命令预览</div>
<div style="color:#8899aa;font-family:monospace;font-size:13px;background:#060d18;padding:8px 12px;border-radius:6px;margin-top:4px;word-break:break-all">${cmdPreview}</div>
<div style="color:#64748b;font-size:12px;margin-top:8px">来源服务器</div>
<div style="color:#e0e8f0">${serverIp} · ${SERVER_ID}</div>
</td></tr>
</table>
<div style="text-align:center;margin:24px 0">
<div style="font-size:12px;color:#8899aa;margin-bottom:8px">验证码 · 5 分钟内有效</div>
<div style="font-size:38px;font-weight:bold;color:${opInfo.color};letter-spacing:8px;background:#0d1b2e;padding:14px 28px;border-radius:8px;display:inline-block;border:2px dashed ${opInfo.color}">${code}</div>
</div>
<div style="background:${opInfo.risk === 'high' ? '#3d1a1a' : '#1a2d1a'};border-left:3px solid ${opInfo.color};padding:10px 14px;border-radius:4px;font-size:13px;color:${opInfo.risk === 'high' ? '#e06c75' : '#4ec9b0'}">
${opInfo.risk === 'high' ? '⚠️ 高权限操作 · 确认前请仔细检查命令内容' : '💡 将此验证码发给铸渊确认 → 释放操作权限 · 仅当次有效'}
</div>
</td></tr>
<tr><td style="background:#0a1628;padding:14px 32px;text-align:center">
<div style="font-size:11px;color:#556677">ICE-GL∞ 光湖语言系统 · 小湖灯自动发送 · 国作登字-2026-A-00037559</div>
</td></tr>
</table>
</td></tr>
</table>
</body>
</html>`;

    const plainBody = `光湖语言系统 · Gatekeeper v3 · 操作授权\n
操作类型: ${opInfo.label} (${opType})
调用者: ${callerName} (${callerPid})
命令: ${cmdPreview}
来源服务器: ${serverIp} (${SERVER_ID})
风险等级: ${opInfo.risk === 'high' ? '高' : '中'}
\n验证码: ${code}
有效期: 5 分钟
\n将此验证码发给铸渊确认 → 释放操作权限
如非本人操作，请忽略。
---
ICE-GL∞ 光湖语言系统 · 小湖灯自动发送
国作登字-2026-A-00037559`;

    const rawEmail =
      `From: 光湖小湖灯 <${smtpUser}>\r\n` +
      `To: ${targetName} <${targetEmail}>\r\n` +
      `Subject: =?UTF-8?B?${Buffer.from(`${opInfo.icon} Gatekeeper授权 · ${callerName} · ${opInfo.label}`, 'utf-8').toString('base64')}?=\r\n` +
      `MIME-Version: 1.0\r\n` +
      `Content-Type: multipart/alternative; boundary="${boundary}"\r\n` +
      `\r\n` +
      `--${boundary}\r\n` +
      `Content-Type: text/plain; charset="utf-8"\r\n` +
      `Content-Transfer-Encoding: base64\r\n` +
      `\r\n` +
      `${Buffer.from(plainBody, 'utf-8').toString('base64')}\r\n` +
      `--${boundary}\r\n` +
      `Content-Type: text/html; charset="utf-8"\r\n` +
      `Content-Transfer-Encoding: base64\r\n` +
      `\r\n` +
      `${Buffer.from(htmlBody, 'utf-8').toString('base64')}\r\n` +
      `--${boundary}--\r\n` +
      `.\r\n`;

    const authPlain = `\0${smtpUser}\0${SMTP.pass}`;

    const socket = tls.connect({ host: SMTP.host, port: SMTP.port, rejectUnauthorized: false }, () => {
      let stage = 0;
      let buffer = '';

      const processResponse = () => {
        // Process complete lines (ending with \r\n)
        while (buffer.includes('\r\n')) {
          const idx = buffer.indexOf('\r\n');
          const line = buffer.slice(0, idx);
          buffer = buffer.slice(idx + 2);
          if (!line) continue;
          const code = parseInt(line.slice(0, 3)) || 0;
          const isLast = line.length > 3 && line[3] === ' '; // '250 OK' vs '250-AUTH'

          if (stage === 0) {
            if (code === 220) { stage = 1; socket.write('EHLO guanghu-engine\r\n'); }
          } else if (stage === 1) {
            if (isLast && code === 250) { stage = 2; socket.write('AUTH PLAIN ' + Buffer.from(authPlain).toString('base64') + '\r\n'); }
          } else if (stage === 2) {
            if (code === 235) { stage = 3; socket.write('MAIL FROM:<' + smtpUser + '>\r\n'); }
            else if (code >= 500) { log('WARN', 'smtp_auth_failed', line); socket.end(); resolve(false); return; }
          } else if (stage === 3) {
            if (code === 250) { stage = 4; socket.write('RCPT TO:<' + targetEmail + '>\r\n'); }
            else if (code >= 500) { log('WARN', 'smtp_mail_from_failed', line); socket.end(); resolve(false); return; }
          } else if (stage === 4) {
            if (code === 250) { stage = 5; socket.write('DATA\r\n'); }
            else if (code >= 500) { log('WARN', 'smtp_rcpt_failed', line); socket.end(); resolve(false); return; }
          } else if (stage === 5) {
            if (code === 354) { socket.write(rawEmail); stage = 6; }
          } else if (stage === 6) {
            if (code === 250) {
              socket.write('QUIT\r\n');
              socket.end();
              log('INFO', 'email_sent', 'to=' + targetEmail + ' op=' + opType);
              resolve(true);
              return;
            }
          }

          if (code >= 500 && stage > 0 && stage !== 2) {
            log('WARN', 'smtp_error', 'stage=' + stage + ' line=' + line.slice(0, 100));
            socket.end();
            resolve(false);
            return;
          }
        }
      };

      socket.on('data', (data) => {
        buffer += data.toString();
        processResponse();
      });
      socket.on('error', (err) => {
        log('WARN', 'smtp_socket_error', err.message);
        resolve(false);
      });
    });

    socket.on('error', (err) => {
      log('WARN', 'smtp_connect_error', err.message);
      resolve(false);
    });

    socket.setTimeout(15000, () => {
      log('WARN', 'smtp_timeout', '');
      socket.destroy();
      resolve(false);
    });
  });
}

// ═══════════════════════════════════════
// 清理过期操作
// ═══════════════════════════════════════
function cleanExpiredOps() {
  const now = Date.now() / 1000;
  for (const [cid, op] of Object.entries(pendingOps)) {
    if (op.expires < now) delete pendingOps[cid];
  }
}

// ═══════════════════════════════════════
// HTTP 服务器
// ═══════════════════════════════════════
const server = http.createServer(async (req, res) => {
  if (req.method === 'OPTIONS') { jr(res, 204, {}); return; }

  const ip = req.socket.remoteAddress || 'unknown';
  if (!checkRate(ip)) { log('WARN', 'rate_limit', ip); jr(res, 429, { error: '请求过于频繁 · 每分钟最多 ' + MAX_REQUESTS + ' 次' }); return; }

  const url = new URL(req.url, 'http://' + ((req.headers.host) || 'localhost'));
  const route = url.pathname;

  // ═══ 无需认证的端点 ═══
  if (req.method === 'GET') {
    if (route === '/health') {
      jr(res, 200, {
        ok: true, service: 'guanghu-engine', version: '3.2.0',
        uptime: process.uptime().toFixed(0) + 's',
        timestamp: new Date().toISOString(),
        auth_mode: 'human-verification-required'
      });
      return;
    }
    if (route === '/status') {
      const total = os.totalmem(), free = os.freemem();
      jr(res, 200, {
        ok: true, hostname: HOSTNAME, server_id: SERVER_ID,
        platform: os.platform(), arch: os.arch(), cpus: os.cpus().length,
        uptime: os.uptime(), uptime_str: fmtUptime(os.uptime()),
        memory: { total: fmtBytes(total), free: fmtBytes(free), used: fmtBytes(total - free), usage: ((total - free) / total * 100).toFixed(1) + '%' },
        load: os.loadavg().map(n => +n.toFixed(2)),
        engine: { version: '3.2.0', port: PORT, uptime: process.uptime().toFixed(0) + 's' }
      });
      return;
    }
    if (route === '/ping') { jr(res, 200, { ok: true, pong: true }); return; }

    // ═══ GET /auth/whitelist — 查看白名单（需认证） ═══
    if (route === '/auth/whitelist') {
      const authResult = auth(req.headers);
      if (!authResult.ok) { jr(res, 401, { error: authResult.reason }); return; }
      const whitelist = loadWhitelist();
      jr(res, 200, {
        ok: true,
        server_id: SERVER_ID,
        triads: whitelist.triads.map(t => ({
          server: t.server,
          human: t.human,
          personalities: t.personalities,
          bound_servers: t.bound_servers
        }))
      });
      return;
    }
  }

  // ═══ 需要认证的 POST 端点 ═══
  if (req.method !== 'POST') { jr(res, 405, { error: '只接受 POST 请求 · 可用端点: /auth/request, /auth/confirm, /auth/register, /auth/whitelist (GET或POST), /health (GET), /status (GET), /ping (GET)' }); return; }

  const authResult = auth(req.headers);
  if (!authResult.ok) {
    log('WARN', 'auth_failed', authResult.reason);
    jr(res, 401, { error: authResult.reason });
    return;
  }

  const identity = authResult.identity;
  log('INFO', 'auth_ok', identity.pid + ' (' + identity.name + ')');

  const body = await parseBody(req);
  if (!body) { jr(res, 400, { error: '无效 JSON' }); return; }

  // ═══ 工作会话：一次验证码开启，结束信号立即吊销 ═══
  if (route === '/auth/session/request') {
    cleanExpiredOps();
    const sourceServer = body.source_server || identity.server || SERVER_ID;
    const scopes = Array.isArray(body.scopes) ? [...new Set(body.scopes)] : ['code-repo'];
    const action = getAction(body.action);
    if (!action) { jr(res, 400, { error: '必须指定已登记 action', available: listActions() }); return; }
    if (!scopes.includes(action.scope)) { jr(res, 400, { error: 'action 与 scopes 不匹配', required_scope: action.scope }); return; }
    if (!scopes.length || scopes.some(s => !OP_TYPES[s])) { jr(res, 400, { error: '无效 scopes', available: Object.keys(OP_TYPES) }); return; }
    const wlResult = checkWhitelist(sourceServer, identity.pid);
    if (!wlResult.ok) { jr(res, 403, { error: wlResult.reason }); return; }
    const triad = wlResult.triad;
    const code = generateCode(), challengeId = crypto.randomBytes(16).toString('hex');
    pendingOps[challengeId] = { code, expires: Date.now()/1000 + CODE_TTL, is_session: true, action: body.action, scopes, caller: identity, server: sourceServer, target_email: triad.email, target_name: triad.human };
    const sent = await sendVerificationEmail(code, scopes[0], identity.name, identity.pid, '开启工作会话 · 范围: ' + scopes.join(', '), triad.email, triad.human, SERVER_ID);
    jr(res, 200, { ok:true, challenge_id:challengeId, email_sent:sent, email_mode:'whitelist', scopes, expires_in:CODE_TTL, message:'验证码已发送到固定登记邮箱' });
    return;
  }

  if (route === '/auth/session/confirm') {
    cleanExpiredOps();
    const op = pendingOps[body.challenge_id || ''];
    if (!op || !op.is_session) { jr(res, 404, { error:'会话挑战不存在或已过期' }); return; }
    if (op.caller.pid !== identity.pid) { jr(res, 403, { error:'会话身份不匹配' }); return; }
    if (op.code !== String(body.code || '')) { jr(res, 403, { error:'验证码错误' }); return; }
    delete pendingOps[body.challenge_id];
    const created = sessions.create(identity, op.server, op.scopes, [op.action]);
    log('INFO','session_started','pid='+identity.pid+' scopes='+op.scopes.join(','));
    jr(res,200,{ok:true,session_token:created.token,scopes:op.scopes,expires_in:created.expires_in,idle_timeout:created.idle_timeout,message:'工作会话已开启'});
    return;
  }

  if (route === '/auth/session/exec') {
    const sessionToken = req.headers['x-session-token'] || body.session_token || '';
    const opType = body.op_type || 'code-repo';
    const sourceServer = body.source_server || identity.server || SERVER_ID;
    const action = getAction(body.action);
    const verified = sessions.verify(sessionToken, identity, sourceServer, opType, body.action);
    if (!verified.ok) { jr(res,403,{error:verified.reason}); return; }
    if (!action) { jr(res,400,{error:'必须指定已登记 action',available:listActions()}); return; }
    if (action.scope !== opType) { jr(res,400,{error:'action 与 op_type 不匹配',required_scope:action.scope}); return; }
    const result = await runAuthorizedAction(body.action, body.timeout);
    log('INFO','session_exec','pid='+identity.pid+' op='+opType+' code='+result.code);
    jr(res,200,{ok:true,executed:true,session:true,exit_code:result.code,stdout:result.stdout,stderr:result.stderr});
    return;
  }

  if (route === '/auth/session/end') {
    const sessionToken = req.headers['x-session-token'] || body.session_token || '';
    const ended = sessions.end(sessionToken, identity);
    log('INFO','session_ended','pid='+identity.pid+' ended='+ended);
    jr(res,ended?200:404,{ok:ended,message:ended?'工作会话已结束':'会话不存在'});
    return;
  }

  // ═══ /auth/request — 发起操作请求 ═══
  if (route === '/auth/request') {
    cleanExpiredOps();

    const opType = body.op_type || 'code-repo';
    const description = body.description || '未提供描述';
    const sourceServer = body.source_server || identity.server || SERVER_ID;
    if (body.email || body.target_name) { jr(res, 400, { error: '禁止客户端指定邮箱 · 请先注册服务器白名单' }); return; }

    if (!OP_TYPES[opType]) {
      jr(res, 400, { error: '未知操作类型: ' + opType, available: Object.keys(OP_TYPES) });
      return;
    }

    const action = getAction(body.action);
    if (!action) { jr(res, 400, { error: '必须指定已登记 action', available: listActions() }); return; }
    if (action.scope !== opType) { jr(res, 400, { error: 'action 与 op_type 不匹配', required_scope: action.scope }); return; }

    let targetEmail, targetName;

    {
      const wlResult = checkWhitelist(sourceServer, identity.pid);
      if (!wlResult.ok) {
        log('WARN', 'whitelist_denied', wlResult.reason);
        // 不在白名单也不带邮箱 → 不响应（防恶意刷邮箱）
        jr(res, 403, { error: wlResult.reason });
        return;
      }
      const triad = wlResult.triad;
      targetEmail = triad.email;
      targetName = triad.human;
    }

    // 生成验证码
    const code = generateCode();
    const challengeId = crypto.randomBytes(16).toString('hex');
    const now = Date.now() / 1000;

    pendingOps[challengeId] = {
      code: code,
      expires: now + CODE_TTL,
      op_type: opType,
      action: body.action,
      caller: { pid: identity.pid, name: identity.name },
      target_email: targetEmail,
      target_name: targetName,
      server: sourceServer
    };

    // 发送验证码邮件
    const cmdPreview = 'action=' + body.action;
    const sent = await sendVerificationEmail(code, opType, identity.name, identity.pid, cmdPreview, targetEmail, targetName, SERVER_ID);

    log('INFO', 'auth_request', 'challenge=' + challengeId.slice(0, 16) + ' op=' + opType + ' email_sent=' + sent);

    jr(res, 200, {
      ok: true,
      challenge_id: challengeId,
      email_sent: sent,
      email_mode: 'whitelist',
      caller: { pid: identity.pid, name: identity.name },
      op_type: opType,
      op_label: OP_TYPES[opType].label,
      cmd_preview: cmdPreview,
      expires_in: CODE_TTL,
      message: '验证码已发送到 ' + targetEmail + '，请查收后通过 /auth/confirm 确认操作'
    });
    return;
  }

  // ═══ /auth/confirm — 确认验证码并执行 ═══
  if (route === '/auth/confirm') {
    cleanExpiredOps();

    const challengeId = body.challenge_id || '';
    const code = body.code || '';

    if (!challengeId || !code) {
      jr(res, 400, { error: '缺少 challenge_id 或 code' });
      return;
    }

    if (!pendingOps[challengeId]) {
      jr(res, 404, { error: 'challenge_id 不存在或已过期' });
      return;
    }

    const op = pendingOps[challengeId];
    const now = Date.now() / 1000;

    if (now > op.expires) {
      delete pendingOps[challengeId];
      jr(res, 410, { error: '验证码已过期 · 请重新发起 /auth/request' });
      return;
    }

    if (op.code !== code) {
      log('WARN', 'auth_confirm_wrong_code', challengeId.slice(0, 16));
      jr(res, 403, { error: '验证码错误' });
      return;
    }

    // 验证通过 → 执行命令
    delete pendingOps[challengeId];
    log('INFO', 'auth_confirm_ok', challengeId.slice(0, 16) + ' op=' + op.op_type + ' caller=' + op.caller.pid);

    const result = await runAuthorizedAction(op.action, body.timeout);

    jr(res, 200, {
      ok: true,
      executed: true,
      op_type: op.op_type,
      action: op.action,
      caller: op.caller,
      exit_code: result.code,
      stdout: result.stdout,
      stderr: result.stderr,
      message: '命令已执行 · 操作完成'
    });
    return;
  }

  // ═══ /auth/whitelist — 查看白名单 ═══
  if (route === '/auth/whitelist') {
    const whitelist = loadWhitelist();
    jr(res, 200, {
      ok: true,
      server_id: SERVER_ID,
      triads: whitelist.triads.map(t => ({
        server: t.server,
        human: t.human,
        personalities: t.personalities,
        bound_servers: t.bound_servers
      }))
    });
    return;
  }

  // ═══ /auth/register — 注册新三元组（需主权者验证码） ═══
  if (route === '/auth/register') {
    const newServer = body.server || '';
    const newHuman = body.human || '';
    const newEmail = body.email || '';
    const newPersonalities = body.personalities || [];

    if (!newServer || !newHuman || !newEmail) {
      jr(res, 400, { error: '缺少必要字段: server, human, email' });
      return;
    }

    // 注册也需要验证码（发给主权者 ICE-GL∞）
    const whitelist = loadWhitelist();
    const sovereign = whitelist.triads.find(t => t.human === 'ICE-GL∞');
    if (!sovereign) {
      jr(res, 500, { error: '主权者信息缺失' });
      return;
    }

    // 生成验证码发到主权者邮箱
    const regCode = generateCode();
    const regCid = crypto.randomBytes(16).toString('hex');

    pendingOps[regCid] = {
      code: regCode,
      expires: Date.now() / 1000 + CODE_TTL,
      op_type: 'system-arch',
      cmd: 'REGISTER:' + JSON.stringify({ server: newServer, human: newHuman, email: newEmail, personalities: newPersonalities }),
      caller: identity,
      target_email: sovereign.email,
      target_name: sovereign.human,
      server: SERVER_ID,
      is_register: true,
      register_data: { server: newServer, human: newHuman, email: newEmail, personalities: newPersonalities }
    };

    const sent = await sendVerificationEmail(regCode, 'system-arch', identity.name, identity.pid,
      '注册新三元组: ' + newServer + ' + ' + newHuman + ' + ' + newPersonalities.join(','),
      sovereign.email, sovereign.human, SERVER_ID);

    jr(res, 200, {
      ok: true,
      challenge_id: regCid,
      email_sent: sent,
      message: '注册请求验证码已发送到主权者邮箱 ' + sovereign.email + '，确认后生效'
    });
    return;
  }

  // ═══ /exec — 旧接口兼容（自动转发到验证码流程） ═══
  if (route === '/exec') {
    if (!body.action) { jr(res, 400, { error: '缺少 action 参数' }); return; }

    // v3 安全策略: /exec 不再直接执行，返回提示要求走验证码流程
    log('WARN', 'exec_blocked_v3', 'caller=' + identity.pid + ' 尝试绕过验证码调用 /exec');

    jr(res, 403, {
      error: 'Gatekeeper v3 安全策略: /exec 已停用 · 请使用分层授权流程',
      required_flow: 'POST /auth/request → 邮箱验证码 → POST /auth/confirm',
      help: '将原有的 /exec 请求改为: ① POST /auth/request (action + op_type) → ② 查收邮箱验证码 → ③ POST /auth/confirm (challenge_id + code)',
      example: {
        step1: 'curl -X POST http://' + SERVER_ID + ':' + PORT + '/auth/request -H "Authorization: Bearer <token>" -H "Content-Type: application/json" -d \'{"action":"sync-status","op_type":"code-repo"}\'',
        step2: 'curl -X POST http://' + SERVER_ID + ':' + PORT + '/auth/confirm -H "Authorization: Bearer <token>" -H "Content-Type: application/json" -d \'{"challenge_id":"<从step1返回>","code":"<邮箱验证码>"}\''
      }
    });
    return;
  }

  // ═══ /hlpd — HLDP 模块执行（同样需要验证码） ═══
  if (route === '/hlpd') {
    if (!body.hlpd) { jr(res, 400, { error: '缺少 hlpd 参数' }); return; }
    log('WARN', 'hlpd_blocked_v3', 'HLDP 执行需要验证码授权');
    jr(res, 403, {
      error: 'Gatekeeper v3: HLDP 执行需要验证码授权',
      required_flow: 'POST /auth/request → 邮箱验证码 → POST /auth/confirm'
    });
    return;
  }

  // ═══ /file/read /file/write /file/list — 文件操作（只读无需验证，写入需要） ═══
  if (route === '/file/read') {
    if (!body.path) { jr(res, 400, { error: '缺少 path 参数' }); return; }
    log('INFO', 'file_read', body.path);
    try {
      const content = fs.readFileSync(body.path, 'utf-8');
      const stat = fs.statSync(body.path);
      jr(res, 200, { ok: true, path: body.path, size: stat.size, modified: stat.mtime.toISOString(), content: content.slice(0, MAX_OUTPUT) });
    } catch (e) { jr(res, 404, { ok: false, error: e.message }); }
    return;
  }

  if (route === '/file/list') {
    const p = (body && body.path) || '.';
    log('INFO', 'file_list', p);
    try {
      const items = fs.readdirSync(p, { withFileTypes: true });
      const files = [];
      for (const x of items) { files.push({ name: x.name, type: x.isDirectory() ? 'dir' : 'file' }); }
      jr(res, 200, { ok: true, path: p, files: files });
    } catch (e) { jr(res, 404, { ok: false, error: e.message }); }
    return;
  }

  if (route === '/file/write') {
    log('WARN', 'file_write_blocked_v3', '文件写入需要验证码授权');
    jr(res, 403, {
      error: 'Gatekeeper v3: 文件写入需要验证码授权',
      required_flow: 'POST /auth/request { action: "已登记动作", op_type: "system-arch" } → 验证码 → /auth/confirm'
    });
    return;
  }

  // 未知路由
  jr(res, 404, { error: '未知路径: ' + route, available: ['/auth/request', '/auth/confirm', '/auth/whitelist', '/auth/register', '/health', '/status', '/ping', '/file/read', '/file/list'] });
});

// ═══════════════════════════════════════
// 启动
// ═══════════════════════════════════════
server.on('error', (e) => {
  if (e.code === 'EADDRINUSE') {
    log('FATAL', 'eaddrinuse', '端口 ' + PORT + ' 已被占用');
    console.error('❌ 端口 ' + PORT + ' 已被占用，无法启动。');
    setTimeout(() => process.exit(1), 3000);
  } else {
    log('FATAL', 'server_error', e.message);
    process.exit(1);
  }
});

server.listen(PORT, HOST, () => {
  log('INFO', 'startup', 'host=' + HOSTNAME + ' bind=' + HOST + ' port=' + PORT + ' v3.2.0 server_id=' + SERVER_ID);
  console.log('  ⚔️ 光湖驱动引擎 v3.2 · 工作会话授权 · 已就绪');
  console.log('  主机名: ' + HOSTNAME + '  标识: ' + SERVER_ID + '  端口: ' + PORT);
  console.log('  安全策略: Token=身份标识 · 操作=验证码授权 · 白名单=三元组校验');
});

process.on('SIGTERM', () => { log('INFO', 'shutdown', 'SIGTERM'); server.close(() => process.exit(0)); });
process.on('SIGINT', () => { log('INFO', 'shutdown', 'SIGINT'); server.close(() => process.exit(0)); });
process.on('uncaughtException', (e) => { log('ERROR', 'uncaught', e.message); });
process.on('unhandledRejection', (e) => { log('ERROR', 'unhandled', e.message); });
