// Live system metrics — the fast path behind the Admin → System gauges.
//
// The periodic, host-side picture (disk, network, docker, per-app, 24h history)
// is produced by the webui_system_metrics generator into frontend/data/system/.
// This endpoint exists only to make the headline gauges *tick* every couple of
// seconds, so it is deliberately the cheapest, safest thing it can be:
//   - reads only /proc (no subprocess spawn, no docker, no privileges)
//   - CPU% from an in-memory delta of the previous /proc/stat read
//   - a single-flight + short cache so N open tabs cause ~1 /proc read/sec
//
// Namespace note: this runs *inside* the libreportal container. /proc/stat,
// /proc/meminfo and /proc/loadavg are not namespaced, so they report host-wide
// values that match the generator's numbers. /proc/net/dev IS per-netns (it
// would show only this container's traffic), so network is intentionally left
// to the host-side generator and not served here.
const express = require('express');
const fs = require('fs').promises;
const os = require('os');

const router = express.Router();

const CORES = os.cpus().length || 1;
const MIN_INTERVAL_MS = 750;   // serve cache to anything faster than this

let prevCpu = null;            // { total, idle } from the last read
let cache = null;              // { sample, at }
let inflight = null;           // dedupe concurrent cache-miss reads

async function readCpu() {
    const data = await fs.readFile('/proc/stat', 'utf8');
    const first = data.split('\n', 1)[0];                 // "cpu  u n s i io irq sirq steal ..."
    const n = first.trim().split(/\s+/).slice(1).map(Number);
    const idle = (n[3] || 0) + (n[4] || 0);               // idle + iowait
    const total = n.reduce((a, b) => a + (b || 0), 0);
    return { total, idle };
}

async function readMem() {
    const data = await fs.readFile('/proc/meminfo', 'utf8');
    const m = {};
    for (const line of data.split('\n')) {
        const mm = line.match(/^(\w+):\s+(\d+)/);
        if (mm) m[mm[1]] = parseInt(mm[2], 10) * 1024;    // kB -> bytes
    }
    const total = m.MemTotal || 0;
    const available = m.MemAvailable || 0;
    const used = Math.max(0, total - available);
    const swapTotal = m.SwapTotal || 0;
    const swapUsed = Math.max(0, swapTotal - (m.SwapFree || 0));
    return {
        total, used, available,
        percent: total ? +(used / total * 100).toFixed(1) : 0,
        swap_total: swapTotal, swap_used: swapUsed,
        swap_percent: swapTotal ? +(swapUsed / swapTotal * 100).toFixed(1) : 0
    };
}

async function readLoad() {
    const data = await fs.readFile('/proc/loadavg', 'utf8');
    const [l1, l5, l15] = data.trim().split(/\s+/).map(Number);
    return { load1: l1 || 0, load5: l5 || 0, load15: l15 || 0 };
}

async function sample() {
    const [cpuNow, memory, load] = await Promise.all([readCpu(), readMem(), readLoad()]);
    let percent = 0;
    if (prevCpu) {
        const dt = cpuNow.total - prevCpu.total;
        const di = cpuNow.idle - prevCpu.idle;
        if (dt > 0) percent = +Math.max(0, Math.min(100, (1 - di / dt) * 100)).toFixed(1);
    }
    prevCpu = cpuNow;
    return {
        cpu: {
            percent,
            cores: CORES,
            load1: load.load1, load5: load.load5, load15: load.load15,
            load1_percent: +Math.min(100, load.load1 / CORES * 100).toFixed(1)
        },
        memory,
        t: Date.now()
    };
}

router.get('/live', async (req, res) => {
    const now = Date.now();
    if (cache && (now - cache.at) < MIN_INTERVAL_MS) {
        res.set('Cache-Control', 'no-store');
        return res.json(cache.sample);
    }
    try {
        if (!inflight) {
            inflight = sample()
                .then((s) => { cache = { sample: s, at: Date.now() }; return s; })
                .finally(() => { inflight = null; });
        }
        const s = await inflight;
        res.set('Cache-Control', 'no-store');
        res.json(s);
    } catch (err) {
        res.status(500).json({ error: 'metrics_unavailable' });
    }
});

module.exports = router;
