/**
 * SCIM module-wide RLS-blindness fix — verification harness (throwaway).
 *
 * Proves the fix in server/lib/scim.ts that re-points the SCIM request path off
 * the plain module `db` (which, under the aegis_app RLS role with no tenant GUC,
 * sees 0 rows on scim_configs / scim_provisions) onto:
 *   - withBypassRls for the pre-tenant bearer-token credential scan (Cat 1), and
 *   - withTenantRls for every post-auth read/write on the RLS-enrolled SCIM tables
 *     (Cat 2: GET list, GET by-id, POST insert + config-count, PUT, PATCH, DELETE).
 *
 * Methodology
 *   suPool (DATABASE_URL superuser) seeds/cleans fixtures (bypasses RLS).
 *   appPool (aegis_app, the request role) proves the RLS substrate directly.
 *   fetch() against the running dev server exercises the REAL route handlers.
 *
 *   RED   — substrate proof the path WAS dead: as aegis_app with NO GUC, the
 *           config scan and the provision read both return 0 rows (fix-independent;
 *           this is the exact mechanism that made validateBearerToken return null
 *           -> every SCIM endpoint 401).
 *   GREEN — through the real handlers post-fix: GET list/by-id return 200 and are
 *           tenant-isolated; POST (201) and DELETE/deprovision (204) write paths
 *           succeed under the tenant GUC and are cross-tenant denied (404).
 *   NON-HOLLOW — as aegis_app WITH GUC=A the seeded A row IS visible (a hollow
 *           wrap that still used module db would 0-row even here); paired with the
 *           GUC=B negative (Rule 18 interlock).
 *
 * NOTE on CSRF: the global csrfProtection middleware is mounted before the SCIM
 * routes and /scim is not allowlisted, so state-changing SCIM methods are blocked
 * with 403 BEFORE reaching the handler (a SEPARATE pre-existing defect from the
 * RLS bug). To isolate the layer under test (RLS), the write probes satisfy CSRF
 * properly (mint a token + carry the session cookie); the RLS fix is what makes
 * the handler's DB work once the request reaches it.
 *
 * Never logs bearer tokens or decrypted PII values.
 */
import pg from "pg";
import { randomBytes } from "crypto";
import { encryptionService } from "../server/lib/encryption-service";
import { encryptPii } from "../server/lib/pii-encryption";

const { Pool } = pg;
const BASE = "http://localhost:5000";

function appConnStr(): string {
  const u = new URL(process.env.DATABASE_URL!);
  u.username = "aegis_app";
  u.password = process.env.AEGIS_APP_DB_PASSWORD!;
  return u.toString();
}

const suPool = new Pool({ connectionString: process.env.DATABASE_URL });
const appPool = new Pool({ connectionString: appConnStr() });

let pass = 0, fail = 0;
const ok = (cond: boolean, name: string, detail = "") => {
  if (cond) { pass++; console.log(`PASS  ${name}`); }
  else { fail++; console.log(`FAIL  ${name}${detail ? `  ::  ${detail}` : ""}`); }
};

const RUN = Date.now();
const tA = `scim-fix-A-${RUN}`;
const tB = `scim-fix-B-${RUN}`;
const tokenA = randomBytes(40).toString("hex"); // never logged
const tokenB = randomBytes(40).toString("hex"); // never logged
const ctxA = { type: "tenant" as const, tenantId: tA };
const ctxB = { type: "tenant" as const, tenantId: tB };

const uAlice = `alice@scimfix-a-${RUN}.test`;
const uBob   = `bob@scimfix-a-${RUN}.test`;
const uCarol = `carol@scimfix-b-${RUN}.test`;
const uDave  = `dave@scimfix-a-${RUN}.test`; // POST-created

let idAlice = "", idBob = "", idCarol = "", idDave = "";

async function seedProvision(tenant: string, ctx: typeof ctxA, ext: string, userName: string): Promise<string> {
  const r = await suPool.query(
    `INSERT INTO scim_provisions (tenant_id, external_id, user_name, email, groups, active, sync_status)
     VALUES ($1,$2,$3,$4,'[]',true,'synced') RETURNING id`,
    [tenant, ext, encryptPii(userName, ctx), encryptPii(userName, ctx)],
  );
  return r.rows[0].id as string;
}

async function getCsrf(): Promise<{ cookie: string; token: string }> {
  const r = await fetch(`${BASE}/api/csrf-token`);
  const sc = r.headers.get("set-cookie") ?? "";
  const m = sc.match(/connect\.sid=[^;]+/);
  const body: any = await r.json();
  return { cookie: m ? m[0] : "", token: body.csrfToken };
}

