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

import { withWebObserveCommandRendered, withWebObserveStatusRendered } from "../hwlab-node-web-observe-render";
import { compactWebProbeScriptResult } from "../hwlab-node-web-probe-summary";
import { renderWebProbeScriptResult } from "./web-observe-render";
import { webProbeScriptCommandGovernance, withRecoveredWebProbeReportArtifact } from "./web-observe-scripts";

test("observe command renderer separates control completion from async turn terminal state", () => {
  const rendered = withWebObserveCommandRendered({
    ok: true,
    status: "control-completed",
    command: "web-probe observe command",
    id: "webobs-fixture",
    commandId: "cmd-fixture",
    observerCommand: { type: "sendPrompt" },
    observer: { ok: true, completedAt: "2026-07-10T01:00:01.000Z", result: { mark: "submitted" } },
    control: { executionStatus: "completed", businessTurnTerminalImplied: false },
    asyncTurn: { applicable: true, submissionStatus: "accepted", terminalStatus: null, terminalObserved: false, traceId: "trc_fixture" },
  });
  const text = String(rendered.renderedText);
  assert.match(text, /CONTROL.*TURN_SUBMIT.*TURN_TERMINAL/u);
  assert.match(text, /completed.*accepted.*not-observed.*trc_fixture/u);
  assert.match(text, /does not imply the asynchronous business turn reached a terminal state/u);
  assert.match(text, /observe status webobs-fixture --command-id cmd-fixture/u);
  assert.match(text, /--view turn-summary --command-id cmd-fixture/u);
});

test("observe command renderer exposes bounded performance capture artifact drill-down", () => {
  const rendered = withWebObserveCommandRendered({
    ok: true,
    status: "control-completed",
    command: "web-probe observe command",
    id: "webobs-fixture",
    commandId: "cmd-performance",
    observerCommand: { type: "performanceCapture" },
    observer: {
      ok: true,
      completedAt: "2026-07-13T12:00:00.000Z",
      result: {
        captureId: "cpu-fixture",
        durationMs: 1200,
        profileSha256: "sha256:profile",
        reportPath: ".state/web-observe/performance/cpu-fixture/summary.json",
        reportSha256: "sha256:report",
      },
    },
    control: { executionStatus: "completed", businessTurnTerminalImplied: false },
    asyncTurn: { applicable: false, submissionStatus: "not-applicable", terminalStatus: null, terminalObserved: false },
  });
  const text = String(rendered.renderedText);
  assert.match(text, /Performance capture:/u);
  assert.match(text, /sha256:profile/u);
  assert.match(text, /sha256:report/u);
  assert.match(text, /observe collect webobs-fixture --file .*summary\.json/u);
  assert.doesNotMatch(text, /nodes.*callFrame/u);
});

test("observe status renderer exposes the exact command phase and failed report evidence", () => {
  const rendered = withWebObserveStatusRendered({
    ok: true,
    status: "running",
    command: "web-probe observe status",
    id: "webobs-fixture",
    node: "NC01",
    lane: "v03",
    observer: {
      processAlive: true,
      manifest: { baseUrl: "https://hwlab.example.test", targetPath: "/workbench" },
      heartbeat: { status: "running", sampleSeq: 12, commandSeq: 3, updatedAt: "2026-07-10T01:00:01.000Z" },
      diagnostics: { effectiveLiveness: "alive", heartbeatStale: false },
      commands: { pendingCount: 0, processingCount: 0, abandonedCount: 0, failedCount: 1 },
      exactCommand: {
        commandId: "cmd-fixture",
        type: "validateRealtimeFanout",
        phase: "failed",
        ok: false,
        error: {
          message: "terminal event arrived before subscriber disconnect",
          details: {
            reportPath: "artifacts/realtime-fanout/cmd-fixture/report.json",
            reportSha256: "sha256:fixture-report",
          },
        },
      },
      tails: { samples: [], control: [], network: [] },
    },
    next: {
      status: "bun scripts/cli.ts web-probe observe status webobs-fixture",
    },
  });

  const text = String(rendered.renderedText);
  assert.match(text, /Exact command:/u);
  assert.match(text, /cmd-fixture.*validateRealtimeFanout.*failed.*false/u);
  assert.match(text, /sha256:fixture-report/u);
  assert.match(text, /terminal event arrived before subscriber disconnect/u);
});

