/* =========================================================================
   SRE MONITORING DASHBOARD
   Vanilla JS. No frameworks, no external chart libraries.

   LIVE DATA SOURCES (NO LOG FILES)
   -------------------------------
   1. /health  →  { "status": "healthy" }
      Live HTTP GET health check. Evaluates service status (Healthy/Unhealthy)
      and measures real round-trip latency in milliseconds.

   2. /metrics →  { "requests": <int> }
      Live HTTP GET metrics check. Retrieves authoritative total request count
      tracked by the Go service backend.
   ========================================================================= */

const CONFIG = {
  primaryBaseUrl: 'https://gheyma-app-2b88268529f9.hosted.ghaymah.systems',
  fallbackBaseUrl: 'http://localhost:8080',
  healthPath: '/health',
  metricsPath: '/metrics',
  maxHistoryPoints: 30,     // recent health check data points for charts
  autoRefreshMs: 15000,
  uptimeBuckets: 48,         // signature strip check count
  requestTimeoutMs: 6000,
};

/* =========================================================================
   Persistent Request Cache (localStorage)
   Prevents Total Requests from resetting to 0 when Go server restarts/hibernates
   ========================================================================= */

const CACHE_KEYS = {
  CUMULATIVE_TOTAL: 'ghaymah_cumulative_total_requests',
  LAST_RAW_COUNT: 'ghaymah_last_raw_server_requests',
};

function getStoredTotalRequests() {
  try {
    const val = localStorage.getItem(CACHE_KEYS.CUMULATIVE_TOTAL);
    return val ? parseInt(val, 10) || 0 : 0;
  } catch (_) {
    return 0;
  }
}

function updateStoredTotalRequests(serverRequests) {
  try {
    let accumulatedTotal = getStoredTotalRequests();
    const lastRawStr = localStorage.getItem(CACHE_KEYS.LAST_RAW_COUNT);
    const lastRaw = lastRawStr !== null ? parseInt(lastRawStr, 10) || 0 : null;

    if (lastRaw === null) {
      accumulatedTotal = Math.max(accumulatedTotal, serverRequests);
    } else if (serverRequests >= lastRaw) {
      const delta = serverRequests - lastRaw;
      accumulatedTotal += delta;
    } else {
      // Server restarted or hibernated (serverCount reset to smaller value)
      accumulatedTotal += serverRequests;
    }

    localStorage.setItem(CACHE_KEYS.CUMULATIVE_TOTAL, accumulatedTotal.toString());
    localStorage.setItem(CACHE_KEYS.LAST_RAW_COUNT, serverRequests.toString());
    return accumulatedTotal;
  } catch (_) {
    return serverRequests;
  }
}

/* =========================================================================
   State
   ========================================================================= */

const state = {
  history: [],                 // [{ok, latencyMs, httpCode, timestamp, raw, error, url}]
  events: [],                  // [{time, level, message}]
  totals: { total: getStoredTotalRequests(), success: 0, failed: 0, totalSource: 'metrics' },
  uptimeBuckets: [],           // [{state: 'ok'|'down'}]
  isChecking: false,
  metricsError: null,
  activeBaseUrl: CONFIG.primaryBaseUrl,
};

/* =========================================================================
   Live HTTP Fetch Helpers
   ========================================================================= */

async function fetchHealthFromUrl(url) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), CONFIG.requestTimeoutMs);
  const startTime = performance.now();
  try {
    const res = await fetch(url, { signal: controller.signal, cache: 'no-store' });
    const endTime = performance.now();
    const latencyMs = Math.round(endTime - startTime);
    let body = null;
    let rawText = '';
    try {
      rawText = await res.text();
      body = JSON.parse(rawText);
    } catch (_) {}

    const isHealthy = res.ok && body && body.status === 'healthy';
    return {
      ok: isHealthy,
      httpCode: res.status,
      latencyMs,
      timestamp: new Date(),
      raw: rawText || (body ? JSON.stringify(body) : `HTTP ${res.status}`),
      isNetworkError: false,
      error: isHealthy ? null : `HTTP ${res.status}${body && body.status ? ` (${body.status})` : ''}`,
      url,
    };
  } catch (err) {
    const endTime = performance.now();
    const latencyMs = Math.round(endTime - startTime);
    const isAbort = err.name === 'AbortError';
    const isNetworkError = !isAbort && err.name === 'TypeError';
    return {
      ok: false,
      httpCode: null,
      latencyMs,
      timestamp: new Date(),
      raw: null,
      isNetworkError,
      error: isAbort ? 'request timed out' : (isNetworkError ? 'network/CORS error — server unreachable' : (err.message || 'network error')),
      url,
    };
  } finally {
    clearTimeout(timeout);
  }
}

