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

import {
  buildDevRuntimePostflightReport,
  parseArgs
} from "./dev-runtime-postflight.mjs";

test("check mode is source-only and does not attempt live reads or M3 writes", async () => {
  const report = await buildDevRuntimePostflightReport(parseArgs(["--check"]), {
    m3SourceBuilder: async () => ({
      mode: "source-static",
      summary: {
        status: "pass",
        classification: "source_static_contract_checked",
        trustedGreen: false,
        result: "source checked"
      }
    }),
    now: () => "2026-05-23T00:00:00.000Z"
  });

  assert.equal(report.conclusion.status, "pass");
  assert.equal(report.actions.healthLiveReadAttempted, false);
  assert.equal(report.actions.v1ReadAttempted, false);
  assert.equal(report.actions.m3MutationAttempted, false);
  assert.equal(report.safety.secretValuesPrinted, false);
});

test("live mode refuses without explicit DEV confirmations", async () => {
  const report = await buildDevRuntimePostflightReport(parseArgs(["--live"]), {
    now: () => "2026-05-23T00:00:00.000Z"
  });

  assert.equal(report.conclusion.status, "blocked");
  assert.equal(report.safetyRefusal, true);
  assert.equal(report.actions.healthLiveReadAttempted, false);
  assert.equal(report.actions.m3MutationAttempted, false);
});

test("live mode skips M3 writes when /health/live durable runtime is blocked", async () => {
  const report = await buildDevRuntimePostflightReport(
    parseArgs(["--live", "--confirm-dev", "--confirmed-non-production"]),
    {
      httpGetJson: async (url) => ({ ok: true, status: 200, json: apiPayload({ ready: url.endsWith("/v1") }) }),
      m3Runner: async () => {
        throw new Error("M3 runner should not run before durable readiness is green");
      },
      now: () => "2026-05-23T00:00:00.000Z"
    }
  );

  assert.equal(report.conclusion.status, "blocked");
  assert.equal(report.actions.healthLiveReadAttempted, true);
  assert.equal(report.actions.v1ReadAttempted, true);
  assert.equal(report.actions.m3MutationAttempted, false);
  assert.equal(report.m3.status, "not_run");
  assert.equal(report.blockers.some((blocker) => blocker.scope === "api-health-live"), true);
  assertStructuredBlockers(report.blockers);
  assert.match(report.blockers.find((blocker) => blocker.scope === "api-health-live").reason, /auth_blocked/u);
  assert.equal(JSON.stringify(report).includes("postgresql://"), false);
});

test("live mode requires /v1 durable readiness before M3 writes", async () => {
  const report = await buildDevRuntimePostflightReport(
    parseArgs(["--live", "--confirm-dev", "--confirmed-non-production"]),
    {
      httpGetJson: async (url) => ({ ok: true, status: 200, json: apiPayload({ ready: !url.endsWith("/v1") }) }),
      m3Runner: async () => {
        throw new Error("M3 runner should not run when /v1 is blocked");
      },
      now: () => "2026-05-23T00:00:00.000Z"
    }
  );

  assert.equal(report.conclusion.status, "blocked");
  assert.equal(report.actions.m3MutationAttempted, false);
  assert.equal(report.blockers.some((blocker) => blocker.scope === "api-v1"), true);
  assertStructuredBlockers(report.blockers);
});

test("live mode requires /v1 to expose the controlled M3 IO route before M3 writes", async () => {
  const report = await buildDevRuntimePostflightReport(
    parseArgs(["--live", "--confirm-dev", "--confirmed-non-production"]),
    {
      httpGetJson: async (url) => {
        const payload = apiPayload({ ready: true });
        if (url.endsWith("/v1")) {
          delete payload.m3IoControl;
        }
        return { ok: true, status: 200, json: payload };
      },
      m3Runner: async () => {
        throw new Error("M3 runner should not run when /v1 omits the M3 IO contract");
      },
      now: () => "2026-05-23T00:00:00.000Z"
    }
  );

  assert.equal(report.conclusion.status, "blocked");
  assert.equal(report.actions.m3MutationAttempted, false);
  assert.equal(report.blockers.some((blocker) =>
    blocker.scope === "api-v1" &&
    blocker.summary.includes("controlled M3 IO route")
  ), true);
  assertStructuredBlockers(report.blockers);
});

