import assert from "node:assert/strict";
import test from "node:test";

import {
  buildRpt004Report,
  classifyCodeAgentSessionTurns,
  classifyCodeAgentSkillPath,
  classifySkillCliM3Route,
  classifyM3DurableEvidence,
  classifyRpt004Conclusion,
  parseArgs,
  validateLiveBoundary
} from "./rpt004-mvp-e2e-harness.mjs";

const fixtureCommit = "667349554b1f";

test("RPT-004 CLI accepts the required live URL/API URL shape", () => {
  const args = parseArgs([
    "--live",
    "--url",
    "http://74.48.78.17:16666/",
    "--api-url",
    "http://74.48.78.17:16667/"
  ]);

  assert.equal(args.live, true);
  assert.equal(args.url, "http://74.48.78.17:16666/");
  assert.equal(args.apiUrl, "http://74.48.78.17:16667/");
  assert.equal(args.writeReport, true);
  assert.equal(args.allowM3Write, false);
  assert.doesNotThrow(() => validateLiveBoundary(args));
});

test("RPT-004 CLI requires explicit flag before live M3 writes", () => {
  const defaultLive = parseArgs([
    "--live",
    "--url",
    "http://74.48.78.17:16666/",
    "--api-url",
    "http://74.48.78.17:16667/"
  ]);
  const writeLive = parseArgs([
    "--live",
    "--allow-m3-write",
    "--url",
    "http://74.48.78.17:16666/",
    "--api-url",
    "http://74.48.78.17:16667/"
  ]);

  assert.equal(defaultLive.allowM3Write, false);
  assert.equal(writeLive.allowM3Write, true);
});

test("RPT-004 live boundary rejects deprecated or swapped active DEV ports", () => {
  assert.throws(
    () => validateLiveBoundary({ url: "http://74.48.78.17:6666/", apiUrl: "http://74.48.78.17:16667/" }),
    /16666/u
  );
  assert.throws(
    () => validateLiveBoundary({ url: "http://74.48.78.17:16667/", apiUrl: "http://74.48.78.17:16666/" }),
    /16666/u
  );
});

test("RPT-004 classifies deployed revision drift as NOT_CURRENT, never MVP PASS", () => {
  const conclusion = classifyRpt004Conclusion([
    check("expected-artifact-current", "pass"),
    check("live-revision-current", "not_current"),
    check("live-health-ready", "pass"),
    check("16666-default-chinese-workbench", "pass")
  ]);

  assert.equal(conclusion.status, "not_current");
  assert.equal(conclusion.classification, "NOT_CURRENT");
  assert.equal(conclusion.mvpPass, false);
});

test("RPT-004 classifies degraded live health as BLOCKED, never MVP PASS", () => {
  const conclusion = classifyRpt004Conclusion([
    check("expected-artifact-current", "pass"),
    check("live-revision-current", "pass"),
    check("live-health-ready", "blocked"),
    check("16666-default-chinese-workbench", "pass"),
    check("m3-v1-io-trusted-loop", "not_run")
  ]);

  assert.equal(conclusion.status, "blocked");
  assert.equal(conclusion.classification, "BLOCKED");
  assert.equal(conclusion.mvpPass, false);
});

test("RPT-004 reports MVP_PASS only when every required live dimension passes", () => {
  const conclusion = classifyRpt004Conclusion([
    check("expected-artifact-current", "pass"),
    check("live-revision-current", "pass"),
    check("live-health-ready", "pass"),
    check("16666-default-chinese-workbench", "pass"),
    check("code-agent-skill-path", "pass"),
    check("code-agent-session-thread", "pass"),
    check("skill-cli-hwlab-api-m3-route", "pass"),
    check("m3-v1-io-trusted-loop", "pass"),
    check("operation-audit-evidence-durable-green", "pass")
  ]);

  assert.equal(conclusion.status, "pass");
  assert.equal(conclusion.classification, "MVP_PASS");
  assert.equal(conclusion.mvpPass, true);
});

test("Code Agent OpenAI fallback cannot satisfy skill path/toolCalls readiness", () => {
  const result = classifyCodeAgentSkillPath({
    status: "completed",
    provider: "openai-responses",
    model: "gpt-5.5",
    backend: "hwlab-cloud-api/openai-responses",
    implementationType: "openai-responses-fallback",
    runner: {
      kind: "openai-responses-fallback"
    },
    reply: {
      content: "text answer"
    },
    toolCalls: [],
    skills: {
      status: "not_requested",
      items: []
    }
  }, { httpStatus: 200, httpOk: true });

  assert.equal(result.status, "blocked");
  assert.match(result.summary, /openai-fallback/u);
});

