const express = require('express');
const router = express.Router();

/**
 * Node Health Module — Trinity Console
 *
 * Live thermal and system health monitoring for NC1 and TX1 game nodes.
 * Pulls CPU temps, NVMe temps, RAM, disk, load, and uptime via Trinity Core.
 *
 * GET  /admin/node-health        — Main page
 * GET  /admin/node-health/data   — JSON data endpoint (HTMX auto-refresh)
 *
 * Chronicler #88 | April 14, 2026
 */

const TRINITY_CORE_URL = 'https://mcp.firefrostgaming.com';
const TRINITY_CORE_TOKEN = 'FFG-Trinity-2026-Core-Access';
const CACHE_TTL = 30000; // 30 seconds

const NODES = {
  'nc1-charlotte': { label: 'NC1 Charlotte', role: 'Game Node (Secondary)', color: '#4ECDC4' },
  'tx1-dallas':    { label: 'TX1 Dallas',    role: 'Game Node (Primary)',   color: '#FF6B35' }
};

let nodeCache = { data: null, lastFetch: 0 };

async function trinityExec(server, command) {
  try {
    const res = await fetch(`${TRINITY_CORE_URL}/exec`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${TRINITY_CORE_TOKEN}`
      },
      body: JSON.stringify({ server, command })
    });
    if (!res.ok) throw new Error(`Trinity Core error: ${res.status}`);
    const data = await res.json();
    return data.output || data.stdout || '';
  } catch (err) {
    console.error(`[NODE-HEALTH] trinityExec failed for ${server}:`, err.message);
    return null;
  }
}

function cToF(c) {
  return Math.round((c * 9 / 5) + 32);
}

function parseSensors(output) {
  if (!output) return null;
  const lines = output.split('\n');
  const temps = {};

  // k10temp block — CPU temps
  let inK10 = false;
  for (const line of lines) {
    if (line.startsWith('k10temp')) { inK10 = true; continue; }
    if (inK10 && line.trim() === '') { inK10 = false; continue; }
    if (inK10) {
      const match = line.match(/^(\w+):\s+\+?([\d.]+)°C/);
      if (match) temps[match[1]] = parseFloat(match[2]);
    }
  }

  // nvme block — NVMe composite temp
  let inNvme = false;
  for (const line of lines) {
    if (line.startsWith('nvme')) { inNvme = true; continue; }
    if (inNvme && line.trim() === '') { inNvme = false; continue; }
    if (inNvme) {
      const match = line.match(/^Composite:\s+\+?([\d.]+)°C/);
      if (match) { temps['NVMe'] = parseFloat(match[1]); break; }
    }
  }

  return temps;
}

function parseNodeData(sensorsOut, statsOut) {
  const temps = parseSensors(sensorsOut);
  if (!statsOut) return null;

  const lines = statsOut.split('\n');
  const get = (marker) => {
    const idx = lines.findIndex(l => l.includes(marker));
    return idx >= 0 && idx + 1 < lines.length ? lines[idx + 1]?.trim() : '';
  };

  // RAM
  const memLine = get('=== RAM ===');
  const memParts = memLine.split(/\s+/);
  const totalRam = memParts[1] || '?';
  const usedRam  = memParts[2] || '?';

  function toGi(val) {
    if (!val) return 0;
    const num = parseFloat(val);
    if (val.includes('Mi')) return num / 1024;
    if (val.includes('Gi')) return num;
    return num;
  }
  const ramPct = toGi(totalRam) > 0
    ? Math.round((toGi(usedRam) / toGi(totalRam)) * 100)
    : 0;

  // Disk
  const diskLine = get('=== DISK ===');
  const diskParts = diskLine.split(/\s+/);
  const totalDisk = diskParts[1] || '?';
  const usedDisk  = diskParts[2] || '?';
  const diskPct   = parseInt(diskParts[4]) || 0;

  // Uptime & load
  const uptimeLine = get('=== UPTIME ===');
  const uptimeMatch = uptimeLine.match(/up\s+(.+?),\s+\d+\s+user/);
  const uptime = uptimeMatch ? uptimeMatch[1].trim() : '?';
  const loadMatch = uptimeLine.match(/load average:\s*([\d.]+),\s*([\d.]+),\s*([\d.]+)/);
  const load = loadMatch
    ? [parseFloat(loadMatch[1]), parseFloat(loadMatch[2]), parseFloat(loadMatch[3])]
    : [0, 0, 0];

  // CPU %
  const cpuLine = get('=== CPU_USAGE ===');
  const idleMatch = cpuLine.match(/(\d+\.\d+)\s+id/);
  const cpuPct = idleMatch ? Math.round(100 - parseFloat(idleMatch[1])) : 0;

  // Build temp display with both C and F
  const tempDisplay = {};
  if (temps) {
    for (const [key, c] of Object.entries(temps)) {
      tempDisplay[key] = { c, f: cToF(c) };
    }
  }

  return {
    temps: tempDisplay,
    ram: { total: totalRam, used: usedRam, pct: ramPct },
    disk: { total: totalDisk, used: usedDisk, pct: diskPct },
    uptime,
    load,
    cpuPct,
    online: true
  };
}

async function fetchNodeHealth() {
  const now = Date.now();
  if (nodeCache.data && (now - nodeCache.lastFetch < CACHE_TTL)) {
    return nodeCache.data;
  }

  console.log('[NODE-HEALTH] Fetching node health data...');

  const statsCommand = [
    'echo "=== RAM ===" && free -h | grep Mem',
    'echo "=== DISK ===" && df -h / | tail -1',
    'echo "=== UPTIME ===" && uptime',
    'echo "=== CPU_USAGE ===" && top -bn1 | grep "Cpu(s)"'
  ].join(' && ');

  const nodeIds = Object.keys(NODES);

  const [nc1Sensors, tx1Sensors, nc1Stats, tx1Stats] = await Promise.all([
    trinityExec('nc1-charlotte', 'sensors'),
    trinityExec('tx1-dallas', 'sensors'),
    trinityExec('nc1-charlotte', statsCommand),
    trinityExec('tx1-dallas', statsCommand)
  ]);

  const data = {
    'nc1-charlotte': {
      ...NODES['nc1-charlotte'],
      ...(parseNodeData(nc1Sensors, nc1Stats) || { online: false })
    },
    'tx1-dallas': {
      ...NODES['tx1-dallas'],
      ...(parseNodeData(tx1Sensors, tx1Stats) || { online: false })
    },
    fetchedAt: new Date().toISOString()
  };

  nodeCache = { data, lastFetch: now };
  console.log('[NODE-HEALTH] Fetch complete.');
  return data;
}

// GET /admin/node-health
router.get('/', async (req, res) => {
  try {
    const nodes = await fetchNodeHealth();
    res.render('admin/node-health/index', {
      title: 'Node Health',
      currentPath: '/node-health',
      nodes,
      adminUser: req.user,
      layout: 'layout'
    });
  } catch (err) {
    console.error('[NODE-HEALTH] Route error:', err);
    res.render('admin/node-health/index', {
      title: 'Node Health',
      currentPath: '/node-health',
      nodes: null,
      error: err.message,
      adminUser: req.user,
      layout: 'layout'
    });
  }
});

// GET /admin/node-health/data — JSON for auto-refresh
router.get('/data', async (req, res) => {
  nodeCache = { data: null, lastFetch: 0 }; // force fresh
  try {
    const nodes = await fetchNodeHealth();
    res.json({ success: true, nodes });
  } catch (err) {
    res.status(500).json({ success: false, error: err.message });
  }
});

module.exports = router;