async function fetchMetricsFromUrl(url) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), CONFIG.requestTimeoutMs);
  try {
    const res = await fetch(url, { signal: controller.signal, cache: 'no-store' });
    if (!res.ok) {
      return { ok: false, requests: null, isNetworkError: false, error: `HTTP ${res.status}`, url };
    }
    const json = await res.json();
    return {
      ok: true,
      requests: typeof json.requests === 'number' ? json.requests : null,
      isNetworkError: false,
      error: null,
      url,
    };
  } catch (err) {
    const isAbort = err.name === 'AbortError';
    const isNetworkError = !isAbort && err.name === 'TypeError';
    return {
      ok: false,
      requests: null,
      isNetworkError,
      error: isAbort ? 'request timed out' : (err.message || 'network error'),
      url,
    };
  } finally {
    clearTimeout(timeout);
  }
}

/* =========================================================================
   Smart Dual-Endpoint Fetch (Primary hosted endpoint -> Local fallback)
   ========================================================================= */

async function fetchHealthWithFallback() {
  const bases = [CONFIG.primaryBaseUrl, CONFIG.fallbackBaseUrl].filter(Boolean);
  let lastResult = null;

  for (const base of bases) {
    const url = `${base}${CONFIG.healthPath}`;
    const result = await fetchHealthFromUrl(url);
    if (result.ok) {
      state.activeBaseUrl = base;
      return result;
    }
    lastResult = result;
  }

  return lastResult;
}

async function fetchMetricsWithFallback() {
  const base = state.activeBaseUrl || CONFIG.primaryBaseUrl;
  const primaryUrl = `${base}${CONFIG.metricsPath}`;
  const primaryRes = await fetchMetricsFromUrl(primaryUrl);
  if (primaryRes.ok) return primaryRes;

  const fallbackBase = base === CONFIG.primaryBaseUrl ? CONFIG.fallbackBaseUrl : CONFIG.primaryBaseUrl;
  if (fallbackBase) {
    const fallbackUrl = `${fallbackBase}${CONFIG.metricsPath}`;
    const fallbackRes = await fetchMetricsFromUrl(fallbackUrl);
    if (fallbackRes.ok) return fallbackRes;
  }

  return primaryRes;
}

async function refreshFromSources() {
  const [healthRes, metricsRes] = await Promise.all([
    fetchHealthWithFallback(),
    fetchMetricsWithFallback(),
  ]);

  const anyNetworkError = healthRes.isNetworkError || metricsRes.isNetworkError;
  if (anyNetworkError) showNetworkWarning(); else hideNetworkWarning();

  // Push live check result into history
  state.history.push(healthRes);
  if (state.history.length > CONFIG.maxHistoryPoints) {
    state.history.shift();
  }

  // Update uptime bucket tracking
  state.uptimeBuckets.push({ state: healthRes.ok ? 'ok' : 'down' });
  if (state.uptimeBuckets.length > CONFIG.uptimeBuckets) {
    state.uptimeBuckets.shift();
  }

  // Push new event
  state.events.unshift({
    time: healthRes.timestamp,
    level: healthRes.ok ? 'ok' : 'bad',
    message: healthRes.ok
      ? `Health check passed &mdash; <strong>${healthRes.latencyMs} ms</strong> (${healthRes.url})`
      : `Health check failed &mdash; <strong>${healthRes.error}</strong>`,
  });
  if (state.events.length > 50) {
    state.events.pop();
  }

  // Update success & failed check counts
  const totalChecks = state.history.length;
  const successChecks = state.history.filter(h => h.ok).length;
  const failedChecks = totalChecks - successChecks;

  state.totals.success = successChecks;
  state.totals.failed = failedChecks;

  if (metricsRes.ok && typeof metricsRes.requests === 'number') {
    const cumulativeTotal = updateStoredTotalRequests(metricsRes.requests);
    state.totals.total = cumulativeTotal;
    state.totals.totalSource = 'metrics';
    state.metricsError = null;
  } else {
    const cached = getStoredTotalRequests();
    if (cached > 0) {
      state.totals.total = cached;
    }
    state.metricsError = metricsRes.error || 'unavailable';
  }
}