test("Code Agent Codex stdio skill discovery passes with completed toolCalls and repo skill", () => {
  const result = classifyCodeAgentSkillPath({
    status: "completed",
    conversationId: "cnv_test",
    sessionId: "ses_test",
    messageId: "msg_test",
    traceId: "trc_test",
    provider: "codex-stdio",
    model: "gpt-5.5",
    backend: "hwlab-cloud-api/codex-app-server-stdio",
    workspace: "/workspace/hwlab",
    sandbox: "workspace-write",
    capabilityLevel: "long-lived-codex-stdio-session",
    sessionMode: "codex-app-server-stdio-long-lived",
    implementationType: "repo-owned-codex-app-server-stdio-session",
    session: {
      sessionId: "ses_test",
      status: "idle",
      idleTimeoutMs: 1800000,
      lastTraceId: "trc_test",
      longLivedSession: true,
      codexStdio: true
    },
    sessionReuse: {
      reused: false,
      turn: 1
    },
    runner: {
      kind: "codex-app-server-stdio-runner",
      workspace: "/workspace/hwlab",
      sandbox: "workspace-write",
      sessionMode: "codex-app-server-stdio-long-lived",
      implementationType: "repo-owned-codex-app-server-stdio-session",
      codexStdio: true,
      writeCapable: true,
      durableSession: true
    },
    runnerTrace: {
      runnerKind: "codex-app-server-stdio-runner"
    },
    longLivedSessionGate: {
      status: "pass",
      pass: true
    },
    toolCalls: [{
      name: "skills.discover",
      status: "completed",
      type: "file-read"
    }],
    skills: {
      status: "ready",
      items: [{
        name: "hwlab-agent-runtime",
        source: "/workspace/hwlab/skills/hwlab-agent-runtime/SKILL.md"
      }],
      count: 1
    }
  }, { httpStatus: 200, httpOk: true });

  assert.equal(result.status, "pass");
});

test("Code Agent controlled read-only session skill discovery passes the harness skill path gate", () => {
  const result = classifyCodeAgentSkillPath({
    ...codexSessionPayload({
      toolName: "skills.discover",
      skills: {
        status: "ready",
        items: [{
          name: "hwlab-agent-runtime",
          source: "/workspace/hwlab/skills/hwlab-agent-runtime/SKILL.md"
        }],
        count: 1
      }
    }),
    provider: "codex-readonly-runner",
    backend: "hwlab-cloud-api/codex-readonly-runner",
    sandbox: "read-only",
    capabilityLevel: "read-only-session-tools",
    sessionMode: "controlled-readonly-session-registry",
    implementationType: "controlled-readonly-session-registry",
    runner: {
      kind: "hwlab-readonly-runner",
      workspace: "/workspace/hwlab",
      sandbox: "read-only",
      sessionMode: "controlled-readonly-session-registry",
      implementationType: "controlled-readonly-session-registry",
      codexStdio: false,
      writeCapable: false,
      durableSession: false
    },
    runnerTrace: {
      runnerKind: "hwlab-readonly-runner",
      sessionMode: "controlled-readonly-session-registry"
    }
  }, { httpStatus: 200, httpOk: true });

  assert.equal(result.status, "pass");
  assert.equal(result.observations.controlledSessionRunner, true);
});

test("Code Agent three-turn same session classifier passes with pwd, skills, and context reference", () => {
  const turns = [
    sessionTurn("pwd", codexSessionPayload({
      toolName: "pwd",
      turn: 1,
      reused: false,
      reply: "当前工作目录是 /workspace/hwlab。"
    })),
    sessionTurn("skills", codexSessionPayload({
      toolName: "skills.discover",
      turn: 2,
      reused: true,
      skills: {
        status: "ready",
        items: [{
          name: "hwlab-agent-runtime",
          source: "/workspace/hwlab/skills/hwlab-agent-runtime/SKILL.md"
        }],
        count: 1
      },
      reply: "可用 skill 包括 hwlab-agent-runtime。"
    })),
    sessionTurn("context-pwd", codexSessionPayload({
      toolName: "pwd",
      turn: 3,
      reused: true,
      reply: "前文第一轮你问的是 pwd；当前工作目录仍是 /workspace/hwlab。"
    }))
  ];

  const result = classifyCodeAgentSessionTurns(turns, { expectedConversationId: "cnv_rpt004_fixture" });

  assert.equal(result.status, "pass");
  assert.deepEqual(result.observations.conversationIds, ["cnv_rpt004_fixture"]);
  assert.deepEqual(result.observations.sessionIds, ["ses_rpt004_fixture"]);
  assert.equal(result.observations.turns[2].referencesPrevious, true);
});

