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

import { nodeWebObserveRunnerSource } from "./hwlab-node-web-observe-runner-source";
import { nodeWebObserveRunnerTraceReadabilitySource } from "./hwlab-node-web-observe-runner-trace-readability-source";

test("Workbench 产品 Trace 可读性源码解析独立 YAML 时序并识别终态", () => {
  const source = nodeWebObserveRunnerTraceReadabilitySource();
  const build = new Function(`${source}\nreturn { parseWorkbenchTraceReadabilityConfig, workbenchTraceReadabilityTerminalStatus, workbenchTraceReadabilityFailures, workbenchTraceReadabilityRetention };`) as () => {
    parseWorkbenchTraceReadabilityConfig: (raw: string) => Record<string, unknown>;
    workbenchTraceReadabilityTerminalStatus: (value: string) => boolean;
    workbenchTraceReadabilityFailures: (value: Record<string, unknown>) => Array<Record<string, unknown>>;
    workbenchTraceReadabilityRetention: (running: Record<string, unknown>, terminal: Record<string, unknown>) => Record<string, unknown>;
  };
  const helpers = build();
  assert.deepEqual(helpers.parseWorkbenchTraceReadabilityConfig(JSON.stringify({
    enabled: true,
    runningCardTimeoutMs: 15_000,
    disclosureActionTimeoutMs: 15_000,
    disclosureOpenTimeoutMs: 10_000,
    runningReadableTimeoutMs: 480_000,
    terminalTimeoutMs: 480_000,
    domStableQuietMs: 2_000,
    domStableTimeoutMs: 6_000,
    pollIntervalMs: 100,
  })), {
    enabled: true,
    runningCardTimeoutMs: 15_000,
    disclosureActionTimeoutMs: 15_000,
    disclosureOpenTimeoutMs: 10_000,
    runningReadableTimeoutMs: 480_000,
    terminalTimeoutMs: 480_000,
    domStableQuietMs: 2_000,
    domStableTimeoutMs: 6_000,
    pollIntervalMs: 100,
    valuesRedacted: true,
  });
  assert.equal(helpers.workbenchTraceReadabilityTerminalStatus("completed"), true);
  assert.equal(helpers.workbenchTraceReadabilityTerminalStatus("running"), false);
  assert.deepEqual(helpers.workbenchTraceReadabilityFailures({
    scope: { conversationCount: 1, debugPanelCountInsideConversation: 0 },
    startedDuringRunning: true,
    disclosure: { openBefore: true, autoReadable: true },
    running: { observed: true, snapshot: { rowCount: 2, sourceAuthorityRowCount: 2, projectedAuthorityRowCount: 0 } },
    terminalArrival: { observed: true },
    terminal: {
      stable: true,
      snapshot: {
        disclosure: { open: true },
        rowCount: 3,
        sourceAuthorityRowCount: 3,
        readableSourceRowCount: 3,
        projectedAuthorityRowCount: 0,
      },
    },
    runningRetention: { passed: true },
    retainedRunningRowCount: 2,
  }), []);
  assert.deepEqual(
    helpers.workbenchTraceReadabilityFailures({
      scope: { conversationCount: 1, debugPanelCountInsideConversation: 0 },
      startedDuringRunning: true,
      disclosure: { openBefore: false, autoReadable: false },
      running: { observed: true, snapshot: { rowCount: 2, sourceAuthorityRowCount: 2, projectedAuthorityRowCount: 0 } },
      terminalArrival: { observed: true },
      terminal: { stable: true, snapshot: { disclosure: { open: true }, rowCount: 2, sourceAuthorityRowCount: 2, readableSourceRowCount: 2, projectedAuthorityRowCount: 0 } },
      runningRetention: { passed: true },
      retainedRunningRowCount: 1,
    }).map((failure) => failure.code),
    ["trace_not_auto_expanded"],
  );

  assert.deepEqual(helpers.workbenchTraceReadabilityRetention({
    rowIdentities: ["source:1:tool"],
    disclosure: { eventCount: 121 },
    sourceSeqMin: 1,
    sourceSeqMax: 120,
  }, {
    rowIdentities: ["source:121:terminal"],
    disclosure: { eventCount: 122, rowWindowed: true },
    sourceSeqMin: 2,
    sourceSeqMax: 121,
  }), {
    mode: "window-progress",
    passed: true,
    retainedRowCount: 0,
    retainedConcreteRowCount: 0,
    runningEventCount: 121,
    terminalEventCount: 122,
    runningSourceSeqMin: 1,
    runningSourceSeqMax: 120,
    terminalSourceSeqMin: 2,
    terminalSourceSeqMax: 121,
    valuesRedacted: true,
  });
  assert.deepEqual(helpers.workbenchTraceReadabilityRetention({
    rowIdentities: ["source:18:trace-noise-summary"],
    disclosure: { eventCount: 25, rowWindowed: false },
    sourceSeqMin: 18,
    sourceSeqMax: 18,
  }, {
    rowIdentities: ["source:21:event:evt_fixture", "source:42:terminal:evt_terminal"],
    disclosure: { eventCount: 50, rowWindowed: false },
    sourceSeqMin: 21,
    sourceSeqMax: 42,
  }), {
    mode: "summary-progress",
    passed: true,
    retainedRowCount: 0,
    retainedConcreteRowCount: 0,
    runningEventCount: 25,
    terminalEventCount: 50,
    runningSourceSeqMin: 18,
    runningSourceSeqMax: 18,
    terminalSourceSeqMin: 21,
    terminalSourceSeqMax: 42,
    valuesRedacted: true,
  });
  assert.deepEqual(helpers.workbenchTraceReadabilityRetention({
    rowIdentities: ["source:18:event:evt_running"],
    disclosure: { eventCount: 25, rowWindowed: false },
    sourceSeqMin: 18,
    sourceSeqMax: 18,
  }, {
    rowIdentities: ["source:21:event:evt_terminal"],
    disclosure: { eventCount: 50, rowWindowed: false },
    sourceSeqMin: 21,
    sourceSeqMax: 21,
  }), {
    mode: "identity-overlap",
    passed: false,
    retainedRowCount: 0,
    retainedConcreteRowCount: 0,
    valuesRedacted: true,
  });
});

