#!/usr/bin/env node
/**
 * ═══════════════════════════════════════════════════════════
 * 🛡️ 铸渊运维守卫 · 主入口
 * ═══════════════════════════════════════════════════════════
 *
 * 编号: ZY-OPS-001
 * 端口: 3950
 * 签发: 铸渊 · ICE-GL-ZY001
 * 版权: 国作登字-2026-A-00037559
 *
 * 功能:
 *   - HTTP API 服务（供前端工单面板调用）
 *   - 定时巡检（快速5分钟 / 深度1小时 / 全量每天）
 *   - 自动修复 + 工单升级
 *   - 对话接口（LLM推理）
 *
 * 使用:
 *   node ops-agent/index.js              启动守卫服务
 *   pm2 start ops-agent/ecosystem.config.js
 */

'use strict';

const express = require('express');
const cron = require('node-cron');
const path = require('path');

const healthChecker = require('./health-checker');
const repairEngine = require('./repair-engine');
const memory = require('./memory');
const notifier = require('./notifier');
const llmClient = require('./llm-client');

// ── 进程保活 ──────────────────────────────

process.on('uncaughtException', (err) => {
  console.error('[OPS] 未捕获异常（进程保活）:', err.message);
  console.error('[OPS] 堆栈:', err.stack);
});

process.on('unhandledRejection', (reason) => {
  console.error('[OPS] 未处理的Promise拒绝（进程保活）:', reason);
});

// ── Express 应用 ──────────────────────────

const app = express();
const PORT = parseInt(process.env.OPS_AGENT_PORT || '3950', 10);
const MAX_RETRIES = parseInt(process.env.OPS_MAX_REPAIR_RETRIES || '3', 10);

app.use(express.json({ limit: '1mb' }));

// 静态文件 — 运维工单面板
app.use('/ops', express.static(path.join(__dirname, 'web')));

// ── SSE 实时推送 ──────────────────────────

const sseClients = new Set();

app.get('/ops/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'Access-Control-Allow-Origin': '*'
  });
  res.write('data: {"type":"connected","message":"运维守卫 SSE 已连接"}\n\n');

  sseClients.add(res);
  req.on('close', () => { sseClients.delete(res); });
});

function broadcast(eventType, data) {
  const payload = `data: ${JSON.stringify({ type: eventType, timestamp: new Date().toISOString(), ...data })}\n\n`;
  for (const client of sseClients) {
    try { client.write(payload); } catch { sseClients.delete(client); }
  }
}

// ── 工具执行（对话中自动诊断） ─────────────

const { execSync } = require('child_process');

/**
 * 根据意图自动执行诊断工具，结果注入给 LLM
 */
async function executeTools(intent, action, message) {
  const results = [];

  try {
    if (action === 'health_check' || action === 'pm2_status') {
      const quick = await healthChecker.quickCheck();
      let text = `## 服务状态\n${quick.summary}\n\n`;
      for (const s of quick.services) {
        text += `- ${s.name}(:${s.port}): ${s.status} ${s.latency ? s.latency + 'ms' : ''} ${s.error || ''}\n`;
      }
      results.push({ tool: '健康检查', result: text });
    }

    if (action === 'system_info') {
      const resources = healthChecker.getSystemResources();
      let text = `## 系统资源\n`;
      text += `- 内存: ${resources.memory.used_pct}% (${resources.memory.free_mb}MB空闲/${resources.memory.total_mb}MB总量)\n`;
      text += `- 磁盘: ${resources.disk.used_pct}% (${resources.disk.available_gb}GB可用)\n`;
      text += `- CPU: ${resources.cpus}核 · 负载: ${resources.load?.[0]?.toFixed(2)}\n`;
      text += `- 运行: ${resources.uptime_hours}小时\n`;
      results.push({ tool: '系统资源', result: text });
    }

    if (action === 'pm2_logs') {
      // 从消息中猜测进程名
      const processName = guessProcessFromMessage(message);
      if (processName) {
        const logs = getPM2Logs(processName, 20);
        if (logs.output) {
          results.push({ tool: `PM2日志(${processName})`, result: logs.output.slice(-2000) });
        }
        if (logs.errorOutput) {
          results.push({ tool: `PM2错误日志(${processName})`, result: logs.errorOutput.slice(-2000) });
        }
      }
    }

    if (action === 'nginx_status') {
      const nginx = healthChecker.getNginxStatus();
      results.push({
        tool: 'Nginx状态',
        result: `Nginx: ${nginx.status} · 配置${nginx.configOk ? '正常' : '异常'}`
      });
    }

    if (action === 'list_tickets') {
      const tickets = memory.getOpenTickets();
      if (tickets.length === 0) {
        results.push({ tool: '工单查询', result: '当前没有开放工单，系统运行正常。' });
      } else {
        let text = `当前有 ${tickets.length} 个开放工单:\n`;
        for (const t of tickets) {
          text += `- ${t.id}: ${t.title} [${t.severity}] — ${t.direction}\n`;
        }
        results.push({ tool: '工单查询', result: text });
      }
    }
  } catch (err) {
    console.error(`[OPS] 工具执行失败: ${err.message}`);
  }

  return results;
}

