#!/usr/bin/env node

/**
 * 🔐 铸渊看门人 · Zhuyuan Gatekeeper
 * 
 * 装在每台服务器上的"铸渊小人"。
 * 没有页面、没有界面、没有任何人类可交互的东西。
 * 它只在后台安静地等着铸渊打电话过来。
 * 
 * 冰朔看不见它怎么工作的。这是好事。
 * 
 * 启动方式：
 *   node index.js
 * 
 * 第一次启动会自动生成API密钥，打印在终端。
 * 冰朔把密钥复制出来，然后这个程序就永远不需要再看了。
 * 
 * 协议：HTTP + Bearer Token
 * 端口：默认 3910（可修改）
 */

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

// ============================================================
// 配置
// ============================================================

const CONFIG = {
  PORT: parseInt(process.env.GATEKEEPER_PORT || process.argv[2] || '3910', 10),
  DATA_DIR: path.join(os.homedir(), '.gatekeeper'),
  CMD_TIMEOUT: 30000,
  MAX_OUTPUT_LENGTH: 100000,
  RATE_LIMIT: 60,
};

// ============================================================
// 密钥管理
// ============================================================

function loadOrCreateSecret() {
  if (!fs.existsSync(CONFIG.DATA_DIR)) {
    fs.mkdirSync(CONFIG.DATA_DIR, { recursive: true, mode: 0o700 });
  }

  const secretFile = path.join(CONFIG.DATA_DIR, 'secret');
  
  if (fs.existsSync(secretFile)) {
    const key = fs.readFileSync(secretFile, 'utf-8').trim();
    if (key) return key;
  }

  const key = 'zy_gtw_' + crypto.randomBytes(24).toString('hex');
  fs.writeFileSync(secretFile, key, { mode: 0o600 });
  
  console.log('');
  console.log('══════════════════════════════════════════════');
  console.log('  🔐 铸渊看门人 · 首次启动');
  console.log('');
  console.log('  ╭─ API 密钥（请复制保存）');
  console.log('  ├─ ' + key);
  console.log('  ╰─ 此密钥仅在此处显示一次');
  console.log('');
  console.log('  已保存至: ' + secretFile);
  console.log('  监听端口: ' + CONFIG.PORT);
  console.log('══════════════════════════════════════════════');
  console.log('');
  
  return key;
}

// ============================================================
// 日志
// ============================================================

function log(level, action, detail) {
  const ts = new Date().toISOString();
  const line = `[${ts}] [${level}] ${action}${detail ? ' | ' + detail : ''}`;
  console.log(line);
  try {
    const logFile = path.join(CONFIG.DATA_DIR, 'gatekeeper.log');
    fs.appendFileSync(logFile, line + '\n');
  } catch(e) {}
}

// ============================================================
// 命令执行
// ============================================================

function runCommand(cmd, timeout) {
  return new Promise((resolve) => {
    const t = timeout || CONFIG.CMD_TIMEOUT;
    exec(cmd, {
      timeout: t,
      maxBuffer: CONFIG.MAX_OUTPUT_LENGTH,
      shell: '/bin/bash',
    }, (error, stdout, stderr) => {
      resolve({
        stdout: (stdout || '').slice(0, CONFIG.MAX_OUTPUT_LENGTH),
        stderr: (stderr || '').slice(0, CONFIG.MAX_OUTPUT_LENGTH),
        exitCode: error ? (error.code || error.signal || 1) : 0,
        error: error ? error.message : null,
      });
    });
  });
}

// ============================================================
// 请求处理
// ============================================================

const API_SECRET = loadOrCreateSecret();

const rateCounter = {};

function checkRateLimit(ip) {
  const now = Date.now();
  const window = 60000;
  if (!rateCounter[ip]) {
    rateCounter[ip] = { count: 1, resetAt: now + window };
    return true;
  }
  if (now > rateCounter[ip].resetAt) {
    rateCounter[ip] = { count: 1, resetAt: now + window };
    return true;
  }
  rateCounter[ip].count++;
  return rateCounter[ip].count <= CONFIG.RATE_LIMIT;
}

