// Real fleet — Sitnov monitoring
// Groups: Websites, Lovable.dev, Cloudflare, Dgx-spark, Godento servers

// Initial group list — used to seed the `makeNodes` demo data. Real groups
// at runtime come from `deriveGroups(nodes)` so that newly-added groups
// (via the admin panel) show up immediately in the sidebar / 3D / heatmap.
const GROUPS = ['Websites', 'Lovable', 'Cloudflare', 'Dgx-spark', 'Godento'];

function deriveGroups(nodes) {
  // Preserve first-seen order; fall back to the hard-coded default when the
  // node list is empty (before the first API poll).
  if (!nodes || !nodes.length) return GROUPS.slice();
  const seen = new Set();
  const out = [];
  for (const n of nodes) {
    if (n.group && !seen.has(n.group)) { seen.add(n.group); out.push(n.group); }
  }
  return out;
}

const SERVER_LIST = [
  // Websites
  { url: 'https://citydent.bg', group: 'Websites' },
  { url: 'https://godento.com', group: 'Websites' },
  { url: 'https://smiledento.com', group: 'Websites' },
  { url: 'https://app.godento.com/booking/', group: 'Websites' },
  { url: 'https://smileon46.com', group: 'Websites' },

  // Lovable.dev
  { url: 'https://go.gotrelu.com', group: 'Lovable' },
  { url: 'https://smile.sitnov.work', group: 'Lovable' },
  { url: 'https://nsitnov.com', group: 'Lovable' },
  { url: 'https://drsitnov.com', group: 'Lovable' },
  { url: 'https://citydentesthetics.com', group: 'Lovable' },
  { url: 'https://timeoff.sitnov.work', group: 'Lovable' },
  { url: 'https://xray-go.sitnov.work', group: 'Lovable' },
  { url: 'https://myrestart.live', group: 'Lovable' },
  { url: 'https://coveandpinestays.com', group: 'Lovable' },
  { url: 'https://nutrisunny.com', group: 'Lovable' },
  { url: 'https://scan-wise.app', group: 'Lovable' },
  { url: 'https://sqlgodento.com', group: 'Lovable' },
  { url: 'https://sqlgodento.dev', group: 'Lovable' },
  { url: 'https://camps-sports.com/', group: 'Lovable' },

  // Cloudflare
  { url: 'https://api.sitnov.work/dashboard', group: 'Cloudflare' },
  { url: 'https://sitsms.sitnov.work', group: 'Cloudflare' },
  { url: 'https://godento.sitnov.work', group: 'Cloudflare' },
  { url: 'https://drugs.sitnov.work/', group: 'Cloudflare' },
  { url: 'https://face.sitnov.work/', group: 'Cloudflare' },
  { url: 'https://coworking.sitnov.work', group: 'Cloudflare' },
  { url: 'https://dicompressor.sitnov.work', group: 'Cloudflare' },
  { url: 'https://aprilvision.sitnov.work/training.html', group: 'Cloudflare' },
  { url: 'https://agentdento.com', group: 'Cloudflare' },
  { url: 'https://askdento.com', group: 'Cloudflare' },
  { url: 'https://agent.askdento.com/', group: 'Cloudflare' },
  { url: 'https://boats.askdento.com/', group: 'Cloudflare' },
  { url: 'https://dc8n8n1.askdento.com/sse', group: 'Cloudflare' },
  { url: 'https://dukovi.askdento.com/', group: 'Cloudflare' },
  { url: 'https://citydent2.askdento.com/', group: 'Cloudflare' },
  { url: 'https://n8nmcp.askdento.com', group: 'Cloudflare' },
  { url: 'https://invoices.askdento.com/', group: 'Cloudflare' },
  { url: 'https://dc8.askdento.com/', group: 'Cloudflare' },

  // Dgx-spark
  { url: 'https://amd.sitnov.work', group: 'Dgx-spark' },
  { url: 'https://bushkalova-api.sitnov.work', group: 'Dgx-spark' },
  { url: 'https://librechat.sitnov.work/login', group: 'Dgx-spark' },
  { url: 'https://macstudioinferencer.sitnov.work', group: 'Dgx-spark' },
  { url: 'https://macstudiolm.sitnov.work', group: 'Dgx-spark' },
  { url: 'https://storage.sitnov.work', group: 'Dgx-spark' },
  { url: 'https://xray.sitnov.work', group: 'Dgx-spark' },
  { url: 'https://godento.cloudflareaccess.com/', group: 'Dgx-spark' },

  // Godento servers (with ports)
  { url: 'https://enter.godento.com:8077/', group: 'Godento' },
  { url: 'https://eodent.godento.com:8050/', group: 'Godento' },
  { url: 'https://enter3.godento.com:7078/', group: 'Godento' },
  { url: 'https://enter4.godento.com:6030/', group: 'Godento' },
  { url: 'https://enter6.godento.com:6530/', group: 'Godento' },
  { url: 'https://enter7.godento.com:6046/', group: 'Godento' },
  { url: 'https://enter9.godento.com:7032/', group: 'Godento' },
  { url: 'https://dc8.godento.com:7040/', group: 'Godento' },
  { url: 'https://enter8.godento.com:7103/', group: 'Godento' },
];