/**
 * 从用户消息中猜测他在问哪个进程
 */
function guessProcessFromMessage(message) {
  const lower = message.toLowerCase();
  if (/mcp|大脑|brain/.test(lower)) return 'age-os-mcp';
  if (/主站|server|3800/.test(lower)) return 'zhuyuan-server';
  if (/预览|preview|3801/.test(lower)) return 'zhuyuan-preview';
  if (/glada|3900/.test(lower)) return 'glada-agent';
  if (/novel|智库|4000/.test(lower)) return 'novel-api';
  if (/agent调度|scheduler/.test(lower)) return 'age-os-agents';
  return null;
}

/**
 * 获取 PM2 进程日志（最近 N 行）
 */
function getPM2Logs(processName, lines = 30) {
  // 安全校验
  const clean = processName.replace(/[^a-zA-Z0-9_-]/g, '');
  const allowed = new Set([
    'zhuyuan-server', 'zhuyuan-preview', 'novel-api',
    'age-os-mcp', 'age-os-agents', 'glada-agent', 'ops-agent'
  ]);
  if (!allowed.has(clean)) {
    throw new Error(`不允许查看进程 "${clean}" 的日志`);
  }

  let output = '';
  let errorOutput = '';
  try {
    output = execSync(`pm2 logs ${clean} --lines ${lines} --nostream 2>/dev/null || echo "无法获取日志"`, {
      encoding: 'utf-8',
      timeout: 10000
    }).trim();
  } catch {
    output = '(PM2日志获取失败)';
  }

  // 也尝试获取错误日志
  try {
    errorOutput = execSync(`pm2 logs ${clean} --err --lines ${lines} --nostream 2>/dev/null || echo ""`, {
      encoding: 'utf-8',
      timeout: 10000
    }).trim();
  } catch {
    errorOutput = '';
  }

  return {
    processName: clean,
    lines,
    output,
    errorOutput,
    timestamp: new Date().toISOString()
  };
}

// ── API: 健康检查 ─────────────────────────

app.get('/health', (_req, res) => {
  const stats = memory.loadStats();
  res.json({
    status: 'ok',
    agent: 'zy-ops-agent',
    version: '1.0.0',
    uptime: Math.round(process.uptime()),
    stats: {
      totalChecks: stats.totalChecks,
      totalRepairs: stats.totalRepairs,
      openTickets: memory.getOpenTickets().length
    },
    timestamp: new Date().toISOString()
  });
});

// ── API: 手动触发巡检 ─────────────────────

app.get('/api/ops/check/quick', async (_req, res) => {
  try {
    const result = await runQuickCheck();
    res.json(result);
  } catch (err) {
    res.status(500).json({ error: true, message: err.message });
  }
});

app.get('/api/ops/check/deep', async (_req, res) => {
  try {
    const result = await runDeepCheck();
    res.json(result);
  } catch (err) {
    res.status(500).json({ error: true, message: err.message });
  }
});

// ── API: 工单 ──────────────────────────────

app.get('/api/ops/tickets', (_req, res) => {
  const status = _req.query.status;
  let tickets;
  if (status === 'open') {
    tickets = memory.getOpenTickets();
  } else {
    tickets = memory.getAllTickets();
  }
  res.json({ tickets, total: tickets.length });
});

app.patch('/api/ops/tickets/:id', (req, res) => {
  const updated = memory.updateTicket(req.params.id, req.body);
  if (!updated) {
    return res.status(404).json({ error: true, message: '工单不存在' });
  }
  broadcast('ticket_updated', { ticket: updated });
  res.json({ ticket: updated });
});

// ── API: 对话 v2（多轮会话 + 工具调用） ──────

app.post('/api/ops/chat', async (req, res) => {
  const { message, sessionId: inputSessionId } = req.body;
  if (!message) {
    return res.status(400).json({ error: true, message: '缺少 message 字段' });
  }

  try {
    // 构建系统上下文
    const memoryContext = memory.buildMemoryContext();

    // 意图识别 → 自动执行相关工具
    const { intent, action } = llmClient.detectIntent(message);
    const toolResults = await executeTools(intent, action, message);

    // 构建实时上下文
    const context = memoryContext;

    // 多轮对话调用
    const result = await llmClient.chat(message, context, {
      sessionId: inputSessionId,
      toolResults
    });

    // 记录对话
    memory.logEvent('chat', {
      question: message.slice(0, 200),
      method: result.method,
      intent: result.intent,
      toolsUsed: result.toolsUsed
    });
    memory.incrementStat('totalChats');

    res.json({
      answer: result.answer,
      patternHints: result.patternHints,
      method: result.method,
      model: result.model,
      sessionId: result.sessionId,
      intent: result.intent,
      toolsUsed: result.toolsUsed,
      timestamp: new Date().toISOString()
    });
  } catch (err) {
    res.status(500).json({ error: true, message: `对话失败: ${err.message}` });
  }
});