async function main() {
  // ── Seed fixtures (superuser, bypasses RLS) ────────────────────────────────
  await suPool.query(
    `INSERT INTO scim_configs (tenant_id, provider_name, encrypted_bearer_token, is_active)
     VALUES ($1,'okta',$2,true)`,
    [tA, encryptionService.encrypt(tokenA)],
  );
  await suPool.query(
    `INSERT INTO scim_configs (tenant_id, provider_name, encrypted_bearer_token, is_active)
     VALUES ($1,'azure_ad',$2,true)`,
    [tB, encryptionService.encrypt(tokenB)],
  );
  idAlice = await seedProvision(tA, ctxA, "ext-alice", uAlice);
  idBob   = await seedProvision(tA, ctxA, "ext-bob",   uBob);
  idCarol = await seedProvision(tB, ctxB, "ext-carol", uCarol);
  console.log(`\n[seed] tenants A/B + 3 provisions seeded (run ${RUN})\n`);

  // ── RED (substrate, fix-independent): the path WAS dead under aegis_app ─────
  {
    const c = await appPool.connect();
    try {
      await c.query("BEGIN");
      const r = await c.query("SELECT count(*)::int AS n FROM scim_configs WHERE is_active = true");
      await c.query("ROLLBACK");
      ok(r.rows[0].n === 0,
        "RED R1 substrate: aegis_app NO-GUC scim_configs is_active scan = 0 rows (validateBearerToken dead path)",
        `got ${r.rows[0].n}`);
    } finally { c.release(); }
  }
  {
    const c = await appPool.connect();
    try {
      await c.query("BEGIN");
      const r = await c.query("SELECT count(*)::int AS n FROM scim_provisions WHERE tenant_id = $1", [tA]);
      await c.query("ROLLBACK");
      ok(r.rows[0].n === 0,
        "RED R2 substrate: aegis_app NO-GUC scim_provisions read for tenant A = 0 rows (GET-list dead path)",
        `got ${r.rows[0].n}`);
    } finally { c.release(); }
  }

  // ── NON-HOLLOW (substrate): with GUC=A the A row IS visible (Rule 18 pair) ──
  let posVisible = false;
  {
    const c = await appPool.connect();
    try {
      await c.query("BEGIN");
      await c.query("SELECT set_config('app.current_tenant_id', $1, true)", [tA]);
      const r = await c.query("SELECT id FROM scim_provisions WHERE id = $1", [idAlice]);
      await c.query("ROLLBACK");
      posVisible = r.rows.length === 1;
      ok(posVisible,
        "NON-HOLLOW pos: aegis_app GUC=A SEES A's provision (tenant context returns rows; a hollow module-db wrap would 0-row here)");
    } finally { c.release(); }
  }
  {
    const c = await appPool.connect();
    try {
      await c.query("BEGIN");
      await c.query("SELECT set_config('app.current_tenant_id', $1, true)", [tB]);
      const r = await c.query("SELECT id FROM scim_provisions WHERE id = $1", [idAlice]);
      await c.query("ROLLBACK");
      ok(posVisible && r.rows.length === 0,
        posVisible
          ? "NON-HOLLOW neg: aegis_app GUC=B canNOT see A's provision (substrate isolation; Rule 18 interlocked with the positive)"
          : "NON-HOLLOW neg: HOLLOW-GREEN — positive leg failed, denial is not trustworthy",
        `foreign rows=${r.rows.length}`);
    } finally { c.release(); }
  }

  // ── GREEN (route, post-fix): GET reads via the real handlers ───────────────
  {
    const res = await fetch(`${BASE}/scim/v2/Users`, { headers: { authorization: `Bearer ${tokenA}` } });
    const body: any = await res.json().catch(() => ({}));
    const names: string[] = (body.Resources ?? []).map((r: any) => r.userName);
    ok(res.status === 200 && names.includes(uAlice) && names.includes(uBob) && !names.includes(uCarol),
      "GREEN G1 route: GET /scim/v2/Users Bearer A = 200, sees A's users only (RED->GREEN)",
      `status=${res.status} count=${names.length}`);
  }
  {
    const res = await fetch(`${BASE}/scim/v2/Users`, { headers: { authorization: `Bearer ${tokenB}` } });
    const body: any = await res.json().catch(() => ({}));
    const names: string[] = (body.Resources ?? []).map((r: any) => r.userName);
    ok(res.status === 200 && names.includes(uCarol) && !names.includes(uAlice) && !names.includes(uBob),
      "GREEN G2 route: GET /scim/v2/Users Bearer B = 200, sees B's user only (paired isolation positive)",
      `status=${res.status} count=${names.length}`);
  }
  {
    const res = await fetch(`${BASE}/scim/v2/Users/${idAlice}`, { headers: { authorization: `Bearer ${tokenA}` } });
    ok(res.status === 200, "GREEN G3a route: GET /scim/v2/Users/:idAlice Bearer A = 200 (by-id positive)", `status=${res.status}`);
  }
  {
    const res = await fetch(`${BASE}/scim/v2/Users/${idCarol}`, { headers: { authorization: `Bearer ${tokenA}` } });
    ok(res.status === 404, "GREEN G3b route: GET /scim/v2/Users/:idCarol Bearer A = 404 (cross-tenant by-id denied)", `status=${res.status}`);
  }

  // ── GREEN (route, post-fix): write paths via the real handlers ─────────────
  // CSRF satisfied so the request reaches the handler; RLS is the layer proven.
  const csrf = await getCsrf();
  const wHeaders = (t: string) => ({
    authorization: `Bearer ${t}`,
    "content-type": "application/json",
    "x-csrf-token": csrf.token,
    cookie: csrf.cookie,
  });
  {
    const res = await fetch(`${BASE}/scim/v2/Users`, {
      method: "POST",
      headers: wHeaders(tokenA),
      body: JSON.stringify({
        schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"],
        userName: uDave, externalId: "ext-dave", active: true,
        emails: [{ value: uDave, primary: true }],
      }),
    });
    const body: any = await res.json().catch(() => ({}));
    idDave = body.id ?? "";
    ok(res.status === 201 && !!idDave,
      "GREEN G4 route: POST /scim/v2/Users Bearer A = 201 (insert WITH CHECK passes under tenant RLS)",
      `status=${res.status}`);
    if (idDave) {
      const row = await suPool.query("SELECT tenant_id, user_name FROM scim_provisions WHERE id = $1", [idDave]);
      const r = row.rows[0];
      ok(!!r && r.tenant_id === tA && typeof r.user_name === "string" && r.user_name.startsWith("ENC:"),
        "GREEN G4b substrate: POSTed row persisted tenant_id=A + PII encrypted (ENC:)",
        r ? `tenant=${r.tenant_id} pii=${String(r.user_name).slice(0, 4)}` : "no row");
    }
  }
  if (idDave) {
    const res = await fetch(`${BASE}/scim/v2/Users/${idDave}`, { method: "DELETE", headers: wHeaders(tokenA) });
    ok(res.status === 204, "GREEN G5 route: DELETE /scim/v2/Users/:idDave Bearer A = 204 (deprovision update path under tenant RLS)", `status=${res.status}`);
    const row = await suPool.query("SELECT active, sync_status FROM scim_provisions WHERE id = $1", [idDave]);
    const r = row.rows[0];
    ok(!!r && r.active === false && r.sync_status === "deprovisioned",
      "GREEN G5b substrate: dave row active=false sync_status=deprovisioned (write took effect)",
      r ? `active=${r.active} sync=${r.sync_status}` : "no row");
  }
  {
    const res = await fetch(`${BASE}/scim/v2/Users/${idCarol}`, { method: "DELETE", headers: wHeaders(tokenA) });
    ok(res.status === 404, "GREEN G6 route: DELETE /scim/v2/Users/:idCarol Bearer A = 404 (cross-tenant write denied)", `status=${res.status}`);
  }

  console.log(`\n──────────── RESULT: ${pass} pass / ${fail} fail ────────────\n`);
}

main()
  .catch((e) => { console.error("HARNESS ERROR:", e); fail++; })
  .finally(async () => {
    // Cleanup (superuser): seeded + POSTed provisions, configs, and the local
    // user syncToLocalUser created for dave.
    try {
      await suPool.query("DELETE FROM scim_provisions WHERE tenant_id IN ($1,$2)", [tA, tB]);
      await suPool.query("DELETE FROM scim_configs WHERE tenant_id IN ($1,$2)", [tA, tB]);
      await suPool.query("DELETE FROM users WHERE username = $1", [uDave]).catch(() => {});
    } catch (e) { console.error("cleanup warn:", (e as Error).message); }
    await suPool.end();
    await appPool.end();
    process.exit(fail === 0 ? 0 : 1);
  });
