/**
 * AEGIS SOAR (AS/SOAR/2026/001) — M3 CHUNK 3: goAML Form-B EXPORT — PROD-CONFIRM (live HTTP).
 *
 * STRUCTURAL prod-confirm of the READ-ONLY export GET /api/fincrime/cases/:id/formb.
 * The export WRITES NOTHING; this driver only reads (GET) + probes the absence of any
 * write verb. It drives the REAL prod HTTP routes as the seeded auditor `okello`
 * (role auditor, bound aegis-sovereign — the route allows admin/risk_manager/auditor).
 *
 * GOAML_* are NOT set in prod, so the render uses code DEFAULTS (placeholders, NOT the
 * registered FIA values) — the agreed structural confirm. The live goAML/FIA submission
 * stays NOT built (M3-fenced); this proves the platform RENDERS correctly.
 *
 * Legs (paired — Rule 18):
 *   P1 CORRECT (positive)      — okello GET formb(canonical disposed) → 200; rendered
 *                                fields == prod stored facts; no fabricated FIA ack;
 *                                no 64-hex hash leak; config = placeholders.
 *   P2 PRECONDITION GATE (neg) — near-miss (PENDING + PREPARED STR) → 404 formb_not_renderable
 *                                (the sharp control: gates on disposed-AND-prepared, not
 *                                "has a prepared STR"); bogus uuid → 404.
 *   P3 READ-ONLY FENCE         — POST/PUT/PATCH/DELETE formb (valid session+CSRF) → 404
 *                                (no write verb); paired with GET 200. (Durable child-count
 *                                read-back runs separately via executeSql prod.)
 *   P4 AUTHZ (paired)          — anon GET (no session) → 401; paired with okello 200.
 *
 * Secret discipline (Rule 1): password read from process.env, NEVER printed; session
 * cookie/csrf held in a Jar, NEVER printed. The decrypted STR narrative (payload.reason)
 * is REDACTED to a presence+length marker — never printed.
 *
 * DEV/TEST tooling only — never ships in the product runtime. No DB access (prod DB is a
 * read-only RLS-bypassing replica reached only via executeSql; this process's DATABASE_URL
 * is DEV and is deliberately untouched).
 */

const PROD_URL = process.env.PROD_URL ?? "https://aegis-cyber.replit.app";

const CID = "6d894ee0-2600-47f3-8d19-43d90f7ca7db"; // canonical disposed two-eyes case (FU-071)
const NEAR = "89615ca8-4dad-4e65-9633-99322ff90ce2"; // near-miss: PENDING + PREPARED STR
const BOGUS = "00000000-0000-4000-8000-000000000000"; // valid-format uuid, no such case
const STR_ID = "c8207a47-da44-4b5c-9ea0-8cca172c53f0"; // canonical STR (PREPARED, ack cols NULL)
const EXPECT_ENTITY_REF = `AEGIS-STR-${STR_ID}`;
const EXPECT_TOP_KEYS = JSON.stringify(
  ["activity", "filingStatus", "governance", "reason", "reportHeader", "reportIndicators", "reportingPerson"],
);

function need(k: string): string {
  const v = process.env[k];
  if (!v || v.length === 0) throw new Error(`missing required env ${k}`);
  return v;
}