// ── API: 会话管理 ─────────────────────────

app.get('/api/ops/sessions', (_req, res) => {
  res.json({ sessions: llmClient.listSessions() });
});

app.get('/api/ops/sessions/:id/history', (req, res) => {
  const history = llmClient.getSessionHistory(req.params.id);
  res.json({ sessionId: req.params.id, history });
});

// ── API: PM2 日志查看 ─────────────────────

app.get('/api/ops/pm2-logs/:name', (req, res) => {
  const name = req.params.name;
  const lines = parseInt(req.query.lines || '30', 10);
  const clampedLines = Math.min(Math.max(lines, 1), 200);
  try {
    const result = getPM2Logs(name, clampedLines);
    res.json(result);
  } catch (err) {
    res.status(500).json({ error: true, message: err.message });
  }
});

// ── API: 系统信息 ──────────────────────────

app.get('/api/ops/system-info', async (_req, res) => {
  try {
    const resources = healthChecker.getSystemResources();
    const pm2 = healthChecker.getPM2Status();
    const nginx = healthChecker.getNginxStatus();
    res.json({
      resources,
      pm2: pm2.map(p => ({
        name: p.name,
        status: p.status,
        restarts: p.restarts,
        memory_mb: Math.round((p.memory || 0) / 1024 / 1024),
        cpu: p.cpu,
        uptime_hours: Math.round((p.uptime || 0) / 3600000)
      })),
      nginx,
      timestamp: new Date().toISOString()
    });
  } catch (err) {
    res.status(500).json({ error: true, message: err.message });
  }
});

// ── API: 统计 ──────────────────────────────

app.get('/api/ops/stats', (_req, res) => {
  const stats = memory.loadStats();
  res.json(stats);
});

// ── API: 最近事件 ─────────────────────────

app.get('/api/ops/events', (req, res) => {
  const type = req.query.type;
  const limit = parseInt(req.query.limit || '20', 10);
  const events = memory.queryEvents({ type, limit });
  res.json({ events, total: events.length });
});

// ── 巡检 + 修复 + 告警逻辑 ─────────────────

/**
 * 处理升级：去重 + 邮件 + 广播
 * 同一 (relatedService+diagnosis) 已有 open 工单 → 累加 autoRepairAttempts，不发邮件、不广播
 * 否则 → 创建新工单 + 邮件 + 广播
 */
async function escalateIssue(repairResult) {
  const issue = repairResult.issue;
  const relatedService = issue.service;
  const diagnosis = issue.error || issue.status;

  const existing = memory.findOpenTicketByKey({ relatedService, diagnosis });
  if (existing) {
    const updated = memory.updateTicket(existing.id, {
      autoRepairAttempts: (existing.autoRepairAttempts || 0) + (repairResult.retryCount || MAX_RETRIES)
    });
    console.log(`[OPS] 已存在开放工单 ${existing.id}，仅累加修复尝试次数（不发邮件）`);
    return { ticket: updated, deduplicated: true };
  }

  const ticket = memory.createTicket({
    title: `${issue.service} - ${issue.error || issue.status}`,
    description: repairResult.message,
    severity: issue.severity,
    category: issue.category || 'service',
    direction: classifyDirection(issue),
    diagnosis,
    suggestedFix: issue.fix || '',
    relatedService,
    autoRepairAttempts: repairResult.retryCount || MAX_RETRIES
  });
  broadcast('new_ticket', { ticket });
  await notifier.notifyTicketCreated(ticket);
  return { ticket, deduplicated: false };
}

let isChecking = false;

async function runQuickCheck() {
  if (isChecking) return { skipped: true, reason: '上一次巡检尚未完成' };
  isChecking = true;

  try {
    const result = await healthChecker.quickCheck();
    memory.incrementStat('totalChecks');
    memory.updateStatField('lastQuickCheck', new Date().toISOString());

    // 有问题 → 尝试修复
    if (result.issues.length > 0) {
      const repairResults = repairEngine.autoRepair(result.issues, MAX_RETRIES);
      memory.incrementStat('totalRepairs', repairResults.filter(r => r.repaired).length);

      // 修不了的 → 创建工单（带去重）
      for (const r of repairResults) {
        if (r.escalate) {
          await escalateIssue(r);
        }
      }

      result.repairs = repairResults;

      // 高/紧急级别 → 邮件告警
      await notifier.alertOnIssues(result);
    }

    memory.logEvent('check', {
      type: 'quick',
      healthy: result.healthy,
      issueCount: result.issues.length,
      summary: result.summary
    });

    broadcast('check_complete', { type: 'quick', healthy: result.healthy, summary: result.summary });

    return result;
  } finally {
    isChecking = false;
  }
}

