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

import { rootPath } from "./config";
import { hwlabRuntimeLaneSpecForNode } from "./hwlab-node-lanes";
import { parseWebProbeConsoleVerificationProfiles } from "./hwlab-node-web-probe-console-profile";
import { nodeWebProbeConsoleVerifyScript } from "./hwlab-node-web-probe-console-verify";
import { webProbeScriptCommandGovernance } from "./hwlab-node/web-observe-scripts";
import { parseNodeWebProbeOptions } from "./hwlab-node/web-probe";

function minimalProfile(): Record<string, unknown> {
  return {
    navigationTimeoutMs: 30000,
    settleMs: 100,
    commandTimeoutSeconds: 120,
    minTextLength: 8,
    maxHorizontalOverflowPx: 2,
    outputLimits: {
      failures: 20,
      pageErrors: 20,
      consoleErrors: 20,
      requestFailures: 20,
      responseErrors: 20,
      screenshots: 1,
      textPreviewChars: 120,
    },
    viewports: [{ id: "desktop", width: 1920, height: 1080 }],
    routes: [{ id: "home", path: "/home", readySelector: "#app", screenshot: true }],
    workflows: {},
  };
}

test("Cloud Console verification profile is owned by lane YAML", () => {
  const spec = hwlabRuntimeLaneSpecForNode("v03", "NC01");
  const profileName = spec.webProbe?.defaultConsoleVerificationProfile;
  const profile = profileName === undefined ? undefined : spec.webProbe?.consoleVerificationProfiles?.[profileName];
  assert.equal(profileName, "cloud-console");
  assert.ok(profile);
  assert.deepEqual(profile.viewports.map((item) => [item.width, item.height]), [[1920, 1080], [960, 600], [390, 844]]);
  assert.equal(profile.routes.length, 20);
  assert.equal(profile.routes.filter((item) => item.screenshot).length * profile.viewports.length + (profile.workflows.agentObserver?.views.length ?? 0), 24);
  assert.deepEqual(profile.workflows.hwpod?.requiredCommandPrefixes, ["Get-FileHash .\\", "python .\\"]);
  const agentrunTree = spec.webProbe?.consoleVerificationProfiles?.["agentrun-tree"];
  assert.ok(agentrunTree);
  assert.deepEqual(agentrunTree.routes, [{
    id: "agent-runs-tree",
    path: "/agents/runs?view=tree",
    readySelector: ".agent-mind-map",
    screenshot: false,
  }]);
  assert.deepEqual(agentrunTree.viewports.map((item) => [item.width, item.height]), [[1920, 1080]]);
  assert.deepEqual(agentrunTree.workflows.agentObserver?.selection, {
    selector: ".agent-mind-map-node[data-kind='run']",
    inspectorSelector: ".agent-observer-inspector",
    screenshotName: "agentrun-tree-inspector.png",
  });
  assert.equal(agentrunTree.outputLimits.screenshots, 2);
  const tasktree = spec.webProbe?.consoleVerificationProfiles?.tasktree;
  assert.ok(tasktree);
  assert.deepEqual(tasktree.viewports.map((item) => [item.width, item.height]), [[1920, 1080], [960, 600], [390, 844]]);
  assert.equal(tasktree.routes[0]?.readySelector, "body");
  assert.equal(tasktree.workflows.tasktree?.reportSelector, ".tasktree-reports article");
  assert.equal(tasktree.workflows.tasktree?.scaleSelector, ".tasktree-zoom output");
  assert.equal(tasktree.workflows.tasktree?.secondsScaleLabel, "秒");
  assert.equal(tasktree.workflows.tasktree?.detailSelector, ".tasktree-detail-panel");
  assert.equal(tasktree.workflows.tasktree?.dockedSelector, ".tasktree-docked-detail");
  assert.equal(tasktree.outputLimits.screenshots, 5);
});

test("Cloud Console profile parser rejects unknown fields and duplicate route ids", () => {
  assert.throws(() => parseWebProbeConsoleVerificationProfiles({
    fixture: { ...minimalProfile(), hiddenFallback: true },
  }, "fixture.consoleVerificationProfiles"), /unsupported fields: hiddenFallback/u);

  const duplicate = minimalProfile();
  duplicate.routes = [
    { id: "same", path: "/one", readySelector: "#app", screenshot: false },
    { id: "same", path: "/two", readySelector: "#app", screenshot: false },
  ];
  assert.throws(() => parseWebProbeConsoleVerificationProfiles({ fixture: duplicate }, "fixture.consoleVerificationProfiles"), /duplicate same/u);
});