test("Code Agent fallback-only turns cannot satisfy session checks", () => {
  const turns = [
    sessionTurn("pwd", openAiFallbackPayload()),
    sessionTurn("skills", openAiFallbackPayload()),
    sessionTurn("context-pwd", openAiFallbackPayload({ reply: "第一轮是 pwd。" }))
  ];

  const result = classifyCodeAgentSessionTurns(turns, { expectedConversationId: "cnv_rpt004_fixture" });

  assert.equal(result.status, "blocked");
  assert.equal(result.observations.fallbackOnly, true);
  assert.ok(result.observations.blockers.includes("fallback-only"));
});

test("RPT-004 live report emits NOT_CURRENT and skips downstream checks", async () => {
  const report = await buildRpt004Report({
    ...parseArgs([
      "--live",
      "--url",
      "http://74.48.78.17:16666/",
      "--api-url",
      "http://74.48.78.17:16667/",
      "--expected-commit",
      fixtureCommit
    ]),
    writeReport: false
  }, {
    repoRoot: "/fixture-rpt004",
    fsJson: fixtureArtifactJson(),
    now: () => "2026-05-23T00:00:00.000Z",
    httpGetJson: async (url) => ({ ok: true, status: 200, json: healthPayload(url, { revision: "1111111" }) }),
    httpGetText: async () => ({
      ok: true,
      status: 200,
      contentType: "text/html; charset=utf-8",
      body: workbenchHtml()
    }),
    layoutRunner: async () => {
      throw new Error("layout must not run when NOT_CURRENT");
    }
  });

  assert.equal(report.status, "not_current");
  assert.equal(report.conclusion.classification, "NOT_CURRENT");
  assert.equal(report.dimensions.codeAgentSession.status, "not_run");
  assert.equal(report.dimensions.skillCliM3Route.status, "not_run");
  assert.equal(report.liveMutationUsed, false);
});

test("M3 durable evidence requires operation, audit, evidence, DEV-LIVE green, and persisted writes", () => {
  const green = classifyM3DurableEvidence(greenOperations());
  assert.equal(green.status, "pass");

  const blockedOps = greenOperations();
  blockedOps[2].evidenceState.writeStatus = "written_non_durable";
  const blocked = classifyM3DurableEvidence(blockedOps);
  assert.equal(blocked.status, "blocked");
  assert.equal(blocked.observations.sequence[2].persisted, false);
});

function check(id, status) {
  return {
    id,
    status,
    summary: `${id} ${status}`
  };
}

function greenOperations() {
  return [
    operation("write-do1-true", "do.write", true),
    operation("read-di1-true", "di.read", true),
    operation("write-do1-false", "do.write", false),
    operation("read-di1-false", "di.read", false)
  ];
}

function operation(id, action, value) {
  return {
    id,
    action,
    status: "succeeded",
    operationId: `op_${id}`,
    traceId: `trc_${id}`,
    auditId: `aud_${id}`,
    evidenceId: `evd_${id}`,
    resultValue: value,
    evidenceState: {
      status: "green",
      sourceKind: "DEV-LIVE",
      durable: true,
      writeStatus: "persisted"
    }
  };
}

function sessionTurn(kind, payload, response = { ok: true, status: 200 }) {
  return {
    kind,
    response,
    payload
  };
}

