/**
 * Platform Verification Plan — ref AS/AEGIS-CYBER/VERIFY/2026/001
 * VF module 1 — REGULATOR-GATEWAY "Group A" durable-persistence FIX
 * LIVE DEMONSTRATION (6-leg)
 *
 * Group A scope = the two regulator-session/sandbox swallow sites only
 * (createRegulatorSession + createSandboxEnvironment). The SAR/dossier swallow
 * sites belong to later groups and are NOT touched here.
 *
 * The ORIGINAL hole: createRegulatorSession() built the session in-memory, set
 * the cache + bumped the counter, and persisted with a fire-and-forget
 * `RegDB.saveRegulatorSession(session).catch(() => {})` (same for the sandbox).
 * A failed durable write was silently swallowed: the API returned 200, the cache
 * advertised a live session/sandbox, but no row was ever committed — and on a
 * SANDBOX request a half-state was possible (sandbox persisted, session lost, or
 * vice-versa) with no error surfaced anywhere.
 *
 * The FIX (persist-then-cache, on the request transaction):
 *   - createRegulatorSession is async; on a SANDBOX request it awaits
 *     createSandboxEnvironment FIRST (which awaits saveSandbox), stamps the
 *     sandbox id onto the session, THEN awaits saveRegulatorSession, and only
 *     after BOTH writes succeed does it set the caches + bump the counters.
 *   - the `.catch(() => {})` swallows are gone, so a write failure propagates to
 *     the route's asyncHandler → HTTP 500.
 *   - because both writes ride the same request transaction (tenant-middleware
 *     COMMIT-on-finish<500 / ROLLBACK-on-finish>=500), a session-write failure
 *     after a successful sandbox-write rolls the sandbox row back too — no orphan,
 *     no cache half-state.
 *
 * This script exercises the REAL exported functions and the REAL middleware —
 * it is NOT a code-read.
 *
 * Six legs:
 *   L1a — POSITIVE / real server :5000 / READ_ONLY: login → csrf →
 *         POST /api/regulator/session, then independent owner read-back asserts
 *         exactly ONE regulator_sessions row with sandbox_environment_id NULL and
 *         ZERO sandbox rows.
 *   L1b — POSITIVE / real server :5000 / SANDBOX: same, asserts ONE session row
 *         whose sandbox_environment_id is non-NULL AND ONE sandbox_environments
 *         row whose id === that reference (referential integrity through the
 *         persist-then-cache ordering).
 *   L2a — NEGATIVE / in-script Express + REAL tenantMiddleware / READ_ONLY, fault
 *         armed at "regulator.saveRegulatorSession": handler returns 500, error
 *         is the deliberate FaultInjectionError, ZERO session rows, cache + counter
 *         unchanged (no swallow, no half-state).
 *   L2b — NEGATIVE / in-script / SANDBOX, fault armed at the SESSION write: the
 *         sandbox write SUCCEEDS inside the request tx, then the session write
 *         throws → 500 → middleware ROLLBACK → BOTH rows absent. The atomic-nested
 *         proof — paired with L3b (its positive control, same path, which DOES
 *         produce a sandbox row).
 *   L3a — CONTROL / in-script / SANDBOX, BOTH flags unset: hook is inert-by-default
 *         → 200, both rows present (proves the fault primitive is a no-op when not
 *         armed).
 *   L3b — CONTROL / in-script / SANDBOX, master flag ARMED but TARGET non-matching:
 *         hook is tag-scoped → no-op → 200, both rows present (proves an armed
 *         master flag cannot fire on a tag it was not aimed at).
 *
 * FAITHFULNESS NOTES (Rule 5 / Rule 19 — claims tagged by HOW verified):
 *   - L2/L3 mount the REAL tenantMiddleware, so the inner db.insert() calls ride
 *     a genuine request transaction with the real COMMIT/ROLLBACK-on-finish
 *     semantics — the exact mechanism under test. The fault is injected ONLY via
 *     process.env toggles read by server/lib/fault-injection.ts; no product code
 *     is altered by this script.
 *   - regulator_sessions and sandbox_environments are non-RLS tables with no
 *     tenant_id column, so the inserts are NOT gated by the GUC tenant context;
 *     that is what makes L2b's rollback proof genuine (the sandbox row really
 *     lands in the tx before the session write throws, then is rolled back).
 *   - Positive legs go through the real server process, whose in-memory cache this
 *     script cannot inspect; their proof is the independent owner DB read-back.
 *     Negative/control legs run in THIS process, so their cache/counter state is
 *     asserted directly via the exported getters.
 *
 * Connections:
 *   - owner = pg.Pool on DATABASE_URL (dev DB owner; BYPASSRLS; independent
 *             out-of-band observer for seed / read-back / cleanup).
 *   - real  = the imported functions + middleware, which use appPool (aegis_app
 *             restricted role) via the `db` proxy.
 *
 * Side-effect discipline: imports only db, tenant-middleware, regulator-gateway,
 * and fault-injection — NOT server/index.ts or server/routes.ts (no scheduler /
 * second server boot). SEED_DEMO_DATA is left unset, so importing
 * regulator-gateway does NOT seed fabricated demo rows.
 */