async function runDeepCheck() {
  if (isChecking) return { skipped: true, reason: '上一次巡检尚未完成' };
  isChecking = true;

  try {
    const result = await healthChecker.deepCheck();
    memory.incrementStat('totalChecks');
    memory.updateStatField('lastDeepCheck', new Date().toISOString());

    // 有问题 → 修复 + 告警（同 quickCheck 逻辑）
    if (result.issues.length > 0) {
      const repairResults = repairEngine.autoRepair(result.issues, MAX_RETRIES);
      memory.incrementStat('totalRepairs', repairResults.filter(r => r.repaired).length);

      for (const r of repairResults) {
        if (r.escalate) {
          await escalateIssue(r);
        }
      }

      result.repairs = repairResults;
      await notifier.alertOnIssues(result);
    }

    memory.logEvent('check', {
      type: 'deep',
      healthy: result.healthy,
      issueCount: result.issues.length,
      summary: result.summary
    });

    broadcast('check_complete', { type: 'deep', healthy: result.healthy, summary: result.summary });

    return result;
  } finally {
    isChecking = false;
  }
}

async function runDailyReport() {
  const report = await healthChecker.fullReport();
  memory.updateStatField('lastDailyReport', new Date().toISOString());
  memory.logEvent('daily_report', {
    reportId: report.report_id,
    healthy: report.healthy,
    issueCount: report.issues.length,
    summary: report.summary
  });

  await notifier.sendDailyReport(report);
  broadcast('daily_report', { reportId: report.report_id, summary: report.summary });

  return report;
}

/**
 * 判断问题处理方向
 */
function classifyDirection(issue) {
  const err = (issue.error || '').toLowerCase();
  const service = (issue.service || '').toLowerCase();

  // 密钥/配置问题 → 冰朔自己解决
  if (/api.*key|unauthorized|密钥|token/i.test(err)) {
    return '冰朔自行配置 — 检查 .env 文件中的API密钥';
  }
  // 代码错误 → 推给铸渊
  if (/syntaxerror|typeerror|referenceerror|代码/i.test(err)) {
    return '推给铸渊修代码 — 需要在GitHub仓库修复';
  }
  // 资源问题 → 冰朔确认
  if (/内存|磁盘|enomem|enospc/i.test(err)) {
    return '资源不足 — 可能需要升级服务器或清理空间';
  }
  // 进程问题 → 运维守卫已尝试修复
  if (/pm2|进程|restart|offline/i.test(err + service)) {
    return '进程异常 — 运维守卫已尝试自动重启';
  }
  return '待判断 — 需要进一步诊断';
}

// ── 定时调度 ──────────────────────────────

// 每5分钟快速巡检
cron.schedule('*/5 * * * *', () => {
  console.log('[OPS] ⏰ 定时快速巡检');
  runQuickCheck().catch(err => {
    console.error('[OPS] 快速巡检失败:', err.message);
  });
}, { timezone: 'Asia/Shanghai' });

// 每小时深度巡检
cron.schedule('0 * * * *', () => {
  console.log('[OPS] ⏰ 定时深度巡检');
  runDeepCheck().catch(err => {
    console.error('[OPS] 深度巡检失败:', err.message);
  });
}, { timezone: 'Asia/Shanghai' });

// 每天早上8点全量报告
cron.schedule('0 8 * * *', () => {
  console.log('[OPS] ⏰ 每日健康报告');
  runDailyReport().catch(err => {
    console.error('[OPS] 每日报告失败:', err.message);
  });
}, { timezone: 'Asia/Shanghai' });

// ── 启动服务 ──────────────────────────────

app.listen(PORT, '0.0.0.0', () => {
  console.log('═══════════════════════════════════════════════');
  console.log('  🛡️ 铸渊运维守卫 · ZY-OPS-001 已启动');
  console.log(`  端口: ${PORT}`);
  console.log(`  面板: http://127.0.0.1:${PORT}/ops/`);
  console.log('  巡检: 快速5分钟 · 深度1小时 · 全量每天8:00');
  console.log('  签发: 铸渊 · ICE-GL-ZY001');
  console.log('═══════════════════════════════════════════════');

  memory.logEvent('startup', { port: PORT, version: '1.0.0' });

  // 启动后立即做一次快速巡检
  setTimeout(() => {
    console.log('[OPS] 启动后首次巡检...');
    runQuickCheck().catch(err => {
      console.error('[OPS] 首次巡检失败:', err.message);
    });
  }, 5000);
});