/* =========================================================================
   Formatting helpers
   ========================================================================= */

const fmtTime = (d) => d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit' });
const fmtShortTime = (d) => d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
const fmtPct = (n) => `${n.toFixed(2)}%`;

/* =========================================================================
   Render — KPI Cards
   ========================================================================= */

function renderKPIs() {
  const latest = state.history[state.history.length - 1];
  const dot = document.getElementById('topStatusDot');
  const indicator = document.getElementById('statusIndicator');
  const statusText = document.getElementById('statusText');
  const statusFoot = document.getElementById('statusFoot');

  if (!latest) {
    indicator.className = 'status-indicator';
    statusText.textContent = 'Checking…';
    statusText.style.color = 'var(--text-muted)';
    dot.className = 'topbar__dot';
    statusFoot.textContent = 'awaiting first check';
  } else {
    const healthy = latest.ok;
    indicator.className = 'status-indicator ' + (healthy ? 'is-healthy' : 'is-unhealthy');
    statusText.textContent = healthy ? 'Healthy' : 'Unhealthy';
    statusText.style.color = healthy ? 'var(--ok)' : 'var(--bad)';
    dot.className = 'topbar__dot' + (healthy ? '' : ' is-bad');
    statusFoot.textContent = `checked ${fmtTime(latest.timestamp)}`;
  }

  const setText = (id, txt) => {
    const el = document.getElementById(id);
    if (el) el.textContent = txt;
  };

  setText('responseTimeValue', latest ? `${latest.latencyMs} ms` : '—');
  const avgRecent = average(state.history.slice(-10).map(h => h.latencyMs));
  setText('responseTimeFoot', state.history.length
    ? `avg last 10: ${Math.round(avgRecent)} ms`
    : 'current');

  setText('totalRequestsValue', state.totals.total.toLocaleString());
  setText('totalRequestsFoot', state.totals.totalSource === 'metrics'
    ? 'reported by /metrics'
    : `metrics: ${state.metricsError || 'unavailable'}`);

  setText('successValue', state.totals.success.toLocaleString());
  setText('failedValue', state.totals.failed.toLocaleString());

  const checksTotal = state.totals.success + state.totals.failed;
  const successRate = checksTotal ? (state.totals.success / checksTotal) * 100 : 100;
  const failRate = checksTotal ? (state.totals.failed / checksTotal) * 100 : 0;
  setText('successRateFoot', fmtPct(successRate) + ' of checks');
  setText('failedRateFoot', fmtPct(failRate) + ' of checks');

  setText('lastCheckValue', latest ? fmtTime(latest.timestamp) : '—');
  setText('lastCheckFoot', latest
    ? (latest.ok ? `HTTP ${latest.httpCode}` : `HTTP ${latest.httpCode ?? '—'} · ${latest.error}`)
    : '—');
}

function average(arr) {
  if (!arr.length) return 0;
  return arr.reduce((a, b) => a + b, 0) / arr.length;
}

/* =========================================================================
   Render — Latest Health Check Panel
   ========================================================================= */

