import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import test from "node:test";
import path from "node:path";
import { fileURLToPath } from "node:url";

import {
  checkFrontendNoDirectRuntimeCalls,
  classifyM3IoControlReport,
  controlPathReachablePersistenceBlocked,
  expectedM3IoLiveSequence,
  parseM3IoControlE2eArgs,
  requireM3IoControlE2eGates,
  resolveM3IoEndpoint,
  validateExpectedRequestSchemas,
  validateM3IoLiveResponse
} from "./src/m3-io-control-e2e.mjs";

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");

test("M3 IO E2E default mode is SOURCE/static and never permits mutation", () => {
  const args = parseM3IoControlE2eArgs([]);
  assert.equal(args.live, false);
  assert.equal(args.target, "frontend");
  assert.equal(args.frontendUrl, "http://74.48.78.17:16666/");

  assert.deepEqual(requireM3IoControlE2eGates(args), {
    mode: "source-static",
    liveWriteAllowed: false,
    mutationAllowed: false
  });
});

test("M3 IO E2E live mode requires explicit DEV and non-production confirmations", () => {
  assert.throws(
    () => requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--live", "--confirm-dev"])),
    /requires --live --confirm-dev --confirmed-non-production/u
  );
  assert.throws(
    () => requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--live", "--confirm-dev", "--confirmed-non-production", "--dry-run"])),
    /cannot be combined/u
  );
  assert.deepEqual(
    requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--live", "--confirm-dev", "--confirmed-non-production"])),
    {
      mode: "dev-live",
      liveWriteAllowed: true,
      mutationAllowed: true
    }
  );
  assert.deepEqual(
    requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--live", "--confirm-dev", "--expect-non-prod"])),
    {
      mode: "dev-live",
      liveWriteAllowed: true,
      mutationAllowed: true
    }
  );
});

test("M3 IO E2E rejects dangerous flags and non-DEV live endpoints", () => {
  assert.throws(
    () => requireM3IoControlE2eGates(parseM3IoControlE2eArgs(["--prod"])),
    /forbidden/u
  );
  assert.throws(
    () => resolveM3IoEndpoint(parseM3IoControlE2eArgs([
      "--live",
      "--confirm-dev",
      "--confirmed-non-production",
      "--url",
      "http://127.0.0.1:16666/"
    ])),
    /live endpoint must match target/u
  );
  assert.equal(
    resolveM3IoEndpoint(parseM3IoControlE2eArgs(["--live", "--confirm-dev", "--confirmed-non-production"])).href,
    "http://74.48.78.17:16666/v1/m3/io"
  );
  assert.throws(
    () => resolveM3IoEndpoint(parseM3IoControlE2eArgs([
      "--live",
      "--confirm-dev",
      "--confirmed-non-production",
      "--url",
      "http://74.48.78.17:16667/"
    ])),
    /frontend uses DEV/u
  );
  assert.equal(
    resolveM3IoEndpoint(parseM3IoControlE2eArgs([
      "--live",
      "--confirm-dev",
      "--confirmed-non-production",
      "--target",
      "api"
    ])).href,
    "http://74.48.78.17:16667/v1/m3/io"
  );
});

test("M3 IO E2E source mode CLI prints non-mutating SOURCE/DRY-RUN report", () => {
  const stdout = execFileSync("node", ["scripts/m3-io-control-e2e.mjs"], {
    cwd: repoRoot,
    encoding: "utf8",
    stdio: ["ignore", "pipe", "pipe"]
  });
  const report = JSON.parse(stdout);
  assert.equal(report.mode, "source-static");
  assert.equal(report.sourceKind, "SOURCE");
  assert.equal(report.liveMutation.attempted, false);
  assert.equal(report.liveMutation.allowed, false);
  assert.equal(report.dryRunPlan.sourceKind, "DRY-RUN");
  assert.equal(report.dryRunPlan.liveWriteWillRun, false);
  assert.equal(report.summary.classification, "source_static_contract_checked");
  assert.equal(report.summary.trustedGreen, false);
});

test("M3 IO E2E request schemas are fixed to DO1 write and DI1 read", () => {
  const result = validateExpectedRequestSchemas(expectedM3IoLiveSequence());
  assert.equal(result.status, "pass");
  assert.deepEqual(
    result.sequence.map((step) => [step.action, step.gatewayId, step.resourceId, step.boxId, step.port, step.value]),
    [
      ["do.write", "gwsimu_1", "res_boxsimu_1", "boxsimu_1", "DO1", true],
      ["di.read", "gwsimu_2", "res_boxsimu_2", "boxsimu_2", "DI1", undefined],
      ["do.write", "gwsimu_1", "res_boxsimu_1", "boxsimu_1", "DO1", false],
      ["di.read", "gwsimu_2", "res_boxsimu_2", "boxsimu_2", "DI1", undefined]
    ]
  );

  const invalid = expectedM3IoLiveSequence().map((step) =>
    step.id === "write-do1-true"
      ? { ...step, request: { ...step.request, port: "DI1" } }
      : step
  );
  assert.equal(validateExpectedRequestSchemas(invalid).status, "fail");
});