test("observe status renderer exposes existing-session refresh handoff and typed failure evidence", () => {
  const base = {
    ok: true,
    status: "running",
    command: "web-probe observe status",
    id: "webobs-fixture",
    node: "NC01",
    lane: "v03",
    next: {},
  };
  const rendered = withWebObserveStatusRendered({
    ...base,
    observer: {
      processAlive: true,
      manifest: {},
      heartbeat: {},
      diagnostics: {},
      commands: {},
      exactCommand: {
        commandId: "cmd-existing-refresh",
        type: "validateExistingSessionRefresh",
        phase: "done",
        ok: true,
        result: {
          status: "passed",
          sessionId: "ses_existing",
          traceIds: ["trc_existing"],
          phase: "live",
          code: "refresh_live_handoff_validated",
          before: { messageCount: 2 },
          after: { messageCount: 2 },
          refreshReplay: {
            phase: "live",
            topic: "hwlab.event.v1",
            topicPartitions: [0],
            barrier: [{ partition: 0, endOffset: "18" }],
            counts: { matched: 8, replayed: 8, buffered: 1, bufferedDelivered: 1, liveDelivered: 0, deduplicated: 0 },
          },
          productSse: { browserSourceCount: 1, openCount: 1, connectedEventCount: 1, businessEventCount: 9, typedFailureCount: 0, livePhase: true },
          forbiddenRequestCount: 0,
          reportSha256: "sha256:existing-refresh-report",
          screenshot: { sha256: "sha256:existing-refresh-image" },
        },
      },
      tails: { samples: [], control: [], network: [] },
    },
  });
  const text = String(rendered.renderedText);
  assert.match(text, /Workbench 既有 Session 刷新：/u);
  assert.match(text, /ses_existing.*trc_existing.*live.*refresh_live_handoff_validated.*2.*2.*1.*1.*1.*9.*0.*0/u);
  assert.match(text, /hwlab\.event\.v1.*0.*0:18.*8.*8.*1.*1.*0.*0.*true/u);
  assert.match(text, /sha256:existing-refresh-report.*sha256:existing-refresh-image/u);

  const failed = withWebObserveStatusRendered({
    ...base,
    observer: {
      processAlive: true,
      manifest: {},
      heartbeat: {},
      diagnostics: {},
      commands: {},
      exactCommand: {
        commandId: "cmd-existing-refresh-failed",
        type: "validateExistingSessionRefresh",
        phase: "failed",
        ok: false,
        error: {
          message: "Kafka retention barrier gap",
          details: {
            sessionId: "ses_existing",
            traceIds: ["trc_existing"],
            phase: "retention-barrier",
            code: "retention_barrier_failed",
            failure: {
              phase: "retention-barrier",
              code: "retention_barrier_failed",
              streamPhase: "flushing",
              streamCode: "workbench_kafka_refresh_barrier_gap",
              message: "Kafka retention barrier gap",
            },
            reportSha256: "sha256:failed-existing-refresh-report",
          },
        },
      },
      tails: { samples: [], control: [], network: [] },
    },
  });
  const failedText = String(failed.renderedText);
  assert.match(failedText, /Typed failure:/u);
  assert.match(failedText, /retention-barrier.*retention_barrier_failed.*flushing.*workbench_kafka_refresh_barrier_gap.*Kafka retention barrier gap/u);
});