function renderLatestCheck() {
  const latest = state.history[state.history.length - 1];
  const set = (id, text, cls) => {
    const el = document.getElementById(id);
    if (el) {
      el.textContent = text;
      el.className = cls ? 'dd-' + cls : '';
    }
  };
  if (!latest) return;
  set('lcEndpoint', latest.url || `${state.activeBaseUrl}${CONFIG.healthPath}`);
  set('lcStatus', latest.ok ? 'healthy' : 'unhealthy', latest.ok ? 'ok' : 'bad');
  set('lcCode', latest.httpCode ?? '—');
  set('lcLatency', `${latest.latencyMs} ms`);
  set('lcTime', latest.timestamp.toLocaleString());
  set('lcRaw', latest.raw || latest.error || '—');
}

/* =========================================================================
   Render — Events Log
   ========================================================================= */

function renderEvents() {
  const list = document.getElementById('eventsLog');
  if (!list) return;
  list.innerHTML = '';
  state.events.slice(0, 25).forEach(ev => {
    const li = document.createElement('li');
    li.className = 'events-log__row';
    li.innerHTML = `
      <span class="events-log__dot events-log__dot--${ev.level}"></span>
      <span class="events-log__time">${fmtShortTime(ev.time)}</span>
      <span class="events-log__msg">${ev.message}</span>
    `;
    list.appendChild(li);
  });
  const meta = document.getElementById('eventsMeta');
  if (meta) meta.textContent = `${state.events.length} checks logged`;
}

/* =========================================================================
   Render — Uptime Strip & Percentage
   ========================================================================= */

function renderUptime() {
  const strip = document.getElementById('uptimeStrip');
  if (!strip) return;
  strip.innerHTML = '';
  const buckets = state.uptimeBuckets;
  const padding = Math.max(CONFIG.uptimeBuckets - buckets.length, 0);

  for (let i = 0; i < padding; i++) {
    const cell = document.createElement('div');
    cell.className = 'uptime-cell is-unknown';
    strip.appendChild(cell);
  }
  buckets.forEach(b => {
    const cell = document.createElement('div');
    cell.className = 'uptime-cell' + (b.state === 'ok' ? '' : ` is-${b.state}`);
    strip.appendChild(cell);
  });

  const okCount = buckets.filter(b => b.state === 'ok').length;
  const pct = buckets.length ? (okCount / buckets.length) * 100 : 0;
  const elPct = document.getElementById('uptimePct');
  if (elPct) elPct.textContent = buckets.length ? fmtPct(pct) : '—';

  const meta = document.getElementById('uptimeWindowMeta');
  if (meta) meta.textContent = `${buckets.length} live check${buckets.length === 1 ? '' : 's'} recorded`;

  const now = new Date();
  const start = new Date(now.getTime() - CONFIG.uptimeBuckets * CONFIG.autoRefreshMs);
  const elStart = document.getElementById('uptimeTickStart');
  if (elStart) elStart.textContent = buckets.length ? fmtShortTime(start) : 'no data yet';
}

/* =========================================================================
   Canvas Charts — Shared Setup
   ========================================================================= */

function prepCanvas(canvas) {
  if (!canvas) return null;
  const dpr = window.devicePixelRatio || 1;
  const rect = canvas.getBoundingClientRect();
  const w = Math.max(rect.width, 10);
  const h = Math.max(rect.height, 10);
  canvas.width = w * dpr;
  canvas.height = h * dpr;
  const ctx = canvas.getContext('2d');
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  ctx.clearRect(0, 0, w, h);
  return { ctx, w, h };
}

const CSSVAR = (name) => getComputedStyle(document.documentElement).getPropertyValue(name).trim();

function drawChartPlaceholder(ctx, w, h, message) {
  ctx.fillStyle = CSSVAR('--text-dim');
  ctx.font = '12px Outfit, sans-serif';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';
  ctx.fillText(message, w / 2, h / 2);
}

/* ---- Line chart: Response Time over recent health checks ---- */

