// server-monitor.jsx — Owner-only system / database / error monitor
//
// One full-page view rendered when shell sets view === "serverMonitor".
// Polls /api/admin/system every 10 s + /api/admin/errors every 15 s.
// Designed to give the owner a fast read on:
//   - CPU load (1m / 5m / 15m vs core count)
//   - Memory (process RSS + system used %)
//   - Node uptime, BUILD_ID, started_at
//   - DB version, uptime, threads, slow queries
//   - DB total size + biggest tables (top 10)
//   - Connection pool (used / free / queued)
//   - Last 100 5xx errors with stack snippet
//
// All values come from window.api.admin.system / .errors.

function ServerMonitorView() {
  const [snap, setSnap]   = React.useState(null);
  const [errors, setErrors] = React.useState([]);
  const [tick, setTick]   = React.useState(0);
  const [err, setErr]     = React.useState(null);

  React.useEffect(() => {
    let alive = true;
    async function pullSystem() {
      try {
        const r = await window.api.admin.system();
        if (alive) { setSnap(r); setErr(null); }
      } catch (e) {
        if (alive) setErr((e && e.body && (e.body.message || e.body.error)) || "system_unavailable");
      }
    }
    async function pullErrors() {
      try {
        const r = await window.api.admin.errors({ limit: 100 });
        if (alive) setErrors(r.items || []);
      } catch {}
    }
    pullSystem(); pullErrors();
    const id1 = setInterval(pullSystem, 10_000);
    const id2 = setInterval(pullErrors, 15_000);
    return () => { alive = false; clearInterval(id1); clearInterval(id2); };
  }, [tick]);

  async function clearErrors() {
    if (!window.confirm("Clear the error log? This only affects what's shown here — anything already shipped via console / journald is unaffected.")) return;
    try { await window.api.admin.clearErrors(); setErrors([]); }
    catch {}
  }

  if (err) {
    return (
      <div className="sm-page">
        <div className="sm-empty">
          <h2>Server monitor</h2>
          <p>{err === "forbidden_owner_only"
              ? "This screen is owner-only."
              : "Couldn't load server stats — try again in a few seconds."}</p>
        </div>
        <style>{SERVER_MONITOR_CSS}</style>
      </div>
    );
  }
  if (!snap) {
    return (
      <div className="sm-page">
        <div className="sm-empty"><h2>Server monitor</h2><p>Loading…</p></div>
        <style>{SERVER_MONITOR_CSS}</style>
      </div>
    );
  }

  const cpuPct = snap.os.cpu_load_pct != null ? snap.os.cpu_load_pct : 0;
  const memPct = snap.os.used_mem_pct || 0;

  return (
    <div className="sm-page">
      <div className="sm-head">
        <div>
          <div className="sm-eyebrow">SYSTEM MONITOR</div>
          <h1>{snap.os.hostname || "Server"}</h1>
          <div className="sm-sub">
            Build <code>{snap.build || "?"}</code> · started {fmtRel(snap.started_at)} ·
            now {new Date(snap.now).toLocaleTimeString()}
          </div>
        </div>
        <div className="sm-head-actions">
          <button className="sm-btn" onClick={() => setTick(t => t + 1)} title="Refresh">↻ Refresh</button>
        </div>
      </div>

      {/* Top KPI strip */}
      <div className="sm-kpis">
        <SmKpi label="CPU load" value={cpuPct + "%"}
               sub={`${snap.os.load_avg["1m"]} · ${snap.os.load_avg["5m"]} · ${snap.os.load_avg["15m"]} (1/5/15m)`}
               tone={cpuPct > 90 ? "bad" : cpuPct > 70 ? "warn" : "ok"} bar={cpuPct}/>
        <SmKpi label="Memory" value={memPct + "%"}
               sub={`${snap.os.free_mem_mb} MB free of ${snap.os.total_mem_mb} MB`}
               tone={memPct > 90 ? "bad" : memPct > 75 ? "warn" : "ok"} bar={memPct}/>
        <SmKpi label="Node uptime" value={fmtDur(snap.process.uptime_seconds)}
               sub={`${snap.process.node_version} · pid ${snap.process.pid}`}/>
        <SmKpi label="Errors logged" value={(snap.errors && snap.errors.total) || 0}
               sub="Last 500 server errors in memory"
               tone={(snap.errors && snap.errors.total) > 0 ? "warn" : "ok"}/>
      </div>

      {/* Process + OS detail */}
      <div className="sm-row">
        <div className="sm-card">
          <h3>Process</h3>
          <SmKv k="Node"          v={snap.process.node_version}/>
          <SmKv k="PID"           v={snap.process.pid}/>
          <SmKv k="RSS"           v={snap.process.memory.rss_mb + " MB"}/>
          <SmKv k="Heap used"     v={snap.process.memory.heap_used_mb + " / " + snap.process.memory.heap_total_mb + " MB"}/>
          <SmKv k="External"      v={snap.process.memory.external_mb + " MB"}/>
          <SmKv k="Uptime"        v={fmtDur(snap.process.uptime_seconds)}/>
        </div>
        <div className="sm-card">
          <h3>Host</h3>
          <SmKv k="Platform" v={`${snap.os.platform} ${snap.os.release} (${snap.os.arch})`}/>
          <SmKv k="CPU"      v={snap.os.cpu_model || "—"}/>
          <SmKv k="Cores"    v={snap.os.cpu_count}/>
          <SmKv k="Load avg" v={`${snap.os.load_avg["1m"]} · ${snap.os.load_avg["5m"]} · ${snap.os.load_avg["15m"]}`}/>
          <SmKv k="RAM"      v={`${snap.os.total_mem_mb} MB total · ${snap.os.free_mem_mb} MB free`}/>
          <SmKv k="Used"     v={memPct + "%"}/>
        </div>
      </div>

      {/* Database */}
      <div className="sm-card">
        <h3>Database</h3>
        <div className="sm-row">
          <div className="sm-col">
            <SmKv k="Database" v={snap.database.database || "—"}/>
            <SmKv k="Version"  v={snap.database.version  || "—"}/>
            <SmKv k="DB uptime" v={fmtDur(snap.database.uptime_seconds)}/>
            <SmKv k="Total size" v={(snap.database.total_size_mb || 0) + " MB"}/>
            <SmKv k="Tables"    v={snap.database.table_count}/>
            <SmKv k="Rows"      v={Number(snap.database.total_rows).toLocaleString()}/>
          </div>
          <div className="sm-col">
            <SmKv k="Threads connected" v={snap.database.threads_connected}/>
            <SmKv k="Threads running"   v={snap.database.threads_running}/>
            <SmKv k="Max connections"   v={snap.database.max_connections}/>
            <SmKv k="Slow queries"      v={snap.database.slow_queries}
                  tone={snap.database.slow_queries > 0 ? "warn" : "ok"}/>
            <SmKv k="Aborted clients"   v={snap.database.aborted_clients}
                  tone={snap.database.aborted_clients > 0 ? "warn" : "ok"}/>
            <SmKv k="Questions" v={Number(snap.database.questions || 0).toLocaleString()}/>
          </div>
        </div>
        {snap.database.pool && (
          <div className="sm-pool">
            <b>Connection pool</b>
            &nbsp;{snap.database.pool.all_count} active ·
            &nbsp;{snap.database.pool.free_count} free ·
            &nbsp;{snap.database.pool.queue_count} queued
            &nbsp;<span className="sm-muted">(limit {snap.database.pool.connection_limit})</span>
          </div>
        )}
      </div>

      {/* Biggest tables */}
      <div className="sm-card">
        <h3>Biggest tables</h3>
        <table className="sm-table">
          <thead><tr><th>Table</th><th className="r">Rows</th><th className="r">Size (MB)</th></tr></thead>
          <tbody>
            {(snap.database.biggest_tables || []).map(t => (
              <tr key={t.name}>
                <td><code>{t.name}</code></td>
                <td className="r">{Number(t.rows).toLocaleString()}</td>
                <td className="r">{t.size_mb}</td>
              </tr>
            ))}
            {(snap.database.biggest_tables || []).length === 0 && (
              <tr><td colSpan="3" className="sm-empty-row">No tables — fresh database.</td></tr>
            )}
          </tbody>
        </table>
      </div>

      {/* Errors */}
      <div className="sm-card">
        <div className="sm-card-head">
          <h3>Recent errors <span className="sm-pill">{errors.length}</span></h3>
          {errors.length > 0 && (
            <button className="sm-btn sm-btn-quiet" onClick={clearErrors}>Clear</button>
          )}
        </div>
        {errors.length === 0 ? (
          <div className="sm-empty-row">No errors captured. Nice.</div>
        ) : (
          <div className="sm-errs">
            {errors.map((e, i) => (
              <details key={i} className="sm-err">
                <summary>
                  <span className={`sm-err-status sm-err-s-${Math.floor((e.status || 500) / 100)}xx`}>
                    {e.status || "—"}
                  </span>
                  <span className="sm-err-method">{e.method || ""}</span>
                  <span className="sm-err-path">{e.path || "(no path)"}</span>
                  <span className="sm-err-msg">{e.message}</span>
                  <span className="sm-err-ts">{fmtRel(e.ts)}</span>
                </summary>
                {e.stack && (
                  <pre className="sm-err-stack">{e.stack}</pre>
                )}
                {e.extra && (
                  <pre className="sm-err-stack">{JSON.stringify(e.extra, null, 2)}</pre>
                )}
                <div className="sm-err-foot">
                  <span>kind: <code>{e.kind}</code></span>
                  {e.user_id && <span>user: <code>{e.user_id}</code></span>}
                  <span>at: <code>{new Date(e.ts).toLocaleString()}</code></span>
                </div>
              </details>
            ))}
          </div>
        )}
      </div>

      <style>{SERVER_MONITOR_CSS}</style>
    </div>
  );
}