test("live mode does not mistake DB live evidence for durable runtime readiness", async () => {
  const report = await buildDevRuntimePostflightReport(
    parseArgs(["--live", "--confirm-dev", "--confirmed-non-production"]),
    {
      httpGetJson: async () => ({ ok: true, status: 200, json: tcpOnlyPayload() }),
      m3Runner: async () => {
        throw new Error("M3 runner should not run when only TCP/SecretRef DB readiness is green");
      },
      now: () => "2026-05-23T00:00:00.000Z"
    }
  );

  assert.equal(report.conclusion.status, "blocked");
  assert.equal(report.apiHealth.db.connected, true);
  assert.equal(report.apiHealth.db.liveDbEvidence, true);
  assert.equal(report.apiHealth.runtime.durable, false);
  assert.equal(report.apiHealth.readiness.durabilityReady, false);
  assert.equal(report.actions.m3MutationAttempted, false);
  assert.equal(report.blockers.some((blocker) => blocker.scope === "api-health-live"), true);
  assertStructuredBlockers(report.blockers);
});

test("live mode blocks auth, schema, and migration durable readiness before M3 writes", async () => {
  for (const [blocker, queryResult, blockedLayer] of [
    ["runtime_durable_adapter_auth_blocked", "auth_blocked", "auth"],
    ["runtime_durable_adapter_schema_blocked", "schema_blocked", "schema"],
    ["runtime_durable_adapter_migration_blocked", "migration_blocked", "migration"]
  ]) {
    const report = await buildDevRuntimePostflightReport(
      parseArgs(["--live", "--confirm-dev", "--confirmed-non-production"]),
      {
        httpGetJson: async () => ({ ok: true, status: 200, json: apiPayload({ ready: false, blocker, queryResult, blockedLayer }) }),
        m3Runner: async () => {
          throw new Error("M3 runner should not run when durable readiness is blocked");
        },
        now: () => "2026-05-23T00:00:00.000Z"
      }
    );

    assert.equal(report.conclusion.status, "blocked");
    assert.equal(report.actions.m3MutationAttempted, false);
    assert.equal(report.m3.status, "not_run");
    assert.equal(report.apiHealth.runtimeDurableReadiness.classification, blocker);
    assert.equal(report.apiHealth.runtimeDurableReadiness.evidence.queryResult, queryResult);
    assert.equal(report.apiV1.runtimeDurableReadiness.classification, blocker);
    assertStructuredBlockers(report.blockers);
  }
});

test("live mode treats durable_readiness_ready queryResult as ready, not blocked", async () => {
  const report = await buildDevRuntimePostflightReport(
    parseArgs(["--live", "--confirm-dev", "--confirmed-non-production"]),
    {
      httpGetJson: async () => ({ ok: true, status: 200, json: apiPayload({ ready: true, queryResult: "durable_readiness_ready" }) }),
      m3Runner: async () => m3GreenReport(),
      now: () => "2026-05-23T00:00:00.000Z"
    }
  );

  assert.equal(report.conclusion.status, "pass");
  assert.equal(report.actions.m3MutationAttempted, true);
  assert.equal(report.apiHealth.runtimeDurableReadiness.ready, true);
  assert.equal(report.apiHealth.runtimeDurableReadiness.evidence.queryResult, "durable_readiness_ready");
  assert.equal(report.blockers.length, 0);
});

test("live mode records M3 true/false durable green evidence", async () => {
  const report = await buildDevRuntimePostflightReport(
    parseArgs(["--live", "--confirm-dev", "--confirmed-non-production"]),
    {
      httpGetJson: async () => ({ ok: true, status: 200, json: apiPayload({ ready: true }) }),
      m3Runner: async () => m3GreenReport(),
      now: () => "2026-05-23T00:00:00.000Z"
    }
  );

  assert.equal(report.conclusion.status, "pass");
  assert.equal(report.actions.m3MutationAttempted, true);
  assert.equal(report.actions.m3MutationAllowed, true);
  assert.equal(report.m3.trustedGreen, true);
  assert.equal(report.m3.operationCount, 4);
  assert.equal(report.m3.persisted, true);
  assert.equal(report.m3.persistenceContract.ready, true);
  assert.deepEqual(report.m3.persistenceContract.sequence.map((item) => [item.id, item.observedValue, item.persisted]), [
    ["write-do1-true", true, true],
    ["read-di1-true", true, true],
    ["write-do1-false", false, true],
    ["read-di1-false", false, true]
  ]);
  assert.deepEqual(report.m3.operations.map((operation) => operation.resultValue), [true, true, false, false]);
  assert.equal(report.blockers.length, 0);
});