function drawResponseTimeChart() {
  const canvas = document.getElementById('responseTimeChart');
  if (!canvas) return;
  const prep = prepCanvas(canvas);
  if (!prep) return;
  const { ctx, w, h } = prep;
  const data = state.history;
  if (!data.length) {
    drawChartPlaceholder(ctx, w, h, 'Collecting data…');
    return;
  }

  const padL = 34, padR = 10, padT = 12, padB = 20;
  const plotW = w - padL - padR;
  const plotH = h - padT - padB;

  const values = data.map(d => d.latencyMs);
  const maxV = Math.max(...values) * 1.15;
  const minV = 0;

  const xFor = (i) => padL + (data.length === 1 ? plotW / 2 : (i / (data.length - 1)) * plotW);
  const yFor = (v) => padT + plotH - ((v - minV) / (maxV - minV || 1)) * plotH;

  // Gridlines + y-axis labels
  ctx.strokeStyle = CSSVAR('--border-soft');
  ctx.fillStyle = CSSVAR('--text-dim');
  ctx.font = '10px IBM Plex Mono, monospace';
  ctx.textBaseline = 'middle';
  const gridLines = 4;
  for (let i = 0; i <= gridLines; i++) {
    const v = (maxV / gridLines) * i;
    const y = yFor(v);
    ctx.beginPath();
    ctx.moveTo(padL, y);
    ctx.lineTo(w - padR, y);
    ctx.lineWidth = 1;
    ctx.stroke();
    ctx.textAlign = 'right';
    ctx.fillText(Math.round(v), padL - 8, y);
  }

  // x-axis labels (first / mid / last)
  ctx.textAlign = 'center';
  ctx.textBaseline = 'top';
  [0, Math.floor((data.length - 1) / 2), data.length - 1].forEach(i => {
    if (i < 0 || i >= data.length) return;
    ctx.fillText(fmtShortTime(data[i].timestamp), xFor(i), h - padB + 6);
  });

  // Gradient area fill
  const grad = ctx.createLinearGradient(0, padT, 0, padT + plotH);
  grad.addColorStop(0, 'rgba(91,157,249,0.28)');
  grad.addColorStop(1, 'rgba(91,157,249,0.0)');
  ctx.beginPath();
  data.forEach((d, i) => {
    const x = xFor(i), y = yFor(d.latencyMs);
    if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
  });
  ctx.lineTo(xFor(data.length - 1), padT + plotH);
  ctx.lineTo(xFor(0), padT + plotH);
  ctx.closePath();
  ctx.fillStyle = grad;
  ctx.fill();

  // Line
  ctx.beginPath();
  data.forEach((d, i) => {
    const x = xFor(i), y = yFor(d.latencyMs);
    if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
  });
  ctx.strokeStyle = CSSVAR('--info');
  ctx.lineWidth = 2;
  ctx.lineJoin = 'round';
  ctx.stroke();

  // Data points
  data.forEach((d, i) => {
    const x = xFor(i), y = yFor(d.latencyMs);
    ctx.beginPath();
    ctx.arc(x, y, d.ok ? 2.2 : 3.4, 0, Math.PI * 2);
    ctx.fillStyle = d.ok ? CSSVAR('--info') : CSSVAR('--bad');
    ctx.fill();
  });
}

/* ---- Status timeline: pass/fail history ---- */

function renderStatusTimeline() {
  const strip = document.getElementById('statusTimeline');
  if (!strip) return;

  const data = state.history;
  strip.innerHTML = '';

  const passEl = document.getElementById('statusPassCount');
  const failEl = document.getElementById('statusFailCount');
  const tickStart = document.getElementById('statusTickStart');

  const passCount = data.filter(d => d.ok).length;
  const failCount = data.length - passCount;

  if (passEl) passEl.textContent = passCount.toLocaleString();
  if (failEl) failEl.textContent = failCount.toLocaleString();

  if (!data.length) {
    if (tickStart) tickStart.textContent = 'no data yet';
    return;
  }

  const padding = Math.max(CONFIG.maxHistoryPoints - data.length, 0);
  for (let i = 0; i < padding; i++) {
    const cell = document.createElement('div');
    cell.className = 'status-timeline__cell is-pending';
    cell.title = 'Awaiting check';
    cell.setAttribute('aria-label', 'Awaiting check');
    strip.appendChild(cell);
  }

  data.forEach((d) => {
    const cell = document.createElement('div');
    cell.className = 'status-timeline__cell' + (d.ok ? '' : ' is-fail');
    const label = d.ok ? 'Healthy' : 'Unhealthy';
    const tip = `${fmtTime(d.timestamp)} · ${label} · ${d.latencyMs} ms`;
    cell.title = tip;
    cell.setAttribute('aria-label', tip);
    cell.tabIndex = 0;
    strip.appendChild(cell);
  });

  if (tickStart) tickStart.textContent = fmtShortTime(data[0].timestamp);
}

