import assert from "node:assert/strict";
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { spawnSync } from "node:child_process";
import { test } from "bun:test";

import { nodeWebObserveAnalyzerSource } from "../hwlab-node-web-observe-analyzer-source";

const alertThresholds = {
  sameOriginApiSlowMs: 60000,
  partialApiSlowMs: 60000,
  longLivedStreamOpenSlowMs: 60000,
  visibleLoadingSlowMs: 60000,
  turnTimingSampleSlackSeconds: 60,
  turnElapsedSevereTimeoutSeconds: 3600,
  domEvaluateTimeoutRedCount: 99,
  domEvaluateTimeoutRedWindowMs: 60000,
  screenshotTimeoutRedCount: 99,
  pageErrorRedCount: 99,
  longTaskRedMs: 50,
  longAnimationFrameRedMs: 50,
  eventLoopGapRedMs: 50,
  browserProcessSampleIntervalMs: 1000,
  requestRateBucketMs: 10000,
  requestRateTotalRedPerMinute: 999999,
  requestRatePageRedPerMinute: 999999,
  requestRateApiPathRedPerMinute: 999999,
  browserTotalRssRedMb: 999999,
  browserProcessRssRedMb: 999999,
  browserRssGrowthRedMb: 999999,
  browserRssGrowthWindowMs: 60000,
  playwrightResponsivenessRedMs: 60000,
  playwrightResponsivenessTimeoutRedCount: 99,
  cdpMetricsTimeoutRedCount: 99,
  uncommandedStateChangeCommandWindowMs: 1000,
  scrollJumpCommandWindowMs: 1000,
  scrollJumpFromY: 999999,
  scrollJumpToY: 999999,
  sessionRailFallbackRatio: 0.5,
};

const browserFreezePolicy = {
  enabled: true,
  blockerWindowMs: 60000,
  memory: {
    totalRssBlockerMb: 999999,
    processRssBlockerMb: 999999,
    growthBlockerMb: 999999,
  },
  responsiveness: {
    latencyBlockerMs: 60000,
    eventBlockerCount: 99,
  },
  cdp: {
    metricsTimeoutBlockerCount: 99,
  },
  kill: {
    enabled: false,
    gracefulSignal: "SIGTERM",
    forceSignal: "SIGKILL",
    graceMs: 1000,
    pollIntervalMs: 100,
    exitCode: 124,
  },
};

async function writeJsonl(path: string, rows: Array<Record<string, unknown>>): Promise<void> {
  await writeFile(path, rows.map((row) => JSON.stringify(row)).join("\n") + (rows.length > 0 ? "\n" : ""));
}

async function runAnalyzer(stateDir: string): Promise<Record<string, unknown>> {
  const analyzerPath = join(stateDir, "analyze.mjs");
  await writeFile(analyzerPath, nodeWebObserveAnalyzerSource(), { mode: 0o700 });
  const result = spawnSync("bun", [analyzerPath, stateDir], {
    cwd: join(import.meta.dir, "../../.."),
    env: {
      ...process.env,
      UNIDESK_WEB_OBSERVE_ANALYZE_TAIL_SAMPLES: "0",
      UNIDESK_WEB_OBSERVE_ALERT_THRESHOLDS_JSON: JSON.stringify(alertThresholds),
      UNIDESK_WEB_OBSERVE_BROWSER_FREEZE_POLICY_JSON: JSON.stringify(browserFreezePolicy),
    },
    encoding: "utf8",
  });
  assert.equal(result.status, 0, result.stderr || result.stdout);
  return JSON.parse(await readFile(join(stateDir, "analysis", "report.json"), "utf8")) as Record<string, unknown>;
}

