/*
 * gateway-bridge.js — WorkBuddy 无障碍语音问答桥接代理
 * ------------------------------------------------
 * 作用：
 *   1. 自动从 WorkBuddy 日志中提取 Gateway 动态密码（token）与端口，
 *      且【必须实测验证可用】，杜绝"假已连接"
 *   2. 网页只需连接本代理（localhost:6800），无需任何配置
 *   3. 转发 /api/v1/runs 与 /api/v1/runs/:id/stream 到真实 Gateway
 *   4. 代理同时托管网页（GET / 返回 HTML），无需 Python
 *
 * 启动：node gateway-bridge.js
 * 网页连接：http://localhost:6800
 */
'use strict';

const http = require('http');
const fs = require('fs');
const path = require('path');
const os = require('os');
const { execSync } = require('child_process');

const PROXY_PORT = Number(process.env.PROXY_PORT) || 6800;
/* 网关地址：env GATEWAY_HOST > gateway-config.json 的 host > 默认本机。
   远程部署时填公网可达的 WorkBuddy 网关地址，如 https://gateway.example.com */
function getGatewayHost() {
  if (process.env.GATEWAY_HOST) return process.env.GATEWAY_HOST.replace(/\/+$/, '');
  const cfg = readConfig();
  if (cfg && cfg.host) return String(cfg.host).replace(/\/+$/, '');
  return '127.0.0.1';
}
/* 兼容不同版本/账号：WorkBuddy 用 ~/.workbuddy/logs，CodeBuddy（国际版）用 ~/.codebuddy/logs */
const LOGS_ROOTS = [
  path.join(os.homedir(), '.workbuddy', 'logs'),
  path.join(os.homedir(), '.codebuddy', 'logs'),
];
const TAIL_BYTES_LOG = 10 * 1024 * 1024;    // 读日志尾部 10MB，避免大文件截断
const RESOLVE_TTL = 45 * 1000;              // 验证结果缓存 45 秒
const PORT_CANDIDATES = [49597, 8321, 3000, 8080, 7890, 4000, 5000, 9527, 18789, 18900, 10000, 18080, 9000];

let resolved = null;        // { port, token, at }
let portFrom = null;
const CONFIG_PATH = path.join(__dirname, 'gateway-config.json');