test("live mode accepts 4 DEV-LIVE persisted green operations even when legacy summary says source_contract_failed", async () => {
  const m3 = m3GreenReport();
  m3.summary.status = "blocked";
  m3.summary.classification = "source_contract_failed";
  m3.summary.trustedGreen = false;
  m3.summary.result = "legacy source/static contract failed";
  const report = await buildDevRuntimePostflightReport(
    parseArgs(["--live", "--confirm-dev", "--confirmed-non-production"]),
    {
      httpGetJson: async () => ({ ok: true, status: 200, json: apiPayload({ ready: true }) }),
      m3Runner: async () => m3,
      now: () => "2026-05-23T00:00:00.000Z"
    }
  );

  assert.equal(report.conclusion.status, "pass");
  assert.equal(report.m3.persisted, true);
  assert.equal(report.m3.persistenceContract.ready, true);
  assert.equal(report.m3.operationCount, 4);
  assert.deepEqual(report.m3.persistenceContract.sequence.map((item) => [
    item.id,
    item.observedValue,
    item.evidenceGreen,
    item.persisted
  ]), [
    ["write-do1-true", true, true, true],
    ["read-di1-true", true, true, true],
    ["write-do1-false", false, true, true],
    ["read-di1-false", false, true, true]
  ]);
  assert.equal(report.blockers.some((blocker) =>
    blocker.scope === "m3-durable-postflight" &&
    blocker.evidence?.classification === "source_contract_failed"
  ), false);
});

test("live mode blocks when M3 operation evidence is not durable green", async () => {
  const m3 = m3GreenReport();
  m3.liveOperations[1].evidenceState.durable = false;
  const report = await buildDevRuntimePostflightReport(
    parseArgs(["--live", "--confirm-dev", "--confirmed-non-production"]),
    {
      httpGetJson: async () => ({ ok: true, status: 200, json: apiPayload({ ready: true }) }),
      m3Runner: async () => m3,
      now: () => "2026-05-23T00:00:00.000Z"
    }
  );

  assert.equal(report.conclusion.status, "blocked");
  assert.equal(report.blockers.some((blocker) => blocker.scope === "m3-durable-postflight"), true);
  assert.equal(report.m3.persistenceContract.ready, false);
  assertStructuredBlockers(report.blockers);
});

test("live mode blocks when M3 true/false persisted sequence is incomplete", async () => {
  const m3 = m3GreenReport();
  m3.liveOperations = m3.liveOperations.slice(0, 3);
  const report = await buildDevRuntimePostflightReport(
    parseArgs(["--live", "--confirm-dev", "--confirmed-non-production"]),
    {
      httpGetJson: async () => ({ ok: true, status: 200, json: apiPayload({ ready: true }) }),
      m3Runner: async () => m3,
      now: () => "2026-05-23T00:00:00.000Z"
    }
  );

  assert.equal(report.conclusion.status, "blocked");
  assert.equal(report.m3.persistenceContract.ready, false);
  assert.equal(report.m3.persistenceContract.sequence.at(-1).status, "missing");
  assert.equal(report.blockers.some((blocker) => blocker.scope === "m3-durable-postflight"), true);
  assertStructuredBlockers(report.blockers);
});

function assertStructuredBlockers(blockers) {
  assert.ok(blockers.length > 0);
  for (const blocker of blockers) {
    assert.equal(typeof blocker.scope, "string");
    assert.equal(typeof blocker.reason, "string");
    assert.equal(typeof blocker.impact, "string");
    assert.equal(typeof blocker.safeNextAction, "string");
    assert.equal(typeof blocker.retryable, "boolean");
    assert.notEqual(blocker.reason, "");
    assert.notEqual(blocker.impact, "");
    assert.notEqual(blocker.safeNextAction, "");
  }
}