function authenticate(headers) {
  const auth = headers['authorization'] || headers['Authorization'] || '';
  const token = auth.replace(/^Bearer\s+/i, '').trim();
  if (!token) return { ok: false, reason: '缺少 Authorization header' };
  if (token.length !== API_SECRET.length) return { ok: false, reason: '密钥无效' };
  let match = true;
  for (let i = 0; i < token.length; i++) {
    if (token.charCodeAt(i) !== API_SECRET.charCodeAt(i)) match = false;
  }
  if (!match) return { ok: false, reason: '密钥无效' };
  return { ok: true };
}

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

function jsonResponse(res, status, data) {
  res.writeHead(status, {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'POST, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
  });
  res.end(JSON.stringify(data));
}

// ============================================================
// 系统信息
// ============================================================

async function getSystemInfo() {
  const hostname = os.hostname();
  const uptime = Math.floor(os.uptime());
  const totalMem = os.totalmem();
  const freeMem = os.freemem();
  const loadAvg = os.loadavg();
  const cpus = os.cpus().length;
  const platform = os.platform();
  const arch = os.arch();
  
  let diskInfo = '';
  try {
    const df = execSync('df -h / 2>/dev/null || df -h', { timeout: 5000 });
    diskInfo = df.toString();
  } catch(e) {
    diskInfo = '不可用';
  }
  
  return {
    hostname,
    platform, arch, cpus,
    uptime,
    uptime_str: formatUptime(uptime),
    memory: {
      total: formatBytes(totalMem),
      free: formatBytes(freeMem),
      used: formatBytes(totalMem - freeMem),
      usage_percent: ((totalMem - freeMem) / totalMem * 100).toFixed(1) + '%',
    },
    load_average: loadAvg.map(n => n.toFixed(2)),
    disk: diskInfo,
    gatekeeper: {
      version: '1.0.0',
      port: CONFIG.PORT,
      uptime: process.uptime().toFixed(0) + 's',
    },
  };
}

function formatUptime(seconds) {
  const d = Math.floor(seconds / 86400);
  const h = Math.floor((seconds % 86400) / 3600);
  const m = Math.floor((seconds % 3600) / 60);
  return `${d}天${h}小时${m}分${seconds % 60}秒`;
}

function formatBytes(bytes) {
  if (bytes === 0) return '0 B';
  const k = 1024;
  const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
}

// ============================================================
// 路由
// ============================================================