async function writeBaseState(options: { withSourceMap: boolean; captureEndAt: string }): Promise<string> {
  const stateDir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-analyzer-performance-"));
  await mkdir(join(stateDir, "analysis"), { recursive: true });
  const profilePath = join(stateDir, "profile.cpuprofile");
  const sourceMapPath = join(stateDir, "app.js.map");
  await writeFile(join(stateDir, "manifest.json"), JSON.stringify({ jobId: "webobs-performance-test", status: "completed" }) + "\n");
  await writeFile(join(stateDir, "heartbeat.json"), JSON.stringify({ status: "completed", updatedAt: "2026-07-02T00:00:03.000Z" }) + "\n");
  await writeFile(profilePath, JSON.stringify({
    startTime: 0,
    endTime: 2000000,
    nodes: [
      { id: 1, callFrame: { functionName: "(root)", url: "", lineNumber: -1, columnNumber: -1 }, children: [2] },
      { id: 2, callFrame: { functionName: "hotParse", url: "https://hwlab.example.test/assets/app.js", scriptId: "7", lineNumber: 0, columnNumber: 10 } },
    ],
    samples: [2, 2, 2],
    timeDeltas: [600000, 600000, 800000],
  }) + "\n");
  if (options.withSourceMap) {
    await writeFile(sourceMapPath, JSON.stringify({
      version: 3,
      file: "app.js",
      sources: ["src/api/client.ts"],
      names: ["responseJson"],
      mappings: "UAKEA",
    }) + "\n");
  }
  await writeJsonl(join(stateDir, "samples.jsonl"), []);
  await writeJsonl(join(stateDir, "network.jsonl"), []);
  await writeJsonl(join(stateDir, "control.jsonl"), []);
  await writeJsonl(join(stateDir, "console.jsonl"), []);
  await writeJsonl(join(stateDir, "errors.jsonl"), []);
  await writeJsonl(join(stateDir, "browser-process.jsonl"), []);
  await writeJsonl(join(stateDir, "artifacts.jsonl"), options.withSourceMap ? [{
    ts: "2026-07-02T00:00:00.100Z",
    kind: "source-map",
    path: sourceMapPath,
    url: "https://hwlab.example.test/assets/app.js.map",
  }] : []);
  await writeJsonl(join(stateDir, "performance-events.jsonl"), [
    {
      type: "performance-event",
      ts: "2026-07-02T00:00:01.080Z",
      sampleSeq: 11,
      pageRole: "control",
      pageId: "page-1",
      performance: {
        kind: "long-animation-frame",
        name: "long-animation-frame",
        duration: 80,
        blockingDuration: 80,
        timeOrigin: Date.parse("2026-07-02T00:00:00.000Z"),
        startTime: 1000,
        path: "/workbench",
        url: "https://hwlab.example.test/workbench",
        scripts: [{
          invoker: "then",
          invokerType: "promise",
          sourceURL: "https://hwlab.example.test/assets/app.js",
          sourceFunctionName: "Response.json.then",
          sourceCharPosition: 10,
          lineNumber: 0,
          columnNumber: 10,
          duration: 80,
        }],
      },
    },
    {
      type: "performance-capture-completed",
      ts: options.captureEndAt,
      commandId: "cmd-profile",
      captureId: "cap-profile",
      pageRole: "control",
      pageId: "page-1",
      artifact: { kind: "performance-cpu-profile", captureId: "cap-profile", commandId: "cmd-profile", path: profilePath, durationMs: 2000 },
      summary: {
        durationMs: 2000,
        sampleCount: 3,
        totalTimeMs: 2000,
        topFunctions: [{
          functionName: "hotParse",
          url: "https://hwlab.example.test/assets/app.js",
          scriptId: "7",
          lineNumber: 0,
          columnNumber: 10,
          selfTimeMs: 2000,
          totalTimeMs: 2000,
          sampleCount: 3,
        }],
      },
    },
  ]);
  return stateDir;
}

test("observe analyzer maps LoAF and same-window CPU samples through loaded source map", async () => {
  const stateDir = await writeBaseState({ withSourceMap: true, captureEndAt: "2026-07-02T00:00:02.500Z" });
  const report = await runAnalyzer(stateDir);
  const frontendPerformance = report.frontendPerformance as Record<string, unknown>;
  const summary = frontendPerformance.summary as Record<string, unknown>;
  assert.equal(summary.sourceMapStatus, "mapped");
  assert.equal(summary.cpuProfileWindowCoveredCount, 1);

  const scriptHotspot = (frontendPerformance.scriptHotspots as Array<Record<string, unknown>>)[0];
  assert.equal(scriptHotspot.sourceMapStatus, "mapped");
  assert.equal(scriptHotspot.sourceFile, "src/api/client.ts");
  assert.equal(scriptHotspot.sourceLine, 6);
  assert.equal(scriptHotspot.lineNumber, 0);
  assert.equal(scriptHotspot.sourceCharPosition, 10);

  const windowRow = (frontendPerformance.performanceWindows as Array<Record<string, unknown>>)[0];
  assert.equal(windowRow.cpuProfileCoverageStatus, "covered");
  const evidence = windowRow.profileEvidence as Record<string, unknown>;
  assert.equal(evidence.timelineStatus, "window-samples");
  assert.equal(evidence.evidenceScope, "same-window-cpu-samples");
  assert.equal(evidence.sampleCount, 1);
  assert.equal(evidence.totalTimeMs, 80);
  const topFunction = (evidence.topFunctions as Array<Record<string, unknown>>)[0];
  assert.equal(topFunction.functionName, "hotParse");
  assert.equal(topFunction.sourceMapStatus, "mapped");
  assert.equal(topFunction.sourceFile, "src/api/client.ts");
  assert.equal(topFunction.sourceLine, 6);
  assert.equal(topFunction.selfTimeMs, 80);
}, 20_000);

test("observe analyzer preserves raw attribution when source map is missing and marks missed CPU timeline windows", async () => {
  const stateDir = await writeBaseState({ withSourceMap: false, captureEndAt: "2026-07-02T00:00:05.000Z" });
  const report = await runAnalyzer(stateDir);
  const frontendPerformance = report.frontendPerformance as Record<string, unknown>;
  const summary = frontendPerformance.summary as Record<string, unknown>;
  assert.equal(summary.sourceMapStatus, "missing");
  assert.equal(summary.cpuProfileWindowMissedCount, 1);
  assert.equal(summary.attributionMode, "cpu-profile-missed-performance-window");

  const scriptHotspot = (frontendPerformance.scriptHotspots as Array<Record<string, unknown>>)[0];
  assert.equal(scriptHotspot.sourceMapStatus, "missing");
  assert.equal(scriptHotspot.sourceFile, "app.js");
  assert.equal(scriptHotspot.sourceLine, 0);

  const windowRow = (frontendPerformance.performanceWindows as Array<Record<string, unknown>>)[0];
  assert.equal(windowRow.cpuProfileCoverageStatus, "missed");
  assert.equal(windowRow.profileEvidence, null);
  const capture = windowRow.capture as Record<string, unknown>;
  assert.equal(capture.profileTimelineStatus, "loaded");
  assert.equal(capture.windowDistanceMs, 1920);
}, 20_000);