function apiPayload({
  ready,
  blocker = "runtime_durable_adapter_auth_blocked",
  queryResult = ready ? "durable_readiness_ready" : "auth_blocked",
  blockedLayer = "auth"
}) {
  return {
    serviceId: "hwlab-cloud-api",
    environment: "dev",
    status: ready ? "ready" : "blocked",
    ready,
    db: {
      ready,
      connected: ready,
      liveDbEvidence: ready,
      endpointSource: "secret-url-host",
      connectionResult: ready ? "connected" : "connected",
      runtimeReadiness: {
        ready,
        status: ready ? "ready" : "blocked",
        blocker: ready ? null : blocker,
        queryResult,
        requiredEvidence: "runtime_adapter_schema_migration_read_query"
      }
    },
    runtime: {
      adapter: "postgres",
      durable: ready,
      durableRequested: true,
      ready,
      status: ready ? "ready" : "blocked",
      blocker: ready ? null : blocker,
      liveRuntimeEvidence: ready,
      connection: {
        queryResult
      },
      durabilityContract: {
        requiredEvidence: "runtime_adapter_schema_migration_read_query",
        blockedLayer: ready ? null : blockedLayer
      },
      gates: {
        ssl: gate(true),
        auth: gate(ready || !["auth"].includes(blockedLayer), blockedLayer === "auth" ? blocker : null),
        schema: gate(ready || !["schema"].includes(blockedLayer), blockedLayer === "schema" ? blocker : null),
        migration: gate(ready || !["migration"].includes(blockedLayer), blockedLayer === "migration" ? blocker : null),
        durability: gate(ready, blocker)
      }
    },
    readiness: {
      ready,
      status: ready ? "ready" : "blocked",
      durability: {
        ready,
        status: ready ? "ready" : "blocked",
        blocker: ready ? null : blocker,
        blockedLayer: ready ? null : blockedLayer,
        queryResult
      }
    },
    blockerCodes: ready ? []
      : [blocker],
    m3IoControl: {
      route: "/v1/m3/io",
      status: "available",
      contractVersion: "m3-io-control-v1",
      boundaries: {
        directFrontendGatewayOrBoxAccess: false,
        genericHardwareRpcExposedToFrontend: false
      }
    }
  };
}

function tcpOnlyPayload() {
  const payload = apiPayload({ ready: false });
  payload.db.ready = true;
  payload.db.connected = true;
  payload.db.liveDbEvidence = true;
  payload.db.connectionResult = "connected";
  payload.db.runtimeReadiness.blocker = "runtime_durable_adapter_query_blocked";
  payload.db.runtimeReadiness.queryResult = "query_blocked";
  payload.runtime.blocker = "runtime_durable_adapter_query_blocked";
  payload.runtime.durabilityContract.blockedLayer = "durability_query";
  payload.runtime.gates.auth = gate(true);
  payload.runtime.gates.schema = gate(true);
  payload.runtime.gates.migration = gate(true);
  payload.runtime.gates.durability = gate(false, "runtime_durable_adapter_query_blocked");
  payload.readiness.durability.blockedLayer = "durability_query";
  payload.blockerCodes = ["runtime_durable_adapter_query_blocked"];
  return payload;
}

function gate(ready, blocker = null) {
  return {
    checked: true,
    ready,
    status: ready ? "ready" : "blocked",
    blocker: ready ? null : blocker
  };
}

function m3GreenReport() {
  const values = [true, true, false, false];
  return {
    mode: "dev-live",
    summary: {
      status: "pass",
      classification: "trusted_green",
      trustedGreen: true,
      result: "DEV-LIVE M3 IO control path and durable trusted records are green."
    },
    liveOperations: values.map((value, index) => ({
      id: ["write-do1-true", "read-di1-true", "write-do1-false", "read-di1-false"][index],
      action: index % 2 === 0 ? "do.write" : "di.read",
      status: "succeeded",
      operationId: `op_${index}`,
      traceId: `trc_${index}`,
      auditId: `aud_${index}`,
      evidenceId: `ev_${index}`,
      resultValue: value,
      evidenceState: {
        status: "green",
        durable: true,
        sourceKind: "DEV-LIVE",
        writeStatus: "persisted"
      }
    }))
  };
}
