import assert from "node:assert/strict";
import { test } from "bun:test";

import { createCloudApiServer } from "./server.ts";
import { createWebPerformanceStore, webPerformanceRouteTemplate } from "./web-performance.ts";

test("web performance store aggregates browser-perceived durations as Prometheus histograms", () => {
  const store = createWebPerformanceStore({ env: { HWLAB_METRICS_NAMESPACE: "hwlab-v02", HWLAB_GITOPS_TARGET: "v02" } });
  const result = store.record({
    schemaVersion: "hwlab-web-performance-v1",
    page: "/workspace",
    events: [
      { kind: "web_vital", metric: "lcp", route: "/workspace", valueMs: 1234 },
      { kind: "web_vital", metric: "cls", route: "/workspace", value: 0.08 },
      { kind: "api", metric: "api_request", route: "/v1/agent/chat/result/trc_secret", method: "GET", status: 200, statusClass: "2xx", outcome: "ok", valueMs: 420 }
    ]
  });
  const text = store.metricsText();

  assert.deepEqual(result, { accepted: 3, dropped: 0, received: 3 });
  assert.match(text, /hwlab_webui_performance_sample_total\{service="hwlab-cloud-web",namespace="hwlab-v02",gitops_target="v02",kind="web_vital",metric="lcp",route="\/workspace"/u);
  assert.match(text, /hwlab_webui_performance_duration_seconds_bucket\{[^}]*metric="api_request"[^}]*route="\/v1\/agent\/chat\/result\/:id"[^}]*le="0\.5"\} 1/u);
  assert.match(text, /hwlab_webui_layout_shift_score_bucket\{[^}]*metric="cls"[^}]*le="0\.1"\} 1/u);
  assert.doesNotMatch(text, /trc_secret|sessionId|conversationId|threadId|prompt|api key/iu);
});

test("web performance summary exposes user-perceived route latency without high-cardinality data", () => {
  const store = createWebPerformanceStore({ env: { HWLAB_METRICS_NAMESPACE: "hwlab-v02", HWLAB_GITOPS_TARGET: "v02" } });
  store.record({
    schemaVersion: "hwlab-web-performance-v1",
    page: "/workspace",
    events: [
      { kind: "api", metric: "api_request", route: "/v1/agent/chat/result/trc_secret", method: "GET", status: 200, statusClass: "2xx", outcome: "ok", valueMs: 420 },
      { kind: "api", metric: "api_request", route: "/v1/agent/chat/result/trc_secret", method: "GET", status: 0, statusClass: "network", outcome: "timeout", valueMs: 2600 },
      { kind: "long_task", metric: "long_task", route: "/workspace", valueMs: 260 },
      { kind: "web_vital", metric: "lcp", route: "/workspace", valueMs: 3100 }
    ]
  });

  const summary = store.summary();
  const json = JSON.stringify(summary);
  assert.equal(summary.summary.sampleCount, 4);
  assert.equal(summary.namespace, "hwlab-v02");
  assert.ok(summary.apiRoutes.some((row) => row.route === "/v1/agent/chat/result/:id" && row.metric === "api_request" && row.p95 >= 2.5));
  assert.ok(summary.problems.some((row) => row.problem === "timeout" && row.tone === "blocked"));
  assert.ok(summary.longTasks.some((row) => row.metric === "long_task" && row.problem === "slow-long_task" && row.tone !== "ok"));
  assert.doesNotMatch(json, /trc_secret|sessionId|conversationId|threadId|prompt|api key/iu);
});

test("web performance store filters observability self-noise", () => {
  const store = createWebPerformanceStore({ env: { HWLAB_METRICS_NAMESPACE: "hwlab-v02", HWLAB_GITOPS_TARGET: "v02" } });
  const result = store.record({
    schemaVersion: "hwlab-web-performance-v1",
    page: "/performance",
    events: [
      { kind: "web_vital", metric: "lcp", route: "/performance", valueMs: 5900 },
      { kind: "long_task", metric: "long_task", route: "/performance", valueMs: 120 },
      { kind: "api", metric: "api_request", route: "/v1/web-performance/summary", method: "GET", status: 200, statusClass: "2xx", outcome: "ok", valueMs: 620 },
      { kind: "api", metric: "api_request", route: "/v1/skills", method: "GET", status: 200, statusClass: "2xx", outcome: "ok", valueMs: 780 }
    ]
  });
  const summary = store.summary();
  const rowsJson = JSON.stringify({ apiRoutes: summary.apiRoutes, webVitals: summary.webVitals, longTasks: summary.longTasks, problems: summary.problems, rows: summary.rows });
  assert.deepEqual(result, { accepted: 0, dropped: 4, received: 4 });
  assert.equal(summary.summary.sampleCount, 0);
  assert.doesNotMatch(rowsJson, /\/performance|\/v1\/web-performance/u);
});

test("web performance route templates keep metrics low-cardinality", () => {
  assert.equal(webPerformanceRouteTemplate("/v1/workbench/workspace/cnv_abcdef/select-conversation?traceId=trc_secret"), "/v1/workbench/workspace/:id/select-conversation");
  assert.equal(webPerformanceRouteTemplate("#/skills"), "/skills");
});

test("cloud api ingests WebUI performance samples and exposes metrics only on loopback host", async () => {
  const server = createCloudApiServer({ env: { HWLAB_METRICS_NAMESPACE: "hwlab-v02", HWLAB_GITOPS_TARGET: "v02" } });
  await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
  try {
    const address = server.address();
    assert.ok(address && typeof address === "object");
    const baseUrl = `http://127.0.0.1:${address.port}`;
    const ingest = await fetch(`${baseUrl}/v1/web-performance`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        schemaVersion: "hwlab-web-performance-v1",
        page: "/workspace",
        events: [{ kind: "web_vital", metric: "lcp", route: "/workspace", valueMs: 900 }]
      })
    });
    assert.equal(ingest.status, 202);
    const metrics = await fetch(`${baseUrl}/v1/web-performance/metrics`);
    const text = await metrics.text();
    assert.equal(metrics.status, 200);
    assert.match(text, /hwlab_webui_performance_duration_seconds_bucket\{[^}]*metric="lcp"/u);

    const publicHost = await fetch(`${baseUrl}/v1/web-performance/metrics`, { headers: { host: "74.48.78.17:19667" } });
    assert.equal(publicHost.status, 404);

    const summary = await fetch(`${baseUrl}/v1/web-performance/summary`);
    const body = await summary.json();
    assert.equal(summary.status, 200);
    assert.equal(body.route, "/v1/web-performance/summary");
    assert.equal(body.summary.sampleCount, 1);
  } finally {
    await new Promise<void>((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});