/* ---- Doughnut chart: Success vs Failure Ratio ---- */

function drawDoughnut() {
  const canvas = document.getElementById('statsDoughnut');
  if (!canvas) return;
  const prep = prepCanvas(canvas);
  if (!prep) return;
  const { ctx, w, h } = prep;
  const { success, failed } = state.totals;
  const total = success + failed;
  const cx = w / 2, cy = h / 2;
  const radius = Math.min(w, h) / 2 - 8;
  const thickness = radius * 0.34;

  if (!total) {
    ctx.strokeStyle = CSSVAR('--border-soft');
    ctx.lineWidth = thickness;
    ctx.beginPath();
    ctx.arc(cx, cy, radius - thickness / 2, 0, Math.PI * 2);
    ctx.stroke();
    drawChartPlaceholder(ctx, w, h, 'No checks yet');
    return;
  }

  const successFrac = success / total;
  const segments = [
    { frac: successFrac, color: CSSVAR('--ok') },
    { frac: 1 - successFrac, color: CSSVAR('--bad') },
  ];

  let start = -Math.PI / 2;
  segments.forEach(seg => {
    if (seg.frac <= 0) return;
    const end = start + seg.frac * Math.PI * 2;
    ctx.beginPath();
    ctx.arc(cx, cy, radius - thickness / 2, start, end);
    ctx.lineWidth = thickness;
    ctx.strokeStyle = seg.color;
    ctx.lineCap = segments.length > 1 && seg.frac < 1 ? 'butt' : 'round';
    ctx.stroke();
    start = end;
  });

  const centerVal = document.getElementById('doughnutCenterValue');
  if (centerVal) centerVal.textContent = fmtPct(successFrac * 100);
}

/* =========================================================================
   Network/CORS Warning Banner
   ========================================================================= */

function showNetworkWarning() {
  const el = document.getElementById('networkWarning');
  if (el) el.hidden = false;
}

function hideNetworkWarning() {
  const el = document.getElementById('networkWarning');
  if (el) el.hidden = true;
}

/* =========================================================================
   Master Render
   ========================================================================= */

function renderAll() {
  renderKPIs();
  renderLatestCheck();
  renderEvents();
  renderUptime();
  drawResponseTimeChart();
  renderStatusTimeline();
  drawDoughnut();
  const elUpdated = document.getElementById('lastUpdated');
  if (elUpdated) elUpdated.textContent = new Date().toLocaleTimeString();
}

/* =========================================================================
   Refresh Flow
   ========================================================================= */

async function doRefresh() {
  if (state.isChecking) return;
  state.isChecking = true;

  const btn = document.getElementById('refreshBtn');
  btn.classList.add('is-spinning');
  btn.disabled = true;

  try {
    await refreshFromSources();
    renderAll();
  } finally {
    state.isChecking = false;
    btn.classList.remove('is-spinning');
    btn.disabled = false;
  }
}

/* =========================================================================
   Init
   ========================================================================= */

function init() {
  renderAll(); // Initial empty state paint

  document.getElementById('refreshBtn').addEventListener('click', doRefresh);
  window.addEventListener('resize', debounce(renderAll, 150));

  doRefresh(); // Trigger first live check
  setInterval(doRefresh, CONFIG.autoRefreshMs);
}

function debounce(fn, ms) {
  let t;
  return (...args) => {
    clearTimeout(t);
    t = setTimeout(() => fn(...args), ms);
  };
}

document.addEventListener('DOMContentLoaded', init);