/**
 * Platform Verification Plan — ref AS/AEGIS-CYBER/VERIFY/2026/001
 * VF-S5: KYT:197 fire-and-forget durable-write loss — LIVE DEMONSTRATION
 *
 * Purpose (advisor instruction, ground-truth-only): do not classify KYT:197
 * on a code-read alone. Exercise the REAL call path, induce the durable-write
 * failure, and confirm empirically whether the record genuinely never persists
 * (is lost) — or whether something recovers it that the code-read missed. The
 * latter would be the MOST valuable outcome (a reasoning-chain gap caught before
 * designing 20 observe-on-failure fixes). Report STRAIGHT either way.
 *
 * This script does NOT edit kyt-engine.ts (KYT is in the "nothing changes" list).
 * It only EXERCISES the real exported functions and OBSERVES the kyt_results
 * table through an out-of-band owner connection.
 *
 * Three-link chain under test:
 *   L1  analyzeTransaction(ctx) returns a successful result to the caller
 *       regardless of the persist outcome (persist is `.catch(() => {})`).
 *   L2  the durable write (persistKytResult -> kyt_results INSERT) genuinely
 *       fails.
 *   L3  nothing recovers it: kytStats holds no per-record copy; no retry; the
 *       row is absent afterward.
 *
 * Structural sub-question this surfaces: analyzeTransaction calls
 * persistKytResult WITHOUT threading a tenantId (kyt-engine.ts L189-197), so the
 * inserted row's tenant_id is NULL. Under T003 RLS WITH CHECK
 * (tenant_id = current_tenant_id()), NULL fails the check whether or not a GUC
 * is set — so the failure may be CONTINUOUS (every call), not incidental.
 *
 * Connections:
 *   - owner  = pg.Pool on DATABASE_URL (dev DB owner; out-of-band observer for
 *              count / positive-control insert / cleanup).
 *   - real path = the imported functions, which use the module `db` proxy
 *              (appPool = aegis_app restricted role; RLS-enforced). A request
 *              context is simulated faithfully with `withDemoTenantContext`,
 *              which pins an appPool client, sets the tenant GUC LOCAL, and
 *              activates the same AsyncLocalStorage the real middleware uses.
 */

import pg from "pg";
import {
  appPool,
  tenantContext,
  makeClientRunner,
} from "../server/db";
import {
  analyzeTransaction,
  type TransactionContext,
} from "../server/lib/kyt-engine";
import * as KytDB from "../server/lib/kyt-engine-db";

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

const DEMO_TENANT = "vf-s5-tenant";
const base = Date.now() * 100;
const uid = (n: number) => base + n; // numeric sentinel userId
const ustr = (n: number) => String(base + n); // its stored string form

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

async function countByUser(userIdStr: string): Promise<number> {
  const r = await owner.query<{ n: string }>(
    "SELECT count(*)::int AS n FROM kyt_results WHERE user_id = $1",
    [userIdStr],
  );
  return Number(r.rows[0].n);
}

/** Faithfully reproduce the request runtime: pinned appPool client + LOCAL
 *  tenant GUC + tenantContext ALS active, exactly as tenantMiddleware does. */
async function withDemoTenantContext<T>(
  tenantId: string,
  fn: () => Promise<T>,
): Promise<T> {
  const client = await appPool.connect();
  try {
    await client.query("BEGIN");
    await client.query(
      "SELECT set_config('app.current_tenant_id', $1, true)",
      [tenantId],
    );
    const runner = makeClientRunner(client);
    const out = await tenantContext.run({ runner, tenantId }, fn);
    await client.query("COMMIT");
    return out;
  } catch (e) {
    await client.query("ROLLBACK");
    throw e;
  } finally {
    client.release();
  }
}

function validCtx(n: number, amount = 250_000): TransactionContext {
  return {
    userId: uid(n),
    amount,
    currency: "UGX",
    recipientId: "vf-s5-recipient",
    recipientType: "KNOWN",
    channel: "WEB",
    timeOfDay: 12,
    isRecurring: false,
  };
}

interface Row {
  id: string;
  scenario: string;
  expectation: string;
  returned: string;
  threw: string;
  err: string;
  rowsAfter: number | string;
  verdict: string;
}

const rows: Row[] = [];

function record(r: Row) {
  rows.push(r);
}