test("generated Cloud Console runner is valid JavaScript and contains no node or lane authority", () => {
  const profile = parseWebProbeConsoleVerificationProfiles({ fixture: minimalProfile() }, "fixture.consoleVerificationProfiles").fixture;
  assert.ok(profile);
  const script = nodeWebProbeConsoleVerifyScript("fixture", profile, "config/example.yaml#profiles.fixture");
  assert.doesNotThrow(() => new Function(script.replace("export default ", "")));
  const generatorSource = readFileSync(rootPath("scripts/src/hwlab-node-web-probe-console-verify.ts"), "utf8");
  assert.doesNotMatch(generatorSource, /NC01|JD01|D601|\/dashboard|lab-dev\.hwpod\.com/u);
  assert.match(script, /config\/example\.yaml#profiles\.fixture/u);
  assert.match(script, /requestedRoute/u);
  assert.match(script, /noInspectorChromeOverlap/u);
  assert.match(script, /visibleEdgeCount/u);
  assert.match(script, /getComputedStyle\(edge\)/u);
  assert.match(script, /style\.stroke !== "none"/u);
  assert.match(script, /page\.keyboard\.press\("Enter"\)/u);
  assert.match(script, /verifyTasktree/u);
  assert.match(script, /ganttInternalOverflowPx/u);
  assert.match(script, /page\.on\("pageerror"/u);
  assert.match(script, /page\.on\("requestfailed"/u);
  assert.match(script, /pageErrors/u);
  assert.match(script, /requestFailures/u);
  assert.match(script, /detailMode/u);
  assert.match(script, /return \{\s+ok,\s+status:[\s\S]*?pageErrors,\s+consoleErrors,\s+requestFailures,\s+responseErrors,[\s\S]*?profileName: config\.profileName/u);
});

test("long console verification uses the existing remote artifact job", () => {
  const observeScripts = readFileSync(rootPath("scripts/src/hwlab-node/web-observe-scripts.ts"), "utf8");
  const remoteArtifact = readFileSync(rootPath("scripts/src/web-probe-remote-artifact.ts"), "utf8");
  assert.match(observeScripts, /options\.commandTimeoutSeconds > 55/u);
  assert.match(observeScripts, /runWebProbeRemoteArtifactJob/u);
  assert.match(observeScripts, /remote-artifact-download/u);
  assert.match(remoteArtifact, /--allow-text-transfer/u);
  assert.match(remoteArtifact, /\*\.webp\|\*\.json/u);
});

test("console-verify parses into the existing managed script action", () => {
  const options = parseNodeWebProbeOptions([
    "console-verify",
    "--node", "NC01",
    "--lane", "v03",
    "--origin", "internal",
    "--profile", "cloud-console",
  ]);
  assert.equal(options.action, "script");
  if (options.action !== "script") return;
  assert.equal(options.originName, "internal");
  assert.equal(options.viewport, "1920x1080");
  assert.equal(options.commandTimeoutSeconds, 900);
  assert.equal(options.suppressAdHocWarning, true);
  assert.equal(options.scriptSource.kind, "generated");
  assert.equal(options.scriptSource.path, "builtin:console-verify/cloud-console");
  assert.match(options.commandLabel ?? "", /web-probe console-verify/u);
  const governance = webProbeScriptCommandGovernance(options);
  assert.deepEqual(governance.warnings, []);
  assert.equal(governance.commandPromotionHint.repoOwnedTypedCommand, true);
  assert.equal(governance.commandPromotionHint.action, "reuse-repo-owned-typed-command");
});

test("console-verify rejects profiles not declared by owning YAML", () => {
  assert.throws(() => parseNodeWebProbeOptions([
    "console-verify",
    "--node", "NC01",
    "--lane", "v03",
    "--origin", "internal",
    "--profile", "not-configured",
  ]), /unknown --profile not-configured/u);
});