// seeded PRNG
function mulberry32(a) {
  return function() {
    a |= 0; a = (a + 0x6D2B79F5) | 0;
    let t = a;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}
const rand = mulberry32(7);

function parseUrl(raw) {
  try {
    const u = new URL(raw);
    return {
      protocol: u.protocol.replace(':',''),
      host: u.host,               // incl. port
      hostname: u.hostname,
      port: u.port || (u.protocol === 'https:' ? '443' : '80'),
      path: (u.pathname + (u.search || '')) || '/',
    };
  } catch (e) {
    return { protocol: 'https', host: raw, hostname: raw, port: '443', path: '/' };
  }
}

function makeNodes() {
  const nodes = [];
  SERVER_LIST.forEach((entry, i) => {
    const p = parseUrl(entry.url);
    // deterministic latency band per group
    const groupLat = { Websites: 85, Lovable: 120, Cloudflare: 45, 'Dgx-spark': 180, Godento: 95 }[entry.group] || 100;
    const jitter = (rand() - 0.5) * groupLat * 0.6;
    const latency = Math.max(15, groupLat + jitter);
    const uptime = rand() * 60 * 24 * 180; // up to 180 days
    nodes.push({
      id: `n-${String(i + 1).padStart(3, '0')}`,
      url: entry.url,
      hostname: p.host,
      displayName: p.hostname.replace(/^www\./, ''),
      path: p.path,
      protocol: p.protocol,
      port: p.port,
      group: entry.group,
      service: entry.group.toLowerCase(),
      role: entry.group === 'Godento' ? 'server' : entry.group === 'Dgx-spark' ? 'compute' : 'web',
      status: 'ok',
      httpCode: 200,
      latency,
      uptime,
      // network throughput synthesized from group baselines
      netIn: 20 + rand() * 80,
      netOut: 20 + rand() * 80,
      // ssl
      sslDaysLeft: Math.floor(20 + rand() * 340),
      checkedAt: Date.now(),
      latencyHist: [],
      netHist: [],
    });
  });

  // all endpoints start as healthy — real state would come from live probes
  // use the "simulate incident" button in Tweaks to demo critical/offline visuals

  // seed history
  for (const n of nodes) {
    for (let i = 0; i < 60; i++) {
      const base = n.status === 'off' ? 0 : n.latency;
      n.latencyHist.push(Math.max(0, base + (rand() - 0.5) * base * 0.3));
      n.netHist.push(Math.max(0, (n.netIn + n.netOut) + (rand() - 0.5) * 30));
    }
  }
  return nodes;
}

function makeAlerts(nodes) {
  const now = Date.now();
  const alerts = [];
  // SSL warnings — find soonest expiring
  const soonExpire = [...nodes].sort((a,b) => a.sslDaysLeft - b.sslDaysLeft).slice(0, 2);
  soonExpire.forEach((n, i) => {
    if (n.sslDaysLeft < 30) {
      alerts.push({ id: 'a-ssl-' + i, ts: now - 1000 * 60 * (60 + i * 20), severity: 'warn', node: n.id, title: `TLS cert expires in ${n.sslDaysLeft}d`, body: `${n.hostname} · auto-renew pending`, ack: i > 0 });
    }
  });
  if (nodes[3]) alerts.push({ id: 'a-003', ts: now - 1000 * 60 * 18, severity: 'info', node: nodes[3].id, title: 'Probe completed', body: 'all endpoints healthy', ack: true });
  if (nodes[31]) alerts.push({ id: 'a-006', ts: now - 1000 * 60 * 131, severity: 'info', node: nodes[31].id, title: 'DNS record changed', body: 'CNAME updated · verified', ack: true });

  return alerts.sort((a,b) => b.ts - a.ts);
}

const LOG_SOURCES = ['probe', 'dns', 'tls', 'http', 'uptime', 'cf-edge', 'agent', 'alertmanager', 'scheduler', 'n8n'];
const LOG_TEMPLATES = [
  ['I', 'HEAD / → 200 in %dms'],
  ['I', 'GET /healthz → 200 ok'],
  ['I', 'TLS handshake ok · expires in %dd'],
  ['I', 'DNS A → %d.%d.%d.%d (cache hit)'],
  ['D', 'probe scheduled next in %ds'],
  ['W', 'HEAD / → 200 slow (%dms > budget)'],
  ['W', 'retry attempt=%d backoff=%dms'],
  ['W', 'TLS cert rotation pending'],
  ['E', 'HEAD / → 503 service unavailable'],
  ['E', 'connect ETIMEDOUT after %dms'],
  ['I', 'uptime %dh · %d checks · %d%% success'],
  ['D', 'traceroute %d hops · edge cf-sof'],
];

function fmtTpl(tpl) {
  return tpl.replace(/%d/g, () => Math.floor(rand() * 999));
}

function seedLogs(nodes) {
  const lines = [];
  const now = Date.now();
  for (let i = 0; i < 80; i++) {
    const [lvl, tpl] = LOG_TEMPLATES[Math.floor(rand() * LOG_TEMPLATES.length)];
    const node = nodes[Math.floor(rand() * nodes.length)];
    lines.push({
      ts: now - (80 - i) * 1200 - Math.floor(rand() * 400),
      lvl,
      src: LOG_SOURCES[Math.floor(rand() * LOG_SOURCES.length)],
      node: node.id,
      msg: fmtTpl(tpl),
    });
  }
  return lines;
}

function fmtUptime(mins) {
  const d = Math.floor(mins / (60 * 24));
  const h = Math.floor((mins % (60 * 24)) / 60);
  const m = Math.floor(mins % 60);
  if (d > 0) return `${d}d ${h}h`;
  if (h > 0) return `${h}h ${m}m`;
  return `${m}m`;
}
function fmtTs(ts) {
  const d = new Date(ts);
  return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
}
function fmtRel(ts) {
  const s = Math.floor((Date.now() - ts) / 1000);
  if (s < 60) return `${s}s ago`;
  if (s < 3600) return `${Math.floor(s / 60)}m ago`;
  if (s < 86400) return `${Math.floor(s / 3600)}h ago`;
  return `${Math.floor(s / 86400)}d ago`;
}

// Live probe API client — backend agent runs HEAD requests on an interval
async function fetchProbeNodes() {
  const res = await fetch('/api/nodes', { cache: 'no-store' });
  if (!res.ok) throw new Error(`probe api ${res.status}`);
  return res.json();
}

async function triggerProbe(id) {
  const res = await fetch(`/api/probe/${encodeURIComponent(id)}`, { method: 'POST' });
  if (!res.ok) throw new Error(`probe api ${res.status}`);
  return res.json();
}

Object.assign(window, {
  GROUPS, SERVER_LIST, deriveGroups,
  makeNodes, makeAlerts, seedLogs,
  LOG_SOURCES, LOG_TEMPLATES, fmtTpl,
  fmtUptime, fmtTs, fmtRel,
  fetchProbeNodes, triggerProbe,
});