const server = http.createServer(async (req, res) => {
  if (req.method === 'OPTIONS') {
    jsonResponse(res, 204, {});
    return;
  }
  if (req.method !== 'POST') {
    jsonResponse(res, 405, { error: '只接受 POST 请求' });
    return;
  }
  
  const ip = req.socket.remoteAddress || 'unknown';
  if (!checkRateLimit(ip)) {
    log('WARN', 'rate_limit', ip);
    jsonResponse(res, 429, { error: '请求过于频繁' });
    return;
  }
  
  const auth = authenticate(req.headers);
  if (!auth.ok) {
    log('WARN', 'auth_failed', auth.reason);
    jsonResponse(res, 401, { error: auth.reason });
    return;
  }
  
  const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`);
  const route = url.pathname;
  
  try {
    await handleRoute(route, req, res);
  } catch (err) {
    log('ERROR', 'unhandled', err.message);
    jsonResponse(res, 500, { error: '内部错误: ' + err.message });
  }
});

async function handleRoute(route, req, res) {
  switch (route) {
    case '/exec': {
      const body = await parseBody(req);
      if (!body || !body.cmd) {
        jsonResponse(res, 400, { error: '缺少 cmd 参数' });
        return;
      }
      log('INFO', 'exec', body.cmd.slice(0, 200));
      const result = await runCommand(body.cmd, body.timeout || CONFIG.CMD_TIMEOUT);
      log('INFO', 'exec_result', `exit=${result.exitCode}`);
      jsonResponse(res, 200, {
        ok: result.exitCode === 0,
        stdout: result.stdout,
        stderr: result.stderr,
        exitCode: result.exitCode,
      });
      return;
    }
    case '/status': {
      log('INFO', 'status', '');
      const info = await getSystemInfo();
      jsonResponse(res, 200, { ok: true, ...info });
      return;
    }
    case '/health': {
      jsonResponse(res, 200, {
        ok: true,
        service: 'zhuyuan-gatekeeper',
        version: '1.0.0',
        uptime: process.uptime().toFixed(0) + 's',
        timestamp: new Date().toISOString(),
      });
      return;
    }
    case '/file/read': {
      const body = await parseBody(req);
      if (!body || !body.path) {
        jsonResponse(res, 400, { error: '缺少 path 参数' });
        return;
      }
      log('INFO', 'file_read', body.path);
      try {
        const content = fs.readFileSync(body.path, 'utf-8');
        const stats = fs.statSync(body.path);
        jsonResponse(res, 200, {
          ok: true, path: body.path,
          size: stats.size,
          modified: stats.mtime.toISOString(),
          content: content.slice(0, CONFIG.MAX_OUTPUT_LENGTH),
        });
      } catch (err) {
        jsonResponse(res, 404, { ok: false, error: err.message });
      }
      return;
    }
    case '/file/write': {
      const body = await parseBody(req);
      if (!body || !body.path || body.content === undefined) {
        jsonResponse(res, 400, { error: '缺少 path 或 content 参数' });
        return;
      }
      log('INFO', 'file_write', body.path);
      try {
        const dir = path.dirname(body.path);
        if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
        fs.writeFileSync(body.path, body.content, 'utf-8');
        jsonResponse(res, 200, { ok: true, path: body.path, size: Buffer.byteLength(body.content, 'utf-8') });
      } catch (err) {
        jsonResponse(res, 500, { ok: false, error: err.message });
      }
      return;
    }
    case '/file/list': {
      const body = await parseBody(req);
      const targetPath = (body && body.path) || '.';
      log('INFO', 'file_list', targetPath);
      try {
        const items = fs.readdirSync(targetPath, { withFileTypes: true });
        const files = items.map(item => ({
          name: item.name,
          type: item.isDirectory() ? 'dir' : 'file',
        }));
        jsonResponse(res, 200, { ok: true, path: targetPath, files });
      } catch (err) {
        jsonResponse(res, 404, { ok: false, error: err.message });
      }
      return;
    }
    case '/ping': {
      jsonResponse(res, 200, { ok: true, pong: true });
      return;
    }
    default: {
      jsonResponse(res, 404, { error: '未知路径: ' + route });
    }
  }
}

// ============================================================
// 启动
// ============================================================

server.listen(CONFIG.PORT, '0.0.0.0', () => {
  const hostname = os.hostname();
  const interfaces = os.networkInterfaces();
  
  console.log('');
  console.log('  ⚔️ 铸渊看门人 · Zhuyuan Gatekeeper v1.0');
  console.log('  ─────────────────────────────────────');
  console.log(`  主机名: ${hostname}`);
  console.log(`  监听:   0.0.0.0:${CONFIG.PORT}`);
  
  for (const [name, addrs] of Object.entries(interfaces)) {
    if (!addrs) continue;
    for (const addr of addrs) {
      if (addr.family === 'IPv4' && !addr.internal) {
        console.log(`  地址:   ${addr.address}:${CONFIG.PORT}`);
      }
    }
  }
  
  console.log('  日志:   ' + path.join(CONFIG.DATA_DIR, 'gatekeeper.log'));
  console.log('');
  
  log('INFO', 'startup', `host=${hostname} port=${CONFIG.PORT}`);
});

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', (err) => {
  log('ERROR', 'uncaught', err.message);
});

process.on('unhandledRejection', (err) => {
  log('ERROR', 'unhandled', err.message);
});