test("M3 IO E2E classifies reachable control with blocked durable persistence correctly", () => {
  const operations = expectedM3IoLiveSequence().map((step) => ({
    id: step.id,
    action: step.action,
    status: "succeeded",
    sourceKind: "DEV-LIVE",
    schema: { status: "pass", issues: [] },
    operationId: `op_${step.id}`,
    traceId: `trc_${step.id}`,
    auditId: `aud_${step.id}`,
    evidenceId: `ev_${step.id}`,
    resultValue: step.expectedValue,
    evidenceState: {
      status: "blocked",
      sourceKind: "BLOCKED",
      durable: false,
      writeStatus: "not_written"
    }
  }));
  const classification = classifyM3IoControlReport({
    checks: [{ id: "source", status: "pass" }],
    liveOperations: operations
  });
  assert.deepEqual(classification, {
    status: "blocked",
    classification: controlPathReachablePersistenceBlocked,
    trustedGreen: false
  });
});

test("M3 IO E2E classifies trusted green only with durable DEV-LIVE evidence", () => {
  const operations = expectedM3IoLiveSequence().map((step) => ({
    id: step.id,
    action: step.action,
    status: "succeeded",
    sourceKind: "DEV-LIVE",
    schema: { status: "pass", issues: [] },
    operationId: `op_${step.id}`,
    traceId: `trc_${step.id}`,
    auditId: `aud_${step.id}`,
    evidenceId: `ev_${step.id}`,
    resultValue: step.expectedValue,
    evidenceState: {
      status: "green",
      sourceKind: "DEV-LIVE",
      durable: true,
      writeStatus: "persisted"
    }
  }));
  assert.deepEqual(
    classifyM3IoControlReport({
      checks: [{ id: "source", status: "pass" }],
      liveOperations: operations
    }),
    {
      status: "pass",
      classification: "trusted_green",
      trustedGreen: true
    }
  );
});

test("M3 IO E2E live durable green evidence overrides stale source check failure", () => {
  const operations = expectedM3IoLiveSequence().map((step) => ({
    id: step.id,
    action: step.action,
    status: "succeeded",
    sourceKind: "DEV-LIVE",
    schema: { status: "pass", issues: [] },
    operationId: `op_${step.id}`,
    traceId: `trc_${step.id}`,
    auditId: `aud_${step.id}`,
    evidenceId: `ev_${step.id}`,
    resultValue: step.expectedValue,
    evidenceState: {
      status: "green",
      sourceKind: "DEV-LIVE",
      durable: true,
      writeStatus: "persisted"
    }
  }));
  assert.deepEqual(
    classifyM3IoControlReport({
      checks: [{ id: "source", status: "fail", summary: "stale source check" }],
      liveOperations: operations
    }),
    {
      status: "pass",
      classification: "trusted_green",
      trustedGreen: true
    }
  );
});

test("M3 IO E2E does not claim trusted green for SOURCE, DRY-RUN, LOCAL, fixture, or BLOCKED backend evidence", () => {
  for (const sourceKind of ["SOURCE", "DRY-RUN", "LOCAL", "fixture", "BLOCKED"]) {
    const operations = expectedM3IoLiveSequence().map((step) => ({
      id: step.id,
      action: step.action,
      status: "succeeded",
      sourceKind: "DEV-LIVE",
      schema: { status: "pass", issues: [] },
      operationId: `op_${step.id}`,
      traceId: `trc_${step.id}`,
      auditId: `aud_${step.id}`,
      evidenceId: `ev_${step.id}`,
      resultValue: step.expectedValue,
      evidenceState: {
        status: "green",
        sourceKind,
        durable: true,
        writeStatus: "persisted"
      }
    }));
    assert.deepEqual(
      classifyM3IoControlReport({
        checks: [{ id: "source", status: "pass" }],
        liveOperations: operations
      }),
      {
        status: "blocked",
        classification: controlPathReachablePersistenceBlocked,
        trustedGreen: false
      }
    );
  }
});