function SmKpi({ label, value, sub, tone, bar }) {
  return (
    <div className={`sm-kpi ${tone ? "sm-tone-" + tone : ""}`}>
      <div className="sm-kpi-label">{label}</div>
      <div className="sm-kpi-value">{value}</div>
      {bar != null && (
        <div className="sm-kpi-bar"><span style={{ width: Math.max(0, Math.min(100, bar)) + "%" }}/></div>
      )}
      <div className="sm-kpi-sub">{sub}</div>
    </div>
  );
}

function SmKv({ k, v, tone }) {
  return (
    <div className={`sm-kv ${tone ? "sm-tone-" + tone : ""}`}>
      <span className="sm-kv-k">{k}</span>
      <span className="sm-kv-v">{v}</span>
    </div>
  );
}

function fmtDur(sec) {
  const n = Number(sec) || 0;
  if (n < 60)   return `${n}s`;
  if (n < 3600) return `${Math.round(n / 60)}m`;
  if (n < 86400) {
    const h = Math.floor(n / 3600);
    const m = Math.round((n % 3600) / 60);
    return m ? `${h}h ${m}m` : `${h}h`;
  }
  const d = Math.floor(n / 86400);
  const h = Math.round((n % 86400) / 3600);
  return h ? `${d}d ${h}h` : `${d}d`;
}