import pg from "pg";
import bcrypt from "bcryptjs";
import express from "express";
import { once } from "node:events";
import { randomUUID } from "node:crypto";
import { db } from "../server/db";
import { tenantMiddleware } from "../server/lib/tenant-middleware";
import {
  createRegulatorSession,
  getRegulatorSessions,
  getSandboxes,
  getRegulatoryState,
} from "../server/lib/regulator-gateway";

const owner = new pg.Pool({ connectionString: process.env.DATABASE_URL });

const SERVER = "http://127.0.0.1:5000";
const DEMO_TENANT = "aegis-sovereign"; // a real dev tenant (seeded on boot)
const HARNESS_XFF = "203.0.113.55"; // RFC 5737 TEST-NET-3 — own rate-limit bucket
const base = Date.now();

// per-leg sentinels (regulatorId keys session read-back; regulatorName === the
// sandbox's created_for, which keys the sandbox read-back).
const RID = (leg: string) => `REGID-${leg}-${base}`;
const RNAME = (leg: string) => `REGNAME-${leg}-${base}`;
const ALL_RIDS = ["L1A", "L1B", "L2A", "L2B", "L3A", "L3B"].map(RID);
const ALL_RNAMES = ["L1A", "L1B", "L2A", "L2B", "L3A", "L3B"].map(RNAME);

const settle = (ms = 1500) => new Promise((r) => setTimeout(r, ms));

async function sessionRowsByRegId(regulatorId: string) {
  const r = await owner.query<{
    id: string;
    access_level: string;
    sandbox_environment_id: string | null;
  }>(
    "SELECT id, access_level, sandbox_environment_id FROM regulator_sessions WHERE regulator_id = $1",
    [regulatorId],
  );
  return r.rows;
}

async function sandboxRowsByCreatedFor(createdFor: string) {
  const r = await owner.query<{ id: string }>(
    "SELECT id FROM sandbox_environments WHERE created_for = $1",
    [createdFor],
  );
  return r.rows;
}