test("M3 IO E2E frontend guardrail requires readiness before controls unlock", () => {
  const appSource = `
    function m3ControlCanOperate(contract) {
      return contract?.status === "available" &&
        contract?.readiness?.status === "ready" &&
        contract?.readiness?.controlReady === true &&
        contract?.readiness?.sourceKind === "DEV-LIVE" &&
        contract?.readiness?.evidenceLevel === "DEV-LIVE";
    }
    async function runM3IoAction() {
      if (!m3ControlCanOperate()) return;
      return fetchJson("/v1/m3/io", { method: "POST" });
    }
  `;
  const pass = checkFrontendNoDirectRuntimeCalls({
    appSource,
    artifactPublisherSource: 'if (url.pathname === "/v1/m3/io" || url.pathname === "/v1/m3/status") proxyCloudApi();'
  });
  assert.equal(pass.status, "pass");

  const fail = checkFrontendNoDirectRuntimeCalls({
    appSource: `
      function m3ControlCanOperate(contract) {
        return contract?.status === "available";
      }
      async function runM3IoAction() {
        return fetchJson("/v1/m3/io", { method: "POST" });
      }
    `,
    artifactPublisherSource: 'if (url.pathname === "/v1/m3/io" || url.pathname === "/v1/m3/status") proxyCloudApi();'
  });
  assert.equal(fail.status, "fail");
  assert.ok(fail.issues.some((issue) => /readiness/u.test(issue)));
});

test("M3 IO E2E frontend guardrail catches direct gateway or patch-panel calls", () => {
  const appSource = `
    function m3ControlCanOperate(contract) {
      return contract?.status === "available" &&
        contract?.readiness?.status === "ready" &&
        contract?.readiness?.controlReady === true &&
        contract?.readiness?.sourceKind === "DEV-LIVE" &&
        contract?.readiness?.evidenceLevel === "DEV-LIVE";
    }
    async function runM3IoAction() {
      if (!m3ControlCanOperate()) return;
      return fetchJson("/v1/m3/io", { method: "POST" });
    }
    async function other() {
      return fetch("/v1");
    }
  `;
  const pass = checkFrontendNoDirectRuntimeCalls({
    appSource,
    artifactPublisherSource: 'if (url.pathname === "/v1/m3/io" || url.pathname === "/v1/m3/status") proxyCloudApi();'
  });
  assert.equal(pass.status, "pass");

  const fail = checkFrontendNoDirectRuntimeCalls({
    appSource: `
      async function runM3IoAction() {
        await fetch("http://hwlab-gateway-simu-1.hwlab-dev.svc.cluster.local:7101/invoke");
      }
    `,
    artifactPublisherSource: ""
  });
  assert.equal(fail.status, "fail");
  assert.ok(fail.issues.some((issue) => /direct runtime|same-origin|runM3IoAction/u.test(issue)));
});

test("M3 IO E2E live response schema requires identifiers and frontendBypass=false", () => {
  const [step] = expectedM3IoLiveSequence();
  const valid = validateM3IoLiveResponse({
    status: "succeeded",
    accepted: true,
    action: step.action,
    chain: {
      projectId: "prj_m3_hardware_loop",
      sourceGatewayId: "gwsimu_1",
      sourceGatewaySessionId: "gws_gwsimu_1",
      sourceResourceId: "res_boxsimu_1",
      sourceBoxId: "boxsimu_1",
      sourcePort: "DO1",
      targetGatewayId: "gwsimu_2",
      targetGatewaySessionId: "gws_gwsimu_2",
      targetResourceId: "res_boxsimu_2",
      targetBoxId: "boxsimu_2",
      targetPort: "DI1",
      patchPanelServiceId: "hwlab-patch-panel"
    },
    controlPath: {
      cloudApi: true,
      gatewaySimu: true,
      boxSimu: true,
      patchPanel: true,
      frontendBypass: false
    },
    operationId: "op_m3_test",
    traceId: "trc_m3_test",
    auditId: "aud_m3_test",
    evidenceId: "evd_m3_test",
    evidenceState: {
      status: "blocked",
      sourceKind: "BLOCKED"
    },
    result: {
      value: true
    }
  }, step);
  assert.equal(valid.status, "pass");

  const invalid = validateM3IoLiveResponse({
    status: "succeeded",
    accepted: true,
    action: step.action,
    chain: {},
    controlPath: {
      frontendBypass: true
    },
    traceId: "trc_m3_test",
    evidenceState: {
      status: "blocked"
    },
    result: {
      value: false
    }
  }, step);
  assert.equal(invalid.status, "fail");
  assert.ok(invalid.issues.some((issue) => /operationId|frontendBypass|chain|result\.value/u.test(issue)));
});