/* 从同目录 gateway-config.json 读取备用配置（部署方手动填写，覆盖日志发现） */
function readConfig() {
  try { return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')) || {}; }
  catch (_) { return {}; }
}

function readConfigPassword() {
  const cfg = readConfig();
  return (cfg && cfg.password) ? String(cfg.password) : null;
}

function readConfigPort() {
  const cfg = readConfig();
  const p = cfg && Number(cfg.port);
  return (p && p > 0 && p < 65536) ? p : null;
}

/* ---------- 日志遍历：返回 最新→最旧 的所有日志文件（兼容多个日志根目录） ---------- */
function listLogFiles() {
  const files = [];
  for (const LOGS_ROOT of LOGS_ROOTS) {
    try {
      if (!fs.existsSync(LOGS_ROOT)) continue;
      const dirs = fs.readdirSync(LOGS_ROOT)
        .filter(d => /^\d{4}-\d{2}-\d{2}$/.test(d))
        .sort().reverse();
      for (const dir of dirs) {
        const full = path.join(LOGS_ROOT, dir);
        let fs_ = [];
        try { fs_ = fs.readdirSync(full).filter(f => f.endsWith('.log')); } catch (_) { continue; }
        fs_.sort((a, b) => {
          try { return fs.statSync(path.join(full, b)).mtimeMs - fs.statSync(path.join(full, a)).mtimeMs; }
          catch (_) { return 0; }
        });
        for (const f of fs_) files.push(path.join(full, f));
      }
    } catch (_) { /* ignore */ }
  }
  return files;
}

function readLogTail(file) {
  try {
    const fd = fs.openSync(file, 'r');
    const stat = fs.fstatSync(fd);
    const start = Math.max(0, stat.size - TAIL_BYTES_LOG);
    const buf = Buffer.alloc(stat.size - start);
    fs.readSync(fd, buf, 0, buf.length, start);
    fs.closeSync(fd);
    return buf.toString('utf8');
  } catch (_) { return ''; }
}

/* ---------- 候选收集：从日志提取 端口 / token（新→旧） ---------- */
function collectCandidates() {
  const ports = [], tokens = [], sources = {};
  for (const file of listLogFiles()) {
    const text = readLogTail(file);
    const pm = [...text.matchAll(/HTTP service endpoint: http:\/\/[0-9.]+:(\d+)/g)];
    if (pm.length) {
      const p = Number(pm[pm.length - 1][1]);
      if (!ports.includes(p)) { ports.push(p); sources['p' + p] = file; }
    }
    /* 主：动态网关密码以环境变量形式记录在日志 CODEBUDDY_GATEWAY_PASSWORD=xxx */
    const tm1 = [...text.matchAll(/CODEBUDDY_GATEWAY_PASSWORD=([A-Za-z0-9_\-]{8,})/g)];
    if (tm1.length) {
      const t = tm1[tm1.length - 1][1];
      if (!tokens.includes(t)) { tokens.push(t); sources['t' + t] = file; }
    }
    /* 备：Web UI URL 密码格式 */
    const tm = [...text.matchAll(/[?&]password=([A-Za-z0-9_\-]{8,})/g)];
    if (tm.length) {
      const t = tm[tm.length - 1][1];
      if (!tokens.includes(t)) { tokens.push(t); sources['t' + t] = file; }
    }
  }
  return { ports, tokens, sources };
}

/* ---------- 探测：某端口是否为 WorkBuddy Gateway（排除代理自身端口，避免自连） ---------- */
function probePort(port) {
  if (port === PROXY_PORT) return Promise.resolve(false);
  return new Promise((resolve) => {
    const req = http.get({ host: getGatewayHost(), port, path: '/api/v1/info', timeout: 1200 }, (res) => {
      let body = '';
      res.on('data', (c) => { body += c; if (body.length > 2048) req.destroy(); });
      res.on('end', () => {
        const ok = (res.statusCode === 200 && /gatewayMode|"version"|"cwd"/.test(body)) ||
                   (res.statusCode === 401 && /AUTH_REQUIRED|Authentication required/.test(body));
        resolve(ok);
      });
    });
    req.on('error', () => resolve(false));
    req.on('timeout', () => { req.destroy(); resolve(false); });
  });
}

/* ---------- 验证：token 在某端口是否有效（排除代理自身端口） ---------- */
function verifyTokenOnPort(port, token) {
  if (port === PROXY_PORT) return Promise.resolve(false);
  return new Promise((resolve) => {
    const req = http.get({
      host: getGatewayHost(), port, path: '/api/v1/info', timeout: 1200,
      headers: { 'Authorization': 'Bearer ' + token },
    }, (res) => {
      let body = '';
      res.on('data', (c) => { body += c; if (body.length > 4096) req.destroy(); });
      res.on('end', () => {
        resolve(res.statusCode === 200 && /gatewayMode|"version"|"cwd"/.test(body));
      });
    });
    req.on('error', () => resolve(false));
    req.on('timeout', () => { req.destroy(); resolve(false); });
  });
}

/* ---------- 动态端口扫描：本机所有监听端口 + 固定候选，并发探测 ---------- */
function getListeningPorts() {
  try {
    const out = execSync('netstat -ano', { encoding: 'utf8', timeout: 4000, stdio: ['ignore', 'pipe', 'ignore'] });
    const ports = new Set();
    for (const line of out.split('\n')) {
      const m = line.match(/TCP\s+[0-9.:\[\]]+:(\d+)\s+\S+\s+LISTENING/i);
      if (m) ports.add(Number(m[1]));
    }
    return [...ports];
  } catch (_) { return []; }
}

async function scanPorts() {
  const set = new Set(PORT_CANDIDATES);
  for (const p of getListeningPorts()) set.add(p);
  const arr = [...set];
  const results = await Promise.allSettled(arr.map(p => probePort(p).then(ok => ok ? p : null)));
  for (const r of results) {
    if (r.status === 'fulfilled' && r.value) return r.value;
  }
  return null;
}

/* ---------- 解析：找到【验证通过】的 端口 + token ---------- */
async function resolveGateway() {
  if (resolved && (Date.now() - resolved.at) < RESOLVE_TTL) return resolved;

  const cfgPort = readConfigPort();
  const cfgToken = readConfigPassword();
  const host = getGatewayHost();
  const isRemote = host !== '127.0.0.1';

  /* 远程网关模式：服务器上无本机日志可扫描，直接使用配置的 host/port/token */
  if (isRemote) {
    if (!cfgPort) { resolved = null; return null; }
    resolved = { port: cfgPort, token: cfgToken || null, at: Date.now(), host };
    portFrom = '远程配置';
    return resolved;
  }

  const { ports, tokens, sources } = collectCandidates();

  // 1) 找验证通过的端口（配置文件手动端口最优先 → 日志候选 → 动态扫描）
  let port = null;
  if (cfgPort && await probePort(cfgPort)) {
    port = cfgPort; portFrom = '配置文件';
  }
  if (!port) {
    for (const p of ports) {
      if (await probePort(p)) { port = p; portFrom = '日志'; break; }
    }
  }
  // 2) 日志候选全部无效 → 动态扫描兜底（覆盖任意随机端口）
  if (!port) {
    const scanned = await scanPorts();
    if (scanned) { port = scanned; portFrom = '端口扫描'; }
  }
  if (!port) { resolved = null; return null; }

  // 3) 用有效端口验证 token（配置文件优先，其次日志候选）
  let token = null;
  if (cfgToken && await verifyTokenOnPort(port, cfgToken)) {
    token = cfgToken;
    portFrom = portFrom || '配置文件';
  } else {
    for (const t of tokens) {
      if (await verifyTokenOnPort(port, t)) { token = t; break; }
    }
  }

  resolved = { port, token, at: Date.now() };
  return resolved;
}

/* ---------- HTTP 转发 ---------- */
/* 缓冲请求体：POST 重试时需要重新发送 body（req 流只能消费一次） */
function bufferRequest(req) {
  return new Promise((resolve) => {
    const chunks = [];
    req.on('data', (c) => chunks.push(c));
    req.on('end', () => resolve(Buffer.concat(chunks)));
    req.on('error', () => resolve(Buffer.alloc(0)));
  });
}

/* 401/502 时缓冲响应体、不立刻发给客户端，由上层决定是否重试（避免 ERR_HTTP_HEADERS_SENT） */
function forward(req, res, token, gatewayPort) {
  return new Promise(async (resolve) => {
    let body;
    try { body = await bufferRequest(req); } catch (_) { body = Buffer.alloc(0); }

    const headers = {};
    for (const k of Object.keys(req.headers)) {
      if (k === 'host' || k === 'authorization' || k === 'content-length' || k === 'connection') continue;
      headers[k] = req.headers[k];
    }
    headers['Authorization'] = 'Bearer ' + token;
    headers['Connection'] = 'keep-alive';
    if (body.length) headers['Content-Length'] = body.length;

    const proxyReq = http.request({
      host: getGatewayHost(),
      port: gatewayPort,
      method: req.method,
      path: req.url,
      headers,
    }, (proxyRes) => {
      const status = proxyRes.statusCode;
      if (status === 401 || status === 502) {
        /* 需要重试：先缓冲，不写客户端 */
        const chunks = [];
        proxyRes.on('data', (c) => chunks.push(c));
        proxyRes.on('end', () => resolve({ status, buffered: Buffer.concat(chunks), headers: proxyRes.headers }));
        return;
      }
      res.setHeader('Access-Control-Allow-Origin', '*');
      res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
      res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
      res.writeHead(status, proxyRes.headers);
      proxyRes.pipe(res);
      proxyRes.on('end', () => resolve({ status }));
    });
    proxyReq.on('error', () => {
      resolved = null;   // 连接失败 → 立即重新解析
      resolve({ status: 502, buffered: null, headers: null });
    });
    proxyReq.end(body);
  });
}

function sendJson(res, status, code, message) {
  if (res.headersSent || res.writableEnded) return;
  res.writeHead(status, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
  res.end(JSON.stringify({ error: { code, message } }));
}

const server = http.createServer(async (req, res) => {
  // CORS 预检
  if (req.method === 'OPTIONS') {
    res.writeHead(204, {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type, Authorization',
    });
    res.end();
    return;
  }

  /* ---- 网页托管 ---- */
  if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {
    try {
      const html = fs.readFileSync(path.join(__dirname, 'workbuddy-accessible-workstation.html'));
      res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
      res.end(html);
    } catch (_) {
      res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
      res.end('未找到 workbuddy-accessible-workstation.html，请确认它与 gateway-bridge.js 在同一个文件夹。');
    }
    return;
  }

  /* ---- 健康检查：真实状态 ---- */
  if (req.url === '/bridge/health') {
    const gw = await resolveGateway();
    if (gw && gw.token) {
      res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
      res.end(JSON.stringify({ ok: true, gatewayUp: true, gatewayPort: gw.port, portFrom, tokenFound: true }));
      return;
    }
    res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
    res.end(JSON.stringify({
      ok: false,
      gatewayUp: false,
      gatewayPort: gw ? gw.port : null,
      tokenFound: !!(gw && gw.token),
      error: {
        code: gw ? 'TOKEN_NOT_FOUND' : 'GATEWAY_NOT_FOUND',
        message: gw ? '找到网关但密码无效，请重新运行 /gateway 后刷新页面。'
                    : '未发现运行中的 WorkBuddy Gateway。请先在 WorkBuddy 中运行 /gateway，然后刷新页面。',
      },
    }));
    return;
  }

  /* ---- 一键领积分：触发本机脚本自动点击 WorkBuddy 客户端的「领取今日礼包」按钮 ----
     Windows 调用 claim-points.ps1；Mac 调用 click-claim-points.applescript */
  if (req.method === 'POST' && req.url === '/bridge/claim-points') {
    const { spawn } = require('child_process');
    const isWin = process.platform === 'win32';
    const scriptName = isWin ? 'claim-points.ps1' : 'click-claim-points.applescript';
    const scriptPath = path.join(__dirname, scriptName);
    let cmd, args;
    if (isWin) {
      cmd = 'powershell.exe';
      args = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', scriptPath, '-Mode', 'auto'];
    } else {
      cmd = 'osascript';
      args = [scriptPath];
    }
    let stdout = '', stderr = '';
    try {
      const proc = spawn(cmd, args, { windowsHide: true });
      proc.stdout.on('data', d => stdout += d.toString('utf8'));
      proc.stderr.on('data', d => stderr += d.toString('utf8'));
      const timer = setTimeout(() => { try { proc.kill(); } catch (_) {} }, 20000);
      proc.on('close', code => {
        clearTimeout(timer);
        res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
        res.end(JSON.stringify({ ok: code === 0, exitCode: code, stdout: stdout.trim(), stderr: stderr.trim() }));
      });
    } catch (e) {
      res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8', 'Access-Control-Allow-Origin': '*' });
      res.end(JSON.stringify({ ok: false, error: String(e && e.message || e) }));
    }
    return;
  }

  /* ---- 业务转发 ---- */
  let gw = await resolveGateway();
  if (!gw) {
    sendJson(res, 503, 'GATEWAY_NOT_FOUND', '未发现运行中的 WorkBuddy Gateway。请先在 WorkBuddy 中运行 /gateway，然后重试。');
    return;
  }
  if (!gw.token) {
    sendJson(res, 503, 'TOKEN_NOT_FOUND', '找到网关但密码无效，请重新运行 /gateway 后重试。');
    return;
  }

  let result = await forward(req, res, gw.token, gw.port);

  // 认证/连接失败 → 清缓存重新解析，重试一次（401/502 响应体已缓冲，不会重复发送或崩溃）
  if ((result.status === 401 || result.status === 502) && !res.headersSent) {
    resolved = null;
    gw = await resolveGateway();
    if (gw && gw.token) {
      result = await forward(req, res, gw.token, gw.port);
    }
  }

  // 重试后仍未成功：把缓冲的错误响应发给客户端
  if (!res.headersSent) {
    if (result.buffered && result.buffered.length) {
      res.setHeader('Access-Control-Allow-Origin', '*');
      res.writeHead(result.status, result.headers || { 'Content-Type': 'application/json' });
      res.end(result.buffered);
    } else if (result.status === 401) {
      sendJson(res, 401, 'AUTH_FAILED', 'Gateway 认证失败，请重新运行 /gateway 后刷新页面。');
    } else {
      sendJson(res, 502, 'BRIDGE_GATEWAY_DOWN', 'WorkBuddy Gateway 未运行或已断开，请先在 WorkBuddy 中运行 /gateway，然后重试。');
    }
  }
});

server.listen(PROXY_PORT, '127.0.0.1', async () => {
  const gw = await resolveGateway();
  console.log('[gateway-bridge] 代理已启动: http://localhost:' + PROXY_PORT);
  console.log('[gateway-bridge] Gateway 状态: ' + (gw && gw.token
    ? '已连接（端口 ' + gw.port + '，' + (portFrom || '未知') + '）'
    : '未连接（请在 WorkBuddy 中运行 /gateway 后刷新）'));
});