test("observe status renderer exposes bounded Workbench Kafka debug replay evidence", () => {
  const rendered = withWebObserveStatusRendered({
    ok: true,
    status: "running",
    command: "web-probe observe status",
    id: "webobs-fixture",
    node: "NC01",
    lane: "v03",
    observer: {
      processAlive: true,
      manifest: { baseUrl: "http://internal.example.test", targetPath: "/workbench" },
      heartbeat: { status: "running", sampleSeq: 12, commandSeq: 3, updatedAt: "2026-07-10T01:00:01.000Z" },
      diagnostics: { effectiveLiveness: "alive", heartbeatStale: false },
      commands: { pendingCount: 0, processingCount: 0, abandonedCount: 0, failedCount: 0 },
      exactCommand: {
        commandId: "cmd-debug-replay",
        type: "validateWorkbenchKafkaDebugReplay",
        phase: "done",
        ok: true,
        result: {
          status: "passed",
          traceId: "trc_fixture",
          topic: "hwlab.event.debug.v1",
          groupId: "hwlab-v03-workbench-isolated-debug-123-fixture",
          phase: "terminal",
          code: "terminal_complete",
          receivedCount: 35,
          appliedCount: 35,
          layerCounts: {
            server: { scanned: 35, matched: 35, delivered: 35 },
            client: { received: 35, decoded: 35, applied: 35 },
          },
          layerDom: {
            matchCount: 1,
            visibleCount: 1,
            connectedCount: 1,
            textBytes: 128,
            textHash: "sha256:layer-dom",
          },
          layerDomStabilization: {
            stable: true,
            timedOut: false,
            sampleCount: 4,
            elapsedMs: 400,
          },
          traceTimeline: {
            contract: "trace-sequence-authority-v1",
            sourceAuthorityRowCount: 35,
            readableSourceRowCount: 35,
          },
          rawHwlabEventWindow: {
            available: true,
            samePage: true,
            unfilteredContractPresent: true,
            productEventSourceRequestCount: 0,
            counts: { received: 35, retained: 30, rejected: 0, evicted: 5, bytes: 8192 },
          },
          terminalStatus: "completed",
          reportSha256: "sha256:report",
          screenshot: { sha256: "sha256:image" },
        },
      },
      tails: { samples: [], control: [], network: [] },
    },
    next: {},
  });

  const text = String(rendered.renderedText);
  assert.match(text, /Workbench Kafka debug replay:/u);
  assert.match(text, /trc_fixture.*hwlab\.event\.debug\.v1/u);
  assert.match(text, /terminal.*terminal_complete/u);
  assert.match(text, /35.*35.*completed.*sha256:image/u);
  assert.match(text, /SERVER_SCANNED.*CLIENT_APPLIED/u);
  assert.match(text, /分层 DOM 诊断：/u);
  assert.match(text, /1.*1.*1.*128.*sha256:layer-dom.*true.*false.*4.*400/u);
  assert.match(text, /trace-sequence-authority-v1.*35.*35.*true.*true.*true.*35.*30.*0.*5.*8192.*0/u);
});

test("observe status renderer 展示限定作用域的产品 Trace 生命周期证据", () => {
  const rendered = withWebObserveStatusRendered({
    ok: true,
    status: "running",
    command: "web-probe observe status",
    id: "webobs-fixture",
    node: "NC01",
    lane: "v03",
    observer: {
      processAlive: true,
      manifest: { baseUrl: "http://internal.example.test", targetPath: "/workbench" },
      heartbeat: { status: "running", sampleSeq: 12, commandSeq: 4, updatedAt: "2026-07-10T01:00:01.000Z" },
      diagnostics: { effectiveLiveness: "alive", heartbeatStale: false },
      commands: { pendingCount: 0, processingCount: 0, abandonedCount: 0, failedCount: 0 },
      exactCommand: {
        commandId: "cmd-trace-readable",
        type: "validateWorkbenchTraceReadability",
        phase: "done",
        ok: true,
        result: {
          status: "passed",
          traceId: "trc_fixture",
          startedDuringRunning: true,
          forcedExpansion: false,
          scope: { conversationCount: 1, productCardCount: 2, debugPanelCountInsideConversation: 0 },
          disclosure: { openBefore: true, autoReadable: true, openAfterTerminal: true },
          running: { observed: true, snapshot: { status: "running", rowCount: 2, sourceAuthorityRowCount: 2 } },
          terminal: { stable: true, snapshot: { status: "completed", rowCount: 3, sourceAuthorityRowCount: 3, projectedAuthorityRowCount: 0 } },
          runningRetention: { mode: "identity-overlap", passed: true, retainedRowCount: 2 },
          retainedRunningRowCount: 2,
          reportSha256: "sha256:trace-report",
          screenshot: { sha256: "sha256:trace-image" },
        },
      },
      tails: { samples: [], control: [], network: [] },
    },
    next: {},
  });

  const text = String(rendered.renderedText);
  assert.match(text, /Workbench 产品 Trace 可读性：/u);
  assert.match(text, /trc_fixture.*true.*true.*true.*true.*false.*true.*identity-overlap.*true.*2.*sha256:trace-report.*sha256:trace-image/u);
  assert.match(text, /CONVERSATION.*PRODUCT_CARDS.*DEBUG_IN_SCOPE/u);
  assert.match(text, /1.*2.*0.*running.*2.*2.*completed.*3.*3.*0.*true/u);
});