// ── minimal cookie jar (Node fetch / undici getSetCookie) ────────────────────
class Jar {
  private cookies = new Map<string, string>();
  ingest(res: Response) {
    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 LegResult {
  id: string;
  scenario: string;
  expectation: string;
  observed: string;
  verdict: string;
  pass: boolean;
}
const results: LegResult[] = [];
const rec = (r: LegResult) => results.push(r);

// ─────────────────────────────────────────────────────────────────────────────
// POSITIVE legs — through the REAL running server (:5000)
// ─────────────────────────────────────────────────────────────────────────────
async function positiveLegs(): Promise<string | null> {
  const demoUserId = randomUUID();
  const demoUsername = `vf-reg-demo-${base}`;
  const demoPassword = `Vf!Reg-${base}-Demo`;
  const demoHash = bcrypt.hashSync(demoPassword, 10);

  // seed via owner (BYPASSRLS): users + primary UTR on the real dev tenant, so
  // login resolves session.tenantId = aegis-sovereign exactly as a real actor.
  await owner.query(
    `INSERT INTO users (id, username, password, role, full_name, is_active)
     VALUES ($1, $2, $3, 'admin', 'VF-Regulator Demo User', true)`,
    [demoUserId, demoUsername, demoHash],
  );
  await owner.query(
    `INSERT INTO user_tenant_roles (user_id, tenant_id, role, is_active, is_primary)
     VALUES ($1, $2, 'admin', true, true)`,
    [demoUserId, DEMO_TENANT],
  );

  const jar = new Jar();
  try {
    // 1) login (CSRF-exempt)
    const loginRes = await fetch(`${SERVER}/api/auth/login`, {
      method: "POST",
      headers: { "content-type": "application/json", "x-forwarded-for": HARNESS_XFF },
      body: JSON.stringify({ username: demoUsername, password: demoPassword }),
    });
    jar.ingest(loginRes);
    const loginJson: any = await loginRes.json().catch(() => ({}));
    if (loginRes.status !== 200 || loginJson?.success !== true) {
      const fail = (id: string, scn: string) =>
        rec({
          id,
          scenario: scn,
          expectation: "row(s) persist durably through the real route",
          observed: `login HTTP ${loginRes.status} ${JSON.stringify(loginJson).slice(0, 160)}`,
          verdict: "FAIL (could not authenticate seeded user)",
          pass: false,
        });
      fail("L1a", "real :5000 — POST /api/regulator/session READ_ONLY");
      fail("L1b", "real :5000 — POST /api/regulator/session SANDBOX");
      return demoUserId;
    }

    // 2) mint CSRF on the authenticated session
    const csrfRes = await fetch(`${SERVER}/api/csrf-token`, {
      headers: { cookie: jar.header(), "x-forwarded-for": HARNESS_XFF },
    });
    jar.ingest(csrfRes);
    const csrfJson: any = await csrfRes.json().catch(() => ({}));
    const csrfToken: string = csrfJson?.csrfToken;

    const post = (body: unknown) =>
      fetch(`${SERVER}/api/regulator/session`, {
        method: "POST",
        headers: {
          "content-type": "application/json",
          cookie: jar.header(),
          "x-csrf-token": csrfToken,
          "x-forwarded-for": HARNESS_XFF,
        },
        body: JSON.stringify(body),
      });

    // ── L1a — READ_ONLY ──
    {
      const res = await post({
        regulatorId: RID("L1A"),
        regulatorName: RNAME("L1A"),
        organization: "BOU",
        accessLevel: "READ_ONLY",
      });
      const json: any = await res.json().catch(() => ({}));
      await settle();
      const srows = await sessionRowsByRegId(RID("L1A"));
      const sbrows = await sandboxRowsByCreatedFor(RNAME("L1A"));
      const pass =
        res.status === 200 &&
        srows.length === 1 &&
        srows[0].sandbox_environment_id === null &&
        sbrows.length === 0;
      rec({
        id: "L1a",
        scenario: "real :5000 — POST /api/regulator/session READ_ONLY",
        expectation:
          "HTTP 200; exactly 1 regulator_sessions row; sandbox_environment_id NULL; 0 sandbox rows",
        observed: `HTTP ${res.status} (id=${json?.id ?? "?"}); sessionRows=${srows.length}; sandbox_environment_id=${srows[0]?.sandbox_environment_id ?? "n/a"}; sandboxRows=${sbrows.length}`,
        verdict: pass
          ? "PASS (READ_ONLY session persists durably; no sandbox; read back independently)"
          : "FAIL",
        pass,
      });
    }

    // ── L1b — SANDBOX ──
    {
      const res = await post({
        regulatorId: RID("L1B"),
        regulatorName: RNAME("L1B"),
        organization: "BOU",
        accessLevel: "SANDBOX",
      });
      const json: any = await res.json().catch(() => ({}));
      await settle();
      const srows = await sessionRowsByRegId(RID("L1B"));
      const sbrows = await sandboxRowsByCreatedFor(RNAME("L1B"));
      const refOk =
        srows.length === 1 &&
        sbrows.length === 1 &&
        srows[0].sandbox_environment_id === sbrows[0].id;
      const pass = res.status === 200 && refOk;
      rec({
        id: "L1b",
        scenario: "real :5000 — POST /api/regulator/session SANDBOX",
        expectation:
          "HTTP 200; 1 session row + 1 sandbox row; session.sandbox_environment_id === sandbox.id (referential integrity)",
        observed: `HTTP ${res.status} (id=${json?.id ?? "?"}); sessionRows=${srows.length}; sandboxRows=${sbrows.length}; ref ${srows[0]?.sandbox_environment_id ?? "?"} ${refOk ? "===" : "!=="} ${sbrows[0]?.id ?? "?"}`,
        verdict: pass
          ? "PASS (sandbox + session persist atomically; reference intact; read back independently)"
          : "FAIL",
        pass,
      });
    }
  } catch (e: any) {
    rec({
      id: "L1",
      scenario: "real :5000 — POST /api/regulator/session",
      expectation: "rows persist durably with correct references",
      observed: `EXCEPTION ${e?.message ?? String(e)} (is the dev server running on :5000?)`,
      verdict: "FAIL (harness/connectivity error)",
      pass: false,
    });
  }
  return demoUserId;
}

// ─────────────────────────────────────────────────────────────────────────────
// NEGATIVE + CONTROL legs — in-script Express wired to the REAL tenantMiddleware
// ─────────────────────────────────────────────────────────────────────────────
async function negativeAndControlLegs() {
  const app = express();
  app.use(express.json());
  // inject a session BEFORE tenantMiddleware so the tenant resolves exactly as a
  // real authenticated request (the x-tenant-id header source was removed in
  // T002.5 Operation 1). The tables under test are non-RLS, so the resolved
  // tenant does not gate the inserts — but we keep the flow faithful.
  app.use((req, _res, next) => {
    (req as any).session = { tenantId: DEMO_TENANT, userId: "vf-reg-harness" };
    next();
  });
  app.use("/api", tenantMiddleware); // faithful mount (baseUrl="/api")
  app.post("/api/regulator/session-faulttest", async (req, res) => {
    try {
      // mirror the real route (routes.ts POST /api/regulator/session): the route
      // supplies ipAddress from req.ip — req.body does not carry it.
      const { regulatorId, regulatorName, organization, accessLevel, durationHours } =
        req.body;
      const session = await createRegulatorSession({
        regulatorId,
        regulatorName,
        organization,
        accessLevel: accessLevel || "READ_ONLY",
        ipAddress: req.ip || "unknown",
        durationHours,
      });
      res.json({ ok: true, id: session.id });
    } catch (e: any) {
      // MUST return >=500 so tenantMiddleware's onFinish ROLLBACKs the request tx.
      res.status(500).json({ error: e?.message ?? String(e), name: e?.name ?? "Error" });
    }
  });

  const server = app.listen(0);
  await once(server, "listening");
  const port = (server.address() as { port: number }).port;
  const L = `http://127.0.0.1:${port}`;

  const callHarness = (body: unknown) =>
    fetch(`${L}/api/regulator/session-faulttest`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(body),
    });

  const cacheHasSession = (rid: string) =>
    getRegulatorSessions().some((s) => s.regulatorId === rid);
  const cacheHasSandbox = (name: string) =>
    getSandboxes().some((s) => s.createdFor === name);

  try {
    // ── L2a — READ_ONLY, fault armed at the SESSION write ──
    {
      process.env.FAULT_INJECTION_ENABLED = "true";
      process.env.FAULT_INJECTION_TARGET = "regulator.saveRegulatorSession";
      const sessBefore = getRegulatoryState().activeRegulatorSessions;
      const res = await callHarness({
        regulatorId: RID("L2A"),
        regulatorName: RNAME("L2A"),
        organization: "BOU",
        accessLevel: "READ_ONLY",
      });
      const body: any = await res.json().catch(() => ({}));
      await settle();
      const srows = await sessionRowsByRegId(RID("L2A"));
      const sessAfter = getRegulatoryState().activeRegulatorSessions;
      const pass =
        res.status === 500 &&
        body?.name === "FaultInjectionError" &&
        srows.length === 0 &&
        !cacheHasSession(RID("L2A")) &&
        sessAfter - sessBefore === 0;
      rec({
        id: "L2a",
        scenario: "in-script — READ_ONLY, fault armed at regulator.saveRegulatorSession",
        expectation:
          "HTTP 500 (FaultInjectionError); 0 session rows; session NOT cached; activeRegulatorSessions Δ=0",
        observed: `HTTP ${res.status} (${body?.name ?? "?"}); rows=${srows.length}; cached=${cacheHasSession(RID("L2A"))}; counter Δ=${sessAfter - sessBefore}`,
        verdict: pass
          ? "PASS (write failure surfaces as 500, no row, no cache half-state — swallow eliminated)"
          : "FAIL",
        pass,
      });
    }

    // ── L2b — SANDBOX, fault armed at the SESSION write (atomic-nested proof) ──
    {
      process.env.FAULT_INJECTION_ENABLED = "true";
      process.env.FAULT_INJECTION_TARGET = "regulator.saveRegulatorSession";
      const sessBefore = getRegulatoryState().activeRegulatorSessions;
      const sbBefore = getRegulatoryState().sandboxEnvironments;
      const res = await callHarness({
        regulatorId: RID("L2B"),
        regulatorName: RNAME("L2B"),
        organization: "BOU",
        accessLevel: "SANDBOX",
      });
      const body: any = await res.json().catch(() => ({}));
      await settle();
      const srows = await sessionRowsByRegId(RID("L2B"));
      const sbrows = await sandboxRowsByCreatedFor(RNAME("L2B"));
      const sessAfter = getRegulatoryState().activeRegulatorSessions;
      const sbAfter = getRegulatoryState().sandboxEnvironments;
      const pass =
        res.status === 500 &&
        body?.name === "FaultInjectionError" &&
        srows.length === 0 &&
        sbrows.length === 0 &&
        !cacheHasSession(RID("L2B")) &&
        !cacheHasSandbox(RNAME("L2B")) &&
        sessAfter - sessBefore === 0 &&
        sbAfter - sbBefore === 0;
      rec({
        id: "L2b",
        scenario:
          "in-script — SANDBOX, fault armed at SESSION write (sandbox write succeeds first, in tx)",
        expectation:
          "HTTP 500; BOTH rows absent (sandbox rolled back with the request tx); neither cached; both counters Δ=0",
        observed: `HTTP ${res.status} (${body?.name ?? "?"}); sessionRows=${srows.length}; sandboxRows=${sbrows.length}; cachedSess=${cacheHasSession(RID("L2B"))}; cachedSbx=${cacheHasSandbox(RNAME("L2B"))}; counters Δ sess=${sessAfter - sessBefore} sbx=${sbAfter - sbBefore}`,
        verdict: pass
          ? "PASS (partial write atomically rolled back — no orphan sandbox, no half-state; the headline fix)"
          : "FAIL",
        pass,
      });
    }

    // ── L3a — CONTROL: both flags unset → hook inert-by-default ──
    {
      delete process.env.FAULT_INJECTION_ENABLED;
      delete process.env.FAULT_INJECTION_TARGET;
      const res = await callHarness({
        regulatorId: RID("L3A"),
        regulatorName: RNAME("L3A"),
        organization: "BOU",
        accessLevel: "SANDBOX",
      });
      const body: any = await res.json().catch(() => ({}));
      await settle();
      const srows = await sessionRowsByRegId(RID("L3A"));
      const sbrows = await sandboxRowsByCreatedFor(RNAME("L3A"));
      const pass = res.status === 200 && srows.length === 1 && sbrows.length === 1;
      rec({
        id: "L3a",
        scenario: "in-script — SANDBOX, BOTH fault flags unset (inert-by-default control)",
        expectation: "HTTP 200; 1 session row + 1 sandbox row (fault primitive is a no-op when disarmed)",
        observed: `HTTP ${res.status} (ok=${body?.ok}); sessionRows=${srows.length}; sandboxRows=${sbrows.length}`,
        verdict: pass ? "PASS (disarmed hook does not perturb the success path)" : "FAIL",
        pass,
      });
    }

    // ── L3b — CONTROL: master armed, TARGET non-matching → tag-scoped no-op ──
    //   This is also L2b's positive control: identical SANDBOX path, the sandbox
    //   write DOES land — so L2b's absent sandbox row is a genuine rollback, not a
    //   write that never happened.
    {
      process.env.FAULT_INJECTION_ENABLED = "true";
      process.env.FAULT_INJECTION_TARGET = "regulator.__nonmatching_tag__";
      const res = await callHarness({
        regulatorId: RID("L3B"),
        regulatorName: RNAME("L3B"),
        organization: "BOU",
        accessLevel: "SANDBOX",
      });
      const body: any = await res.json().catch(() => ({}));
      await settle();
      const srows = await sessionRowsByRegId(RID("L3B"));
      const sbrows = await sandboxRowsByCreatedFor(RNAME("L3B"));
      const pass = res.status === 200 && srows.length === 1 && sbrows.length === 1;
      rec({
        id: "L3b",
        scenario:
          "in-script — SANDBOX, master ARMED but TARGET non-matching (tag-scoping control)",
        expectation:
          "HTTP 200; 1 session row + 1 sandbox row (armed master flag cannot fire on an untargeted tag)",
        observed: `HTTP ${res.status} (ok=${body?.ok}); sessionRows=${srows.length}; sandboxRows=${sbrows.length}`,
        verdict: pass
          ? "PASS (tag-scoped: armed-but-untargeted is inert; also confirms the SANDBOX write path lands)"
          : "FAIL",
        pass,
      });
    }
  } finally {
    delete process.env.FAULT_INJECTION_ENABLED;
    delete process.env.FAULT_INJECTION_TARGET;
    server.close();
    await once(server, "close").catch(() => {});
  }
}

async function main() {
  console.log("=".repeat(80));
  console.log("VF module 1 — REGULATOR-GATEWAY Group A FIX — LIVE DEMONSTRATION (6-leg)");
  console.log(
    "ref AS/AEGIS-CYBER/VERIFY/2026/001 | env: DEV |",
    new Date().toISOString(),
  );
  console.log("=".repeat(80));

  let demoUserId: string | null = null;
  try {
    demoUserId = await positiveLegs();
    await negativeAndControlLegs();
  } finally {
    // ── cleanup (owner bypasses RLS) ──
    try {
      const ds = await owner.query(
        "DELETE FROM regulator_sessions WHERE regulator_id = ANY($1::text[])",
        [ALL_RIDS],
      );
      const db2 = await owner.query(
        "DELETE FROM sandbox_environments WHERE created_for = ANY($1::text[])",
        [ALL_RNAMES],
      );
      console.log(
        `\ncleanup: deleted ${ds.rowCount} regulator_sessions + ${db2.rowCount} sandbox_environments sentinel row(s).`,
      );
    } catch (e: any) {
      console.log(`cleanup (regulator rows) error: ${e?.message ?? e}`);
    }
    if (demoUserId) {
      for (const [label, q, p] of [
        ["user_tenant_roles", "DELETE FROM user_tenant_roles WHERE user_id = $1", [demoUserId]],
        ["sessions", "DELETE FROM sessions WHERE user_id = $1", [demoUserId]],
        ["users", "DELETE FROM users WHERE id = $1", [demoUserId]],
      ] as [string, string, any[]][]) {
        try {
          await owner.query(q, p);
        } catch (e: any) {
          console.log(`cleanup ${label} (non-fatal): ${e?.message ?? e}`);
        }
      }
    }
    await owner.end().catch(() => {});
  }

  // ── report ──
  console.log("");
  for (const r of results) {
    console.log("-".repeat(80));
    console.log(`[${r.id}] ${r.scenario}`);
    console.log(`     expect  : ${r.expectation}`);
    console.log(`     observe : ${r.observed}`);
    console.log(`     VERDICT : ${r.verdict}`);
  }
  console.log("-".repeat(80));
  const passed = results.filter((r) => r.pass).length;
  const total = results.length;
  const allPass = results.length > 0 && results.every((r) => r.pass);
  console.log(
    `ACCEPTANCE: ${passed}/${total} checks PASS — ${allPass ? "ALL PASS" : "SEE FAILURES ABOVE"}`,
  );
  process.exit(allPass ? 0 : 1);
}

main().catch((e) => {
  console.error("FATAL", e);
  process.exit(1);
});