class Jar {
  private cookies = new Map<string, string>();
  ingest(res: Response): void {
    const setCookies = (res.headers as unknown as { getSetCookie?: () => string[] }).getSetCookie?.() ?? [];
    for (const sc of setCookies) {
      const pair = sc.split(";")[0];
      const idx = pair.indexOf("=");
      if (idx > 0) this.cookies.set(pair.slice(0, idx).trim(), pair.slice(idx + 1).trim());
    }
  }
  header(): string {
    return [...this.cookies.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
  }
}

interface RouteResult { status: number; json: Record<string, unknown> }

async function getCsrf(jar: Jar, xff: string): Promise<string> {
  const res = await fetch(`${PROD_URL}/api/csrf-token`, { headers: { cookie: jar.header(), "x-forwarded-for": xff } });
  jar.ingest(res);
  return ((await res.json().catch(() => ({}))) as { csrfToken?: string })?.csrfToken ?? "";
}

async function call(
  method: string, path: string, jar: Jar | null, csrf: string | null, body: unknown, xff: string,
): Promise<RouteResult> {
  const headers: Record<string, string> = { "x-forwarded-for": xff };
  if (body !== undefined) headers["content-type"] = "application/json";
  if (jar) headers.cookie = jar.header();
  if (csrf) headers["x-csrf-token"] = csrf;
  const res = await fetch(`${PROD_URL}${path}`, { method, headers, body: body !== undefined ? JSON.stringify(body) : undefined });
  const json = (await res.json().catch(() => ({}))) as Record<string, unknown>;
  return { status: res.status, json };
}

interface Session { jar: Jar; csrf: string; xff: string }
async function login(username: string, password: string, xff: string): Promise<Session> {
  const jar = new Jar();
  const res = await fetch(`${PROD_URL}/api/auth/login`, {
    method: "POST",
    headers: { "content-type": "application/json", "x-forwarded-for": xff },
    body: JSON.stringify({ username, password }),
  });
  jar.ingest(res);
  const j = (await res.json().catch(() => ({}))) as { success?: boolean };
  if (res.status !== 200 || j?.success !== true) throw new Error(`login(${username}) failed HTTP ${res.status}`);
  const csrf = await getCsrf(jar, xff);
  if (!csrf) throw new Error(`login(${username}) — no CSRF minted`);
  return { jar, csrf, xff };
}

function allocateActorIps(n: number, runId = Date.now()): string[] {
  const ips: string[] = [];
  for (let i = 0; i < n; i++) ips.push(`203.0.113.${((Math.floor(runId / 1000) + i * 7) % 240) + 1}`);
  return ips;
}

const reasonOf = (r: RouteResult): string => {
  const j = r.json as any;
  return String(j?.error?.details?.reason ?? j?.details?.reason ?? j?.reason ?? "");
};

interface Leg { id: string; scenario: string; expect: string; observed: string; pass: boolean }
const legs: Leg[] = [];
const leg = (id: string, scenario: string, expect: string, observed: string, pass: boolean): void => {
  legs.push({ id, scenario, expect, observed, pass });
};

async function main(): Promise<void> {
  console.log("=".repeat(90));
  console.log("AEGIS SOAR M3 chunk 3 — goAML Form-B EXPORT — PROD-CONFIRM (read-only, live HTTP)");
  console.log(`spec AS/SOAR/2026/001 | env: PROD | ${PROD_URL} | ${new Date().toISOString()}`);
  console.log("=".repeat(90));

  const audPw = need("ENGINEER_OKELLO_PASSWORD");
  const ips = allocateActorIps(2);
  const aud = await login("okello", audPw, ips[0]);
  const anonIp = ips[1];
  console.log("login OK: okello (auditor, aegis-sovereign) + anon jar\n");

  const get = (p: string, s: Session) => call("GET", p, s.jar, s.csrf, undefined, s.xff);

  // ── P1 — CORRECT (positive): canonical disposed case renders & matches stored facts ──
  {
    const r = await get(`/api/fincrime/cases/${CID}/formb`, aud);
    const p = r.json as any;
    const c = {
      s200: r.status === 200,
      reportCode: p?.reportHeader?.reportCode === "STR",
      entityRef: p?.reportHeader?.entityReference === EXPECT_ENTITY_REF,
      fiuRefNull: p?.reportHeader?.fiuRefNumber === null,
      strPrepared: p?.filingStatus?.status === "PREPARED",
      notFiled: p?.filingStatus?.filed === false,
      ackAllNull: p?.filingStatus?.fiaReference === null && p?.filingStatus?.filedAt === null && p?.filingStatus?.acknowledgedAt === null,
      disposition: p?.governance?.disposition === "STR_RECOMMENDED",
      approvedRole: p?.governance?.approvedByRole === "risk_manager",
      preparedRolePresent: typeof p?.governance?.preparedByRole === "string" && p.governance.preparedByRole.length > 0,
      opaqueParty: p?.activity?.reportParty?.partyType === "OPAQUE_EXTERNAL_REFERENCE",
      civilDeferred: p?.activity?.reportParty?.civilIdentityResolution === "DEFERRED_SEPARATELY_GATED",
      narrativePresent: typeof p?.reason === "string" && p.reason.length > 0,
      topKeys: JSON.stringify(Object.keys(p ?? {}).sort()) === EXPECT_TOP_KEYS,
      cfgPlaceholders: p?.reportHeader?.rentityId === "UG-FIA-RENTITY-AEGIS" && p?.reportingPerson?.id === "AEGIS-MLCO",
    };
    // leak guard: no sha256 (64-hex) anywhere in the payload (narrative redacted out first)
    const redacted = { ...p, reason: `[redacted: present=${c.narrativePresent}, len=${String(p?.reason ?? "").length}]` };
    const noHashLeak = !/[0-9a-f]{64}/i.test(JSON.stringify(redacted));
    const pass = Object.values(c).every(Boolean) && noHashLeak;
    leg("P1", "CORRECT — okello GET formb(canonical disposed) renders & matches stored facts",
      "HTTP 200; reportCode STR; entityRef AEGIS-STR-<strId>; fiuRef NULL; STR PREPARED; ack cols NULL; disposition STR_RECOMMENDED; approvedByRole risk_manager; OPAQUE party; narrative present (redacted); top-keys exact; config=placeholders; no 64-hex leak",
      `HTTP ${r.status}; checks=${JSON.stringify(c)}; noHashLeak=${noHashLeak}`, pass);
    console.log("--- P1 rendered payload (narrative REDACTED; config = placeholders, NOT registered FIA values) ---");
    console.log(JSON.stringify(redacted, null, 2));
    console.log("");
  }

  // ── P2 — PRECONDITION GATE (negative): non-disposed / absent → 404 formb_not_renderable ──
  {
    const r = await get(`/api/fincrime/cases/${NEAR}/formb`, aud);
    const pass = r.status === 404 && reasonOf(r) === "formb_not_renderable";
    leg("P2a", "GATE — near-miss (PENDING_MLCO_REVIEW, HAS a PREPARED STR) is NOT renderable",
      "HTTP 404 reason=formb_not_renderable (gate requires disposed-AND-prepared, not just prepared)",
      `HTTP ${r.status}; reason=${reasonOf(r)}`, pass);
  }
  {
    const r = await get(`/api/fincrime/cases/${BOGUS}/formb`, aud);
    const pass = r.status === 404 && reasonOf(r) === "formb_not_renderable";
    leg("P2b", "GATE — non-existent case id (valid uuid format) is NOT renderable",
      "HTTP 404 reason=formb_not_renderable", `HTTP ${r.status}; reason=${reasonOf(r)}`, pass);
  }

  // ── P3 — READ-ONLY FENCE: no write verb on the export path (valid session+CSRF → 404) ──
  for (const m of ["POST", "PUT", "PATCH", "DELETE"]) {
    const r = await call(m, `/api/fincrime/cases/${CID}/formb`, aud.jar, aud.csrf, {}, aud.xff);
    const pass = r.status === 404; // CSRF satisfied (token sent) → 404 means no such route/verb, not a hollow 403
    leg(`P3-${m}`, `FENCE — ${m} formb(canonical) with valid session+CSRF (no write verb may exist)`,
      "HTTP 404 (no route for this verb; CSRF satisfied so not a hollow 403)", `HTTP ${r.status}`, pass);
  }
  {
    const r = await get(`/api/fincrime/cases/${CID}/formb`, aud); // paired positive for the fence
    leg("P3-GET", "FENCE PAIR — GET formb(canonical) still serves (read works; only writes are absent)",
      "HTTP 200", `HTTP ${r.status}`, r.status === 200);
  }

  // ── P4 — AUTHZ (paired): anon (no session) → 401; positive is P1 okello 200 ──
  {
    const anonJar = new Jar();
    const anonCsrf = await getCsrf(anonJar, anonIp);
    const r = await call("GET", `/api/fincrime/cases/${CID}/formb`, anonJar, anonCsrf, undefined, anonIp);
    const pass = r.status === 401;
    leg("P4", "AUTHZ — anonymous GET formb (no session) is rejected (paired positive: P1 okello 200)",
      "HTTP 401", `HTTP ${r.status}`, pass);
  }

  // ── report ──
  console.log("");
  for (const l of legs) {
    console.log("-".repeat(90));
    console.log(`[${l.id}] ${l.scenario}`);
    console.log(`     expect  : ${l.expect}`);
    console.log(`     observe : ${l.observed}`);
    console.log(`     VERDICT : ${l.pass ? "PASS" : "FAIL"}`);
  }
  console.log("-".repeat(90));
  const passed = legs.filter((l) => l.pass).length;
  console.log(`ACCEPTANCE: ${passed}/${legs.length} legs PASS — ${passed === legs.length ? "ALL PASS" : "SEE FAILURES ABOVE"}`);
  if (passed !== legs.length) process.exitCode = 1;
}

main().catch((e) => { console.error("FATAL:", e instanceof Error ? e.message : String(e)); process.exit(1); });