test("observe status renderer 在命令失败时保留产品 Trace typed evidence", () => {
  const rendered = withWebObserveStatusRendered({
    ok: true,
    status: "running",
    command: "web-probe observe status",
    id: "webobs-fixture",
    node: "NC01",
    lane: "v03",
    observer: {
      processAlive: true,
      manifest: {},
      heartbeat: {},
      diagnostics: {},
      commands: {},
      exactCommand: {
        commandId: "cmd-trace-failed",
        type: "validateWorkbenchTraceReadability",
        phase: "failed",
        ok: false,
        error: {
          message: "trace validation failed",
          details: {
            traceId: "trc_failed",
            startedDuringRunning: true,
            forcedExpansion: false,
            scope: { conversationCount: 1, productCardCount: 1, debugPanelCountInsideConversation: 0 },
            disclosure: { openBefore: false, autoReadable: false, openAfterTerminal: true },
            running: { observed: true, snapshot: { status: "running", rowCount: 2, sourceAuthorityRowCount: 2 } },
            terminal: { stable: true, snapshot: { status: "completed", rowCount: 3, sourceAuthorityRowCount: 3, projectedAuthorityRowCount: 0 } },
            runningRetention: { mode: "identity-overlap", passed: true, retainedRowCount: 2 },
            retainedRunningRowCount: 2,
            failures: [{ code: "trace_not_auto_expanded" }],
            reportSha256: "sha256:failed-trace-report",
          },
        },
      },
      tails: { samples: [], control: [], network: [] },
    },
    next: {},
  });

  const text = String(rendered.renderedText);
  assert.match(text, /trc_failed.*identity-overlap.*true.*2.*sha256:failed-trace-report/u);
  assert.match(text, /trace_not_auto_expanded/u);
});

test("observe command renderer 在隔离重放失败时保留分层 typed evidence", () => {
  const rendered = withWebObserveCommandRendered({
    ok: false,
    status: "failed",
    command: "web-probe observe command",
    id: "webobs-fixture",
    commandId: "cmd-debug-failed",
    observerCommand: { type: "validateWorkbenchKafkaDebugReplay" },
    observer: {
      ok: false,
      error: {
        message: "debug trace missing",
        details: {
          traceId: "trc_debug_failed",
          topic: "hwlab.event.debug.v1",
          groupId: "hwlab-v03-workbench-isolated-debug-fixture",
          phase: "filter",
          code: "debug_trace_not_generated",
          receivedCount: 0,
          appliedCount: 0,
          layerCounts: {
            server: { scanned: 27, matched: 0, delivered: 0 },
            client: { received: 0, decoded: 0, applied: 0 },
          },
          reportSha256: "sha256:failed-debug-report",
        },
      },
    },
    control: { executionStatus: "failed" },
    asyncTurn: {},
  });

  const text = String(rendered.renderedText);
  assert.match(text, /trc_debug_failed.*debug_trace_not_generated/u);
  assert.match(text, /27.*0.*0.*0.*0.*0/u);
  assert.match(text, /sha256:failed-debug-report/u);
});