async function main() {
  console.log("=".repeat(78));
  console.log("VF-S5 — KYT:197 fire-and-forget durable-write loss — LIVE DEMO");
  console.log("ref AS/AEGIS-CYBER/VERIFY/2026/001 | env: DEV |", new Date().toISOString());
  console.log("=".repeat(78));

  // ── S0 — harness detection positive control (owner insert, bypasses RLS) ──
  {
    const u = ustr(0);
    let err = "";
    try {
      await owner.query(
        `INSERT INTO kyt_results
           (id, user_id, amount, currency, recipient_type, channel,
            risk_level, risk_score, recommendation, tenant_id)
         VALUES (gen_random_uuid()::text, $1, '100.00', 'UGX', 'KNOWN', 'WEB',
                 'LOW', '0.100', 'APPROVE', $2)`,
        [u, DEMO_TENANT],
      );
    } catch (e: any) {
      err = `${e?.code ?? ""} ${e?.message ?? String(e)}`.trim();
    }
    const n = await countByUser(u);
    record({
      id: "S0",
      scenario: "owner direct INSERT (out-of-band observer)",
      expectation: "row lands; count=1 (proves the count harness works)",
      returned: "n/a",
      threw: err ? "YES" : "no",
      err,
      rowsAfter: n,
      verdict: n === 1 && !err ? "PASS (harness detects a real row)" : "ANOMALY",
    });
  }

  // ── S1 — bare persist, GUC set + matching tenantId threaded (POSITIVE) ──
  {
    const u = ustr(1);
    let threw = "no";
    let err = "";
    try {
      await withDemoTenantContext(DEMO_TENANT, async () => {
        await KytDB.persistKytResult({
          userId: uid(1),
          amount: 250_000,
          currency: "UGX",
          recipientType: "KNOWN",
          channel: "WEB",
          riskLevel: "LOW",
          riskScore: 0.1,
          riskFactors: [],
          recommendation: "APPROVE",
          requiredVerification: [],
          tenantId: DEMO_TENANT,
        });
      });
    } catch (e: any) {
      threw = "YES";
      err = `${e?.code ?? ""} ${e?.message ?? String(e)}`.trim();
    }
    const n = await countByUser(u);
    record({
      id: "S1",
      scenario: "persistKytResult — GUC set + tenantId threaded to match",
      expectation: "no throw; count=1 (persist works WHEN tenantId is threaded)",
      returned: "n/a",
      threw,
      err,
      rowsAfter: n,
      verdict:
        threw === "no" && n === 1
          ? "PASS (real persist path CAN write — not a no-op)"
          : "ANOMALY (positive control did not land)",
    });
  }

  // ── S2 — bare persist, GUC set but NO tenantId (real analyzeTransaction shape) ──
  {
    const u = ustr(2);
    let threw = "no";
    let err = "";
    try {
      await withDemoTenantContext(DEMO_TENANT, async () => {
        await KytDB.persistKytResult({
          userId: uid(2),
          amount: 250_000,
          currency: "UGX",
          recipientType: "KNOWN",
          channel: "WEB",
          riskLevel: "LOW",
          riskScore: 0.1,
          riskFactors: [],
          recommendation: "APPROVE",
          requiredVerification: [],
          // NO tenantId — exactly how analyzeTransaction calls it (L189-197)
        });
      });
    } catch (e: any) {
      threw = "YES";
      err = `${e?.code ?? ""} ${e?.message ?? String(e)}`.trim();
    }
    const n = await countByUser(u);
    record({
      id: "S2",
      scenario: "persistKytResult — GUC set, NO tenantId (in-request shape)",
      expectation: "RLS rejects (NULL tenant_id != GUC); throw; count=0",
      returned: "n/a",
      threw,
      err,
      rowsAfter: n,
      verdict:
        threw === "YES" && n === 0
          ? "L2 CONFIRMED (durable write FAILS in real in-request shape)"
          : n === 1
            ? "SURPRISE: NULL tenant_id row PERSISTED (policy permits NULL)"
            : "ANOMALY",
    });
  }

  // ── S3 — bare persist, NO context + NO tenantId (background/no-request shape) ──
  {
    const u = ustr(3);
    let threw = "no";
    let err = "";
    try {
      await KytDB.persistKytResult({
        userId: uid(3),
        amount: 250_000,
        currency: "UGX",
        recipientType: "KNOWN",
        channel: "WEB",
        riskLevel: "LOW",
        riskScore: 0.1,
        riskFactors: [],
        recommendation: "APPROVE",
        requiredVerification: [],
      });
    } catch (e: any) {
      threw = "YES";
      err = `${e?.code ?? ""} ${e?.message ?? String(e)}`.trim();
    }
    const n = await countByUser(u);
    record({
      id: "S3",
      scenario: "persistKytResult — NO context, NO tenantId (no-request)",
      expectation: "RLS rejects (GUC null, NULL tenant_id); throw; count=0",
      returned: "n/a",
      threw,
      err,
      rowsAfter: n,
      verdict:
        threw === "YES" && n === 0
          ? "L2 CONFIRMED (durable write FAILS in no-request shape)"
          : n === 1
            ? "SURPRISE: row PERSISTED with no context"
            : "ANOMALY",
    });
  }

  // ── S4 — analyzeTransaction, NO context (full real swallow path, no-request) ──
  {
    const u = ustr(4);
    const before = await countByUser(u);
    let returned = "null";
    let threw = "no";
    let err = "";
    try {
      const result = analyzeTransaction(validCtx(4));
      returned = result?.transactionId ? `result(${result.riskLevel})` : "null";
    } catch (e: any) {
      threw = "YES";
      err = `${e?.code ?? ""} ${e?.message ?? String(e)}`.trim();
    }
    await settle(); // let the fire-and-forget persist attempt settle
    const n = await countByUser(u);
    record({
      id: "S4",
      scenario: "analyzeTransaction — NO context (full swallow path)",
      expectation:
        "L1 result returned; no throw out (swallowed); count stays 0 (L3 lost)",
      returned: `${returned} (before=${before})`,
      threw,
      err,
      rowsAfter: n,
      verdict:
        returned.startsWith("result") && threw === "no" && n === 0
          ? "HOLE CONFIRMED (success returned, record LOST, no recovery)"
          : "ANOMALY",
    });
  }

  // ── S5 — analyzeTransaction, WITH context (full real swallow path, in-request) ──
  {
    const u = ustr(5);
    const before = await countByUser(u);
    let returned = "null";
    let threw = "no";
    let err = "";
    try {
      await withDemoTenantContext(DEMO_TENANT, async () => {
        const result = analyzeTransaction(validCtx(5));
        returned = result?.transactionId
          ? `result(${result.riskLevel})`
          : "null";
        await settle(800); // settle the fire-and-forget while the pinned tx is open
      });
    } catch (e: any) {
      threw = "YES";
      err = `${e?.code ?? ""} ${e?.message ?? String(e)}`.trim();
    }
    await settle();
    const n = await countByUser(u);
    record({
      id: "S5",
      scenario: "analyzeTransaction — WITH request context (in-request runtime)",
      expectation:
        "L1 result returned; swallowed; count 0 — hole active even in a correct request",
      returned: `${returned} (before=${before})`,
      threw,
      err,
      rowsAfter: n,
      verdict:
        returned.startsWith("result") && threw === "no" && n === 0
          ? "HOLE ACTIVE IN-REQUEST (analyzeTransaction omits tenantId -> RLS reject -> lost)"
          : n === 1
            ? "in-request persist SUCCEEDED (hole is no-request-only)"
            : "ANOMALY",
    });
  }

  // ── S6 — bare persist, GUC + matching tenantId + numeric overflow (incidental) ──
  {
    const u = ustr(6);
    let threw = "no";
    let err = "";
    try {
      await withDemoTenantContext(DEMO_TENANT, async () => {
        await KytDB.persistKytResult({
          userId: uid(6),
          amount: 1e20, // String(1e20) = 21 digits > numeric(20,2) -> overflow
          currency: "UGX",
          recipientType: "KNOWN",
          channel: "WEB",
          riskLevel: "LOW",
          riskScore: 0.1,
          riskFactors: [],
          recommendation: "APPROVE",
          requiredVerification: [],
          tenantId: DEMO_TENANT,
        });
      });
    } catch (e: any) {
      threw = "YES";
      err = `${e?.code ?? ""} ${e?.message ?? String(e)}`.trim();
    }
    const n = await countByUser(u);
    record({
      id: "S6",
      scenario: "persistKytResult — tenant OK but numeric overflow (incidental DB error)",
      expectation: "throw (numeric overflow); count=0 — a 2nd failure cause the catch also swallows",
      returned: "n/a",
      threw,
      err,
      rowsAfter: n,
      verdict:
        threw === "YES" && n === 0
          ? "CONFIRMED (incidental DB error is also a real throw the empty catch eats)"
          : "ANOMALY",
    });
  }

  // ── Report ──
  console.log("");
  for (const r of rows) {
    console.log("-".repeat(78));
    console.log(`[${r.id}] ${r.scenario}`);
    console.log(`     expect : ${r.expectation}`);
    console.log(`     returned: ${r.returned}`);
    console.log(`     threw   : ${r.threw}${r.err ? "  -> " + r.err : ""}`);
    console.log(`     rowsAfter: ${r.rowsAfter}`);
    console.log(`     VERDICT : ${r.verdict}`);
  }
  console.log("-".repeat(78));

  // ── Cleanup all sentinels (owner bypasses RLS) ──
  const delIds = [0, 1, 2, 3, 4, 5, 6].map((n) => ustr(n));
  const del = await owner.query(
    "DELETE FROM kyt_results WHERE user_id = ANY($1::text[])",
    [delIds],
  );
  console.log(`cleanup: deleted ${del.rowCount} sentinel row(s).`);
  console.log("=".repeat(78));

  await owner.end();
  await appPool.end();
}

main().catch(async (e) => {
  console.error("FATAL:", e);
  try {
    await owner.end();
  } catch {}
  try {
    await appPool.end();
  } catch {}
  process.exit(1);
});