function fmtRel(iso) {
  if (!iso) return "—";
  const ms = Date.now() - new Date(iso).getTime();
  if (ms < 0) return new Date(iso).toLocaleString();
  if (ms < 60_000)      return Math.round(ms / 1000) + "s ago";
  if (ms < 3_600_000)   return Math.round(ms / 60_000) + "m ago";
  if (ms < 86_400_000)  return Math.round(ms / 3_600_000) + "h ago";
  return new Date(iso).toLocaleString();
}

const SERVER_MONITOR_CSS = `
.sm-page {
  flex: 1; min-height: 0; overflow: auto;
  padding: 24px 28px;
  background: var(--bg-app, #f5f6f8);
  color: var(--ink-strong, #0f1729);
  font-family: var(--font-sans, "Figtree", system-ui, sans-serif);
}
.sm-head {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 18px;
}
.sm-head h1 { margin: 4px 0 2px; font-size: 22px; }
.sm-eyebrow {
  font-size: 10.5px; font-weight: 700; letter-spacing: .14em;
  color: var(--ink-muted, #6c7385);
}
.sm-sub  { color: var(--ink-muted, #6c7385); font-size: 12.5px; }
.sm-sub code { background: #eef0f5; padding: 1px 5px; border-radius: 4px; font-size: 11.5px; }
.sm-head-actions { margin-left: auto; display: flex; gap: 8px; }
.sm-btn {
  padding: 7px 12px; border-radius: 7px;
  background: #fff; border: 1px solid var(--border-row, #eaecf1);
  color: var(--ink-strong); cursor: pointer; font: 600 12.5px var(--font-sans);
}
.sm-btn:hover { background: #f7f8fb; }
.sm-btn-quiet { background: transparent; }

.sm-kpis {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: 12px; margin-bottom: 14px;
}
.sm-kpi {
  background: #fff; border: 1px solid var(--border-row, #eaecf1);
  border-radius: 12px; padding: 14px 16px;
}
.sm-kpi-label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .12em;
  color: var(--ink-muted, #6c7385);
}
.sm-kpi-value {
  font-size: 24px; font-weight: 800; color: var(--ink-strong);
  margin: 6px 0 4px; font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.sm-kpi-sub { font-size: 11.5px; color: var(--ink-muted); }
.sm-kpi-bar {
  height: 5px; border-radius: 999px; background: #eef0f5;
  margin: 4px 0 6px; overflow: hidden;
}
.sm-kpi-bar > span {
  display: block; height: 100%;
  background: linear-gradient(90deg, #0ea15c, #066039);
}
.sm-tone-warn .sm-kpi-bar > span { background: linear-gradient(90deg, #f7a82a, #b86b00); }
.sm-tone-bad  .sm-kpi-bar > span { background: linear-gradient(90deg, #e2445c, #8a1024); }

.sm-row {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 12px; margin-bottom: 14px;
}
.sm-col { padding-right: 8px; }
.sm-card {
  background: #fff; border: 1px solid var(--border-row, #eaecf1);
  border-radius: 12px; padding: 16px 18px;
  margin-bottom: 14px;
}
.sm-card h3 {
  margin: 0 0 12px;
  font-size: 13.5px; letter-spacing: 0.01em; color: var(--ink-strong);
}
.sm-card-head { display: flex; align-items: center; }
.sm-card-head h3 { margin-bottom: 0; flex: 1; }
.sm-pill {
  margin-left: 8px;
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  background: #eef0f5; color: #4a4f5e; font-size: 11px; font-weight: 700;
  vertical-align: middle;
}
.sm-kv {
  display: flex; justify-content: space-between; padding: 6px 0;
  border-bottom: 1px solid var(--border-row, #eaecf1);
  font-size: 12.5px;
}
.sm-kv:last-child { border-bottom: none; }
.sm-kv-k { color: var(--ink-muted, #6c7385); }
.sm-kv-v { color: var(--ink-strong); font-variant-numeric: tabular-nums; font-weight: 600; }
.sm-tone-warn .sm-kv-v { color: #8a5a14; }
.sm-tone-bad  .sm-kv-v { color: #8a1024; }
.sm-tone-ok   .sm-kv-v { color: #066039; }

.sm-pool {
  margin-top: 12px; padding: 10px 12px; border-radius: 8px;
  background: #fafbfd; border: 1px solid var(--border-row);
  font-size: 12.5px;
}
.sm-muted { color: var(--ink-muted, #6c7385); }

.sm-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.sm-table th, .sm-table td {
  text-align: left; padding: 8px 10px;
  border-bottom: 1px solid var(--border-row, #eaecf1);
}
.sm-table th { color: var(--ink-muted, #6c7385); font-weight: 700; }
.sm-table td.r, .sm-table th.r { text-align: right; font-variant-numeric: tabular-nums; }
.sm-empty-row { padding: 24px; text-align: center; color: var(--ink-muted, #6c7385); }
.sm-empty { padding: 60px 20px; text-align: center; color: var(--ink-muted, #6c7385); }

.sm-errs { display: flex; flex-direction: column; gap: 6px; }
.sm-err {
  background: #fafbfd; border: 1px solid var(--border-row, #eaecf1);
  border-radius: 8px; padding: 0;
}
.sm-err summary {
  list-style: none; padding: 8px 12px; cursor: pointer;
  display: flex; align-items: center; gap: 10px; font-size: 12.5px;
}
.sm-err summary::-webkit-details-marker { display: none; }
.sm-err-status {
  display: inline-block; padding: 2px 8px; border-radius: 6px;
  font-weight: 700; font-size: 11px;
  background: #eef0f5; color: #4a4f5e;
  min-width: 38px; text-align: center;
}
.sm-err-s-5xx { background: #ffd9df; color: #8a1d2e; }
.sm-err-s-4xx { background: #fef0d9; color: #8a5a14; }
.sm-err-method {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; color: var(--ink-muted); min-width: 44px;
}
.sm-err-path {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; color: var(--ink-strong); white-space: nowrap;
}
.sm-err-msg {
  flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  color: var(--ink-strong); font-weight: 600;
}
.sm-err-ts { color: var(--ink-muted); font-size: 11.5px; }
.sm-err-stack {
  margin: 0 12px 8px;
  background: #0f1729; color: #d8dde8;
  padding: 10px 12px; border-radius: 6px;
  font-size: 11.5px; line-height: 1.45;
  white-space: pre-wrap; word-break: break-word;
  max-height: 320px; overflow: auto;
}
.sm-err-foot {
  padding: 6px 12px 10px; display: flex; gap: 14px;
  font-size: 11px; color: var(--ink-muted);
}

@media (max-width: 900px) {
  .sm-kpis { grid-template-columns: repeat(2, 1fr); }
  .sm-row  { grid-template-columns: 1fr; }
}
`;

window.ServerMonitorView = ServerMonitorView;