test("web-probe script render warns to promote repeated scripts into commands", () => {
  const commandPromotionHint = {
    schemaVersion: "unidesk.web-probe.command-promotion.v1",
    code: "prefer_repo_owned_typed_command",
    promotionRequiredBeforeRepeat: true,
    repoOwnedTypedCommand: false,
    sourceKind: "stdin",
    currentCommand: "web-probe script --node D601 --lane v03",
    preferredArtifact: "repo-owned-typed-command",
    preferredSurface: "web-probe observe command or a dedicated web-probe subcommand",
    action: "promote-before-repeat",
    message: "Before repeating this one-off script, promote the workflow to a repo-owned typed web-probe command.",
    valuesRedacted: true,
  };
  const rendered = renderWebProbeScriptResult({
    commandPromotionHint,
    ok: true,
    status: "pass",
    command: "web-probe script --node D601 --lane v03",
    node: "D601",
    lane: "v03",
    url: "https://hwlab.example.test",
    warnings: [{
      code: "web_probe_script_ad_hoc_only",
      severity: "warning",
      requiredAction: "promote-before-repeat",
      preferredArtifact: "repo-owned-typed-command",
      message: "web-probe script is a one-off exploration escape hatch; before repeating it, promote the workflow to a repo-owned typed command instead of rerunning temporary scripts.",
    }],
    hints: [
      "Before rerunning this script, promote the workflow to a repo-owned typed `web-probe` command.",
    ],
    preferredCommands: {
      typedCommandCatalog: "bun scripts/cli.ts web-probe --help",
      startObserver: "bun scripts/cli.ts web-probe observe start --node D601 --lane v03 --target-path /projects/mdtodo",
      mdtodoSummary: "bun scripts/cli.ts web-probe observe collect <observerId> --view project-mdtodo-summary",
    },
    summary: { ok: true, status: "pass" },
    issueEvidence: {},
    probe: { steps: [], script: { result: { ok: true } } },
    reportLoad: { source: "stdout", path: ".state/web-probe-script/run.demo/web-probe-script-report.json" },
    result: { exitCode: 0, timedOut: false },
  });

  const text = String(rendered.renderedText ?? "");
  const marker = "UNIDESK_WEB_PROBE_COMMAND_PROMOTION_HINT ";
  const firstLine = text.split("\n", 1)[0] ?? "";
  assert.ok(firstLine.startsWith(marker));
  assert.deepEqual(JSON.parse(firstLine.slice(marker.length)), commandPromotionHint);
  assert.equal(Object.keys(rendered)[0], "commandPromotionHint");
  assert.match(text, /WARNINGS/u);
  assert.match(text, /web_probe_script_ad_hoc_only/u);
  assert.match(text, /HINTS/u);
  assert.match(text, /repo-owned typed `web-probe` command/u);
  assert.match(text, /typedCommandCatalog: bun scripts\/cli\.ts web-probe --help/u);
  assert.match(text, /startObserver: bun scripts\/cli\.ts web-probe observe start/u);
  assert.match(text, /mdtodoSummary: bun scripts\/cli\.ts web-probe observe collect/u);
});

test("web-probe script success promotes typed report and screenshot hashes without artifact drill-down", () => {
  const reportSha = `sha256:${"a".repeat(64)}`;
  const screenshotSha = `sha256:${"b".repeat(64)}`;
  const probe = compactWebProbeScriptResult({
    ok: true,
    status: "pass",
    scriptSha256: `sha256:${"c".repeat(64)}`,
    summary: { ok: true, status: "pass" },
    script: { result: {
      ok: true,
      report: { kind: "json", path: ".state/run/evidence.json", byteCount: 512, sha256: reportSha },
      screenshot: { kind: "screenshot", path: ".state/run/screenshot.png", byteCount: 1024, sha256: screenshotSha },
      issueEvidence: { ok: true, status: "pass", reportPath: ".state/run/evidence.json", reportSha256: reportSha, screenshots: [{ sha256: screenshotSha }], valuesRedacted: true },
    } },
    pdfChecks: [{ status: 200, mediaType: "application/pdf", popupCreated: true, popupProtocol: "blob:", popupClosedBeforeBlob: false, ok: true }],
    artifacts: { items: [
      { kind: "screenshot", byteCount: 1024, sha256: screenshotSha, path: ".state/run/screenshot.png" },
      { kind: "json", byteCount: 512, sha256: reportSha, path: ".state/run/evidence.json" },
    ] },
  });
  assert.notEqual(probe, null);
  assert.equal(probe?.failureEvidence, null);
  const rendered = renderWebProbeScriptResult({
    ok: true,
    status: "pass",
    command: "web-probe product-smoke --product fixture --target NC01 --profile admin",
    node: "NC01",
    lane: "v03",
    url: "https://fixture.example.test",
    summary: { ok: true, status: "pass" },
    issueEvidence: probe?.issueEvidence,
    probe,
    reportLoad: { source: "stdout", path: ".state/run/evidence.json" },
    result: { exitCode: 0, timedOut: false },
  });
  const text = String(rendered.renderedText ?? "");
  assert.match(text, new RegExp(reportSha, "u"));
  assert.match(text, new RegExp(screenshotSha, "u"));
  assert.match(text, /ARTIFACTS[\s\S]*screenshot[\s\S]*json/u);
  assert.match(text, /FAILURE_EVIDENCE\n-/u);
  assert.doesNotMatch(text, /FAILURE_EVIDENCE[\s\S]*status\s+200/u);
});