function codexSessionPayload({
  conversationId = "cnv_rpt004_fixture",
  sessionId = "ses_rpt004_fixture",
  toolName = "pwd",
  turn = 1,
  reused = false,
  reply = "当前工作目录是 /workspace/hwlab。",
  skills = {
    status: "not_requested",
    items: [],
    count: 0
  }
} = {}) {
  return {
    status: "completed",
    conversationId,
    sessionId,
    messageId: `msg_rpt004_${turn}`,
    traceId: `trc_rpt004_${turn}`,
    provider: "codex-stdio",
    model: "gpt-5.5",
    backend: "hwlab-cloud-api/codex-app-server-stdio",
    workspace: "/workspace/hwlab",
    sandbox: "workspace-write",
    capabilityLevel: "long-lived-codex-stdio-session",
    sessionMode: "codex-app-server-stdio-long-lived",
    implementationType: "repo-owned-codex-app-server-stdio-session",
    session: {
      sessionId,
      status: "idle",
      idleTimeoutMs: 1800000,
      lastTraceId: `trc_rpt004_${turn}`,
      turn,
      longLivedSession: true,
      codexStdio: true,
      durable: true,
      writeCapable: true
    },
    sessionReuse: {
      conversationId,
      sessionId,
      reused,
      turn,
      status: "idle"
    },
    runner: {
      kind: "codex-app-server-stdio-runner",
      workspace: "/workspace/hwlab",
      sandbox: "workspace-write",
      session: "codex-app-server-stdio-long-lived",
      sessionMode: "codex-app-server-stdio-long-lived",
      implementationType: "repo-owned-codex-app-server-stdio-session",
      codexStdio: true,
      writeCapable: true,
      durableSession: true
    },
    runnerTrace: {
      traceId: `trc_rpt004_${turn}`,
      runnerKind: "codex-app-server-stdio-runner",
      sessionMode: "codex-app-server-stdio-long-lived",
      sessionId,
      turn,
      sessionReused: reused,
      events: [`turn:${turn}`, `tool:${toolName}:completed`]
    },
    longLivedSessionGate: {
      status: "pass",
      pass: true
    },
    toolCalls: [{
      name: toolName,
      status: "completed",
      type: toolName === "skills.discover" ? "file-read" : "shell",
      traceId: `trc_rpt004_${turn}`
    }],
    skills,
    reply: {
      content: reply
    }
  };
}

function openAiFallbackPayload({ reply = "text fallback" } = {}) {
  return {
    status: "completed",
    conversationId: "cnv_rpt004_fixture",
    sessionId: "ses_rpt004_fixture",
    messageId: "msg_fallback",
    traceId: "trc_fallback",
    provider: "openai-responses",
    model: "gpt-5.5",
    backend: "hwlab-cloud-api/openai-responses",
    implementationType: "openai-responses-fallback",
    capabilityLevel: "text-chat-only",
    sessionMode: "provider-text-request",
    runner: {
      kind: "openai-responses-fallback"
    },
    runnerTrace: {
      runnerKind: "openai-responses-fallback"
    },
    toolCalls: [],
    skills: {
      status: "not_requested",
      items: []
    },
    reply: {
      content: reply
    }
  };
}
function healthPayload(url, { revision = fixtureCommit } = {}) {
  const isWeb = String(url).includes(":16666");
  return {
    serviceId: isWeb ? "hwlab-cloud-web" : "hwlab-cloud-api",
    environment: "dev",
    status: "ready",
    ready: true,
    commit: {
      id: revision
    },
    image: {
      tag: revision.slice(0, 7),
      digest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    },
    runtime: {
      durable: true,
      ready: true,
      status: "ready",
      blocker: null,
      liveRuntimeEvidence: true
    },
    readiness: {
      ready: true,
      status: "ready",
      durability: {
        ready: true,
        status: "ready",
        blocker: null
      }
    }
  };
}

function workbenchHtml() {
  return `
<!doctype html>
<html lang="zh-CN">
  <body data-app-shell>
    <main class="workbench-shell">
      <section data-view="workspace">
        <h1>HWLAB 云工作台</h1>
        <aside class="resource-tree">硬件资源</aside>
        <section class="center-workspace">Agent 对话 使用说明</section>
        <aside class="right-sidebar">可信记录 内部复核</aside>
        <form id="command-form" class="command-form"></form>
      </section>
      <section data-view="gate" hidden>gate</section>
    </main>
  </body>
</html>`;
}

function fixtureArtifactJson() {
  const serviceIds = [
    "hwlab-cloud-api",
    "hwlab-cloud-web",
    "hwlab-gateway",
    "hwlab-gateway-simu",
    "hwlab-box-simu",
    "hwlab-patch-panel",
    "hwlab-router",
    "hwlab-tunnel-client",
    "hwlab-edge-proxy",
    "hwlab-agent-skills"
  ];
  const deploy = {
    commitId: fixtureCommit
  };
  const catalog = {
    commitId: fixtureCommit,
    artifactState: "published",
    publish: {
      ciPublished: true,
      registryVerified: true
    },
    services: serviceIds.map((serviceId) => ({
      serviceId,
      commitId: fixtureCommit,
      imageTag: fixtureCommit.slice(0, 7),
      digest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
      publishState: "published"
    }))
  };
  const artifactReport = {
    commitId: fixtureCommit,
    artifactPublish: {
      sourceCommitId: fixtureCommit
    }
  };
  return async (relativePath) => {
    if (relativePath === "deploy/deploy.yaml") return deploy;
    if (relativePath === "deploy/artifact-catalog.dev.json") return catalog;
    if (relativePath === "/tmp/hwlab-dev-gate/dev-artifacts.json") return artifactReport;
    return null;
  };
}