test("运行卡片命中后使用 traceId 稳定定位，不随状态变更漂移", async () => {
  const source = nodeWebObserveRunnerTraceReadabilitySource();
  const runningTimeline = {
    status: "running",
    getAttribute(name: string) {
      return name === "data-status" ? this.status : null;
    },
  };
  const cardElement = (traceId: string, timeline: typeof runningTimeline) => ({
    getAttribute(name: string) {
      return name === "data-trace-id" ? traceId : null;
    },
    querySelector() {
      return timeline;
    },
  });
  const selectedTraceId = "trc_current";
  const stableCard = {
    async count() { return 1; },
    locator() { return { count: async () => 1 }; },
    async getAttribute(name: string) { return name === "data-trace-id" ? selectedTraceId : null; },
  };
  const cards = {
    async count() { return 2; },
    async evaluateAll(callback: (elements: unknown[]) => unknown) {
      return callback([
        cardElement("trc_old", { ...runningTimeline, status: "completed" }),
        cardElement(selectedTraceId, runningTimeline),
      ]);
    },
  };
  const conversation = {
    locator(selector: string) {
      if (selector === ':scope > .message-card[data-role="agent"][data-trace-id]') return cards;
      if (selector === '[data-testid="workbench-kafka-debug-panel"]') return { count: async () => 0 };
      if (selector.includes(`[data-trace-id="${selectedTraceId}"]`)) return { first: () => stableCard };
      throw new Error(`unexpected selector: ${selector}`);
    },
  };
  const conversationRoot = {
    first: () => conversation,
    count: async () => 1,
  };
  const page = {
    locator(selector: string) {
      if (selector === "#conversation-list") return conversationRoot;
      throw new Error(`unexpected page selector: ${selector}`);
    },
  };
  const build = new Function("page", "sleep", `${source}\nreturn { workbenchTraceReadabilityWaitForCurrentProductCard };`) as (
    pageValue: typeof page,
    sleepValue: () => Promise<void>,
  ) => {
    workbenchTraceReadabilityWaitForCurrentProductCard: (timeoutMs: number, pollIntervalMs: number) => Promise<Record<string, any>>;
  };
  const helpers = build(page, async () => {});
  const target = await helpers.workbenchTraceReadabilityWaitForCurrentProductCard(1_000, 1);
  runningTimeline.status = "completed";

  assert.equal(target.traceId, selectedTraceId);
  assert.equal(target.card, stableCard);
  assert.equal(await target.card.getAttribute("data-trace-id"), selectedTraceId);
});

test("生成的 observer runner 包含产品 Trace 命令并通过 Node 语法检查", async () => {
  const source = nodeWebObserveRunnerSource();
  assert.match(source, /validateWorkbenchTraceReadability/u);
  assert.match(source, /#conversation-list/u);
  assert.match(source, /debugPanelCountInsideConversation/u);
  assert.match(source, /trace_not_auto_expanded/u);
  const child = Bun.spawn(["node", "--input-type=module", "--check", "-"], { stdin: "pipe", stdout: "pipe", stderr: "pipe" });
  child.stdin.write(source);
  child.stdin.end();
  const [stderr, exitCode] = await Promise.all([new Response(child.stderr).text(), child.exited]);
  assert.equal(exitCode, 0, stderr);
});