test("web-probe report-file recovery computes and renders its own artifact hash", () => {
  const reportPath = ".state/web-probe-script/run.fixture/web-probe-script-report.json";
  const reportSha = `sha256:${"d".repeat(64)}`;
  const screenshotSha = `sha256:${"e".repeat(64)}`;
  const recovered = withRecoveredWebProbeReportArtifact({
    ok: true,
    status: "pass",
    scriptSha256: `sha256:${"f".repeat(64)}`,
    summary: { ok: true, status: "pass" },
    script: { result: { ok: true } },
    artifacts: { items: [
      { kind: "screenshot", byteCount: 1024, sha256: screenshotSha, path: ".state/run/screenshot.png" },
    ] },
  }, { kind: "json", path: reportPath, byteCount: 4096, sha256: reportSha });
  const probe = compactWebProbeScriptResult(recovered);
  const rendered = renderWebProbeScriptResult({
    ok: true,
    status: "pass",
    command: "web-probe product-smoke --product fixture --target NC01 --profile admin",
    node: "NC01",
    lane: "v03",
    url: "https://fixture.example.test",
    summary: { ok: true, status: "pass", recoveredFrom: "report-file" },
    probe,
    reportLoad: { source: "report-file", path: reportPath },
    result: { exitCode: 0, timedOut: false },
  });
  const text = String(rendered.renderedText ?? "");
  assert.equal(probe?.reportSha256, reportSha);
  assert.match(text, new RegExp(reportSha, "u"));
  assert.match(text, new RegExp(screenshotSha, "u"));
  assert.match(text, /ARTIFACTS[\s\S]*screenshot[\s\S]*json/u);
});

test("web-probe script renders browser runtime auth reachability without Secret drill-down", () => {
  const reportPath = ".state/web-probe-script/run.fixture/web-probe-script-report.json";
  const reportSha = `sha256:${"d".repeat(64)}`;
  const probe = compactWebProbeScriptResult({
    ok: false,
    status: "blocked",
    error: "auth-login-failed",
    errorMessage: "auth-login-failed",
    reportPath,
    reportSha256: reportSha,
    auth: {
      ok: false,
      method: "ui-form-memory",
      status: 0,
      usernamePresent: true,
      attempts: [{ method: "ui-form-memory", response: null, finalPath: null }],
      errorSummary: { message: "page.goto: Target page, context or browser has been closed" },
      valuesRedacted: true,
    },
    script: { ok: false, steps: [] },
    steps: [],
  });
  assert.notEqual(probe, null);
  const rendered = renderWebProbeScriptResult({
    ok: false,
    status: "blocked",
    command: "web-probe product-smoke --product fixture --target test --profile admin",
    node: "fixture-node",
    lane: "fixture-lane",
    url: "https://fixture.example.test",
    degradedReason: "browser-runtime-closed",
    failureKind: probe?.failureKind,
    summary: probe?.summary,
    issueEvidence: probe?.issueEvidence,
    probe,
    reportLoad: { source: "stdout", path: reportPath },
    result: { exitCode: 2, timedOut: false },
  });
  const text = String(rendered.renderedText ?? "");
  assert.match(text, /browser-runtime-closed/u);
  assert.match(text, /browser-runtime-blocker/u);
  assert.match(text, /credentialResolved\s+true/u);
  assert.match(text, /requestReached\s+false/u);
  assert.match(text, /apiResponseReached\s+false/u);
  assert.match(text, /Target page, context or browser has been closed/u);
  assert.match(text, new RegExp(reportPath.replaceAll(".", "\\."), "u"));
  assert.match(text, new RegExp(reportSha, "u"));
});

test("product-smoke failure render exposes PDF decision fields and controlled artifact drill-down", () => {
  const reportSha256 = `sha256:${"c".repeat(64)}`;
  const screenshotSha256 = `sha256:${"d".repeat(64)}`;
  const reportPath = ".state/web-probe-script/run.fixture/web-probe-script-report.json";
  const screenshotPath = ".state/web-probe-script/run.fixture/failure.png";
  const probe = compactWebProbeScriptResult({
    ok: false,
    status: "blocked",
    reportPath,
    reportSha256,
    errorMessage: `发票 PDF 响应无效：${JSON.stringify({ status: 503, mediaType: "text/html", popupCreated: true, popupProtocol: "about:", popupClosedBeforeBlob: true, ok: false })}`,
    artifacts: { items: [{ kind: "screenshot", path: screenshotPath, sha256: screenshotSha256 }] },
  });
  const rendered = renderWebProbeScriptResult({
    ok: false,
    status: "blocked",
    command: "web-probe product-smoke --product pikaoa --target NC01 --profile admin-mvp",
    node: "D601",
    lane: "v03",
    url: "https://fixture.example.test",
    probe,
    reportLoad: { source: "stdout", path: reportPath },
    result: { exitCode: 2, timedOut: false },
  });
  const text = String(rendered.renderedText ?? "");
  assert.match(text, /FAILURE_EVIDENCE[\s\S]*status\s+503/u);
  assert.match(text, /mediaType\s+text\/html/u);
  assert.match(text, /popupCreated\s+true/u);
  assert.match(text, /popupProtocol\s+about:/u);
  assert.match(text, /popupClosedBeforeBlob\s+true/u);
  assert.match(text, new RegExp(reportSha256, "u"));
  assert.match(text, new RegExp(screenshotSha256, "u"));
  assert.match(text, /web-probe artifact inspect --node D601 --lane v03/u);
  assert.doesNotMatch(text, /trans D601:/u);
});

test("web-probe script renders bounded command tails when report output is missing", () => {
  const rendered = renderWebProbeScriptResult({
    ok: false,
    status: "blocked",
    command: "web-probe console-verify --node NC01 --lane v03",
    node: "NC01",
    lane: "v03",
    url: "https://hwlab.example.test",
    degradedReason: "stdout-empty",
    failureKind: "stdout-empty",
    result: {
      exitCode: 43,
      timedOut: false,
      stderrTail: "Error: browser launch failed\n    at runner.mjs:1:1",
      stdoutTail: "partial output",
    },
  });

  const text = String(rendered.renderedText ?? "");
  assert.match(text, /stderrTail\s+Error: browser launch failed at runner\.mjs:1:1/u);
  assert.match(text, /stdoutTail\s+partial output/u);
  assert.match(text, /COMMAND_OUTPUT\nstderrTail: Error: browser launch failed at runner\.mjs:1:1\nstdoutTail: partial output/u);
});

test("web-probe command governance distinguishes ad-hoc scripts from repo-owned generated commands", () => {
  const base = {
    action: "script" as const,
    node: "D601",
    lane: "v03",
    url: "https://hwlab.example.test",
    originName: "public" as const,
    originMode: "public" as const,
    originConfigPath: "config/hwlab-node-lanes.yaml#webProbe.origins.public",
    timeoutMs: 30000,
    viewport: "1920x1080",
    browserProxyMode: "auto" as const,
    browserProxyModeSource: "yaml-origin" as const,
    commandTimeoutSeconds: 60,
    scriptText: "return { ok: true };",
    scriptSource: {
      kind: "stdin" as const,
      path: null,
      byteCount: 20,
      sha256: "sha256:fixture",
    },
  };

  const adHoc = webProbeScriptCommandGovernance(base);
  assert.equal(adHoc.commandPromotionHint.promotionRequiredBeforeRepeat, true);
  assert.equal(adHoc.commandPromotionHint.action, "promote-before-repeat");
  assert.equal(adHoc.warnings.length, 1);
  assert.equal(adHoc.warnings[0]?.requiredAction, "promote-before-repeat");
  assert.match(adHoc.hints[0] ?? "", /repo-owned typed `web-probe` command/u);
  assert.deepEqual(Object.keys(adHoc.preferredCommands)[0], "typedCommandCatalog");
  assert.ok(Object.values(adHoc.preferredCommands).every((command) => command.startsWith("bun scripts/cli.ts web-probe")));

  const repoOwned = webProbeScriptCommandGovernance({
    ...base,
    commandLabel: "web-probe opencode-smoke --node D601 --lane v03",
    suppressAdHocWarning: true,
    generatedHints: ["OpenCode smoke is a repo-owned typed web-probe command."],
    generatedPreferredCommands: {
      withExpectedText: "web-probe opencode-smoke --node D601 --lane v03 --expect-text <assistant-substring>",
    },
    scriptSource: {
      ...base.scriptSource,
      kind: "generated",
      path: "builtin:opencode-smoke",
    },
  });
  assert.equal(repoOwned.commandPromotionHint.promotionRequiredBeforeRepeat, false);
  assert.equal(repoOwned.commandPromotionHint.action, "reuse-repo-owned-typed-command");
  assert.deepEqual(repoOwned.warnings, []);
  assert.doesNotMatch(repoOwned.hints.join("\n"), /temporary script|promote-before-repeat/iu);
});
