/**
 * KYC v1 — FEATURE 1 — CREATE-ROUTE SMOKE (functional-close enabler)
 * Spec: docs/regulatory/AEGIS_CYBER_KYC_V1_BUILD_SPEC.md (AS/KYC/2026/001, Part C).
 *
 * Drives the NEW admin-only onboarding route POST /api/kyc/customers against the REAL running
 * dev server (:5000), then reads the durable row back out-of-band via the DB owner (BYPASSRLS).
 * Paired controls (Rule 18 — every denial paired with a positive in the same run):
 *
 *   LEG A — POSITIVE: POST a valid customer (no status ⇒ PENDING). Assert HTTP 201; owner
 *           read-back proves a durable row exists, status === PENDING, national_id is ciphertext
 *           (≠ plaintext), national_id_hmac === computeLookupHash, and a KYC_CUSTOMER_CREATE
 *           audit row is present (the route really ran end-to-end and durably persisted).
 *   LEG B — DENIAL: POST the same route with status "APPROVED" (fresh national_id, so the refusal
 *           is due to APPROVED, NOT a duplicate). Assert HTTP is NOT 2xx; owner read-back by that
 *           national_id's HMAC proves ZERO rows landed (assertNotApprovedWrite fired in storage —
 *           the route did not mint an APPROVED customer).
 *
 * Side-effect discipline: imports only the PII HMAC helper + pg; drives everything else over HTTP
 * exactly as a real admin client would. Does NOT import server/index.ts or server/routes.ts.
 */

import pg from "pg";
import bcrypt from "bcryptjs";
import { randomUUID } from "node:crypto";
import { computeLookupHash } from "../server/lib/pii-encryption";

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

const SERVER = "http://127.0.0.1:5000";
const TENANT_A = "aegis-sovereign";
const base = Date.now();
const HARNESS_XFF = "203.0.113.58"; // RFC 5737 TEST-NET-3 — own rate-limit bucket

const nidA = `NID-SMOKE-${base}-A`;
const nidB = `NID-SMOKE-${base}-B`;
const hmacA = computeLookupHash(nidA, TENANT_A, "kyc_customers", "national_id");
const hmacB = computeLookupHash(nidB, TENANT_A, "kyc_customers", "national_id");

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

interface LegResult {
  id: string;
  scenario: string;
  expectation: string;
  observed: string;
  verdict: string;
  pass: boolean;
}
const results: LegResult[] = [];
const rec = (r: LegResult) => results.push(r);

async function ownerRowByHmac(hmac: string) {
  const r = await owner.query<{
    id: string;
    national_id: string;
    national_id_hmac: string | null;
    status: string;
  }>(
    "SELECT id, national_id, national_id_hmac, status FROM kyc_customers WHERE national_id_hmac = $1",
    [hmac],
  );
  return r.rows;
}

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("; ");
  }
}

async function main() {
  console.log("=".repeat(80));
  console.log("KYC v1 — FEATURE 1 — CREATE-ROUTE SMOKE (POST /api/kyc/customers)");
  console.log("spec AS/KYC/2026/001 Part C | env: DEV |", new Date().toISOString());
  console.log("=".repeat(80));

  const demoUserId = randomUUID();
  const demoUsername = `kyc-f1-create-admin-${base}`;
  const demoPassword = `Kyc!F1c-${base}-Demo`;
  const demoHash = bcrypt.hashSync(demoPassword, 10);

  try {
    // seed admin + primary UTR on tenant A via owner (BYPASSRLS)
    await owner.query(
      `INSERT INTO users (id, username, password, role, full_name, is_active)
       VALUES ($1, $2, $3, 'admin', 'KYC-F1 Create Smoke Admin', 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, TENANT_A],
    );

    const jar = new Jar();
    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) {
      throw new Error(`login failed HTTP ${loginRes.status} ${JSON.stringify(loginJson).slice(0, 160)}`);
    }
    const csrfRes = await fetch(`${SERVER}/api/csrf-token`, {
      headers: { cookie: jar.header(), "x-forwarded-for": HARNESS_XFF },
    });
    jar.ingest(csrfRes);
    const csrfToken: string = ((await csrfRes.json().catch(() => ({}))) as any)?.csrfToken;

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

    // ── LEG A — positive create (no status ⇒ PENDING) ───────────────────────
    const resA = await post({
      nationalId: nidA,
      fullName: "KYC-F1 Create Subject A",
      dateOfBirth: "1990-01-01",
      phoneNumber: "+256700000000",
    });
    const jsonA: any = await resA.json().catch(() => ({}));
    await settle();
    const rowsA = await ownerRowByHmac(hmacA);
    const createdId: string | undefined = jsonA?.id ?? rowsA[0]?.id;
    const auditA = createdId
      ? Number(
          (
            await owner.query<{ n: string }>(
              "SELECT count(*)::int AS n FROM audit_logs WHERE action = 'KYC_CUSTOMER_CREATE' AND resource = $1",
              [`kyc_customers/${createdId}`],
            )
          ).rows[0].n,
        ) >= 1
      : false;
    const passA =
      resA.status === 201 &&
      rowsA.length === 1 &&
      rowsA[0].status === "PENDING" &&
      rowsA[0].national_id !== nidA &&
      rowsA[0].national_id_hmac === hmacA &&
      auditA;
    rec({
      id: "A",
      scenario: "POST /api/kyc/customers — valid onboarding (status omitted ⇒ PENDING)",
      expectation:
        "HTTP 201; exactly ONE durable row; status PENDING; national_id ciphertext (≠ plaintext); HMAC matches; KYC_CUSTOMER_CREATE audit row present",
      observed: `HTTP ${resA.status}; rows=${rowsA.length}; status=${rowsA[0]?.status}; ciphertext=${rowsA[0] ? rowsA[0].national_id !== nidA : "n/a"}; hmacMatch=${rowsA[0]?.national_id_hmac === hmacA}; audited=${auditA}`,
      verdict: passA ? "PASS (route persists a durable, encrypted, audited PENDING customer)" : "FAIL",
      pass: passA,
    });

    // ── LEG B — denial: status APPROVED is structurally refused on create ────
    const resB = await post({
      nationalId: nidB,
      fullName: "KYC-F1 Create Subject B",
      dateOfBirth: "1990-01-01",
      phoneNumber: "+256700000000",
      status: "APPROVED",
    });
    const bodyB = (await resB.text().catch(() => "")).slice(0, 160);
    await settle();
    const rowsB = await ownerRowByHmac(hmacB);
    const passB = resB.status >= 400 && rowsB.length === 0;
    rec({
      id: "B",
      scenario: "POST /api/kyc/customers with status APPROVED (fresh national_id)",
      expectation: "HTTP NOT 2xx (storage assertNotApprovedWrite throws); ZERO rows persisted",
      observed: `HTTP ${resB.status}; rowsForHmacB=${rowsB.length}; body="${bodyB}"`,
      verdict: passB ? "PASS (create cannot mint an APPROVED customer; nothing landed)" : "FAIL",
      pass: passB,
    });
  } finally {
    try {
      const del = await owner.query(
        "DELETE FROM kyc_customers WHERE national_id_hmac = ANY($1::text[])",
        [[hmacA, hmacB]],
      );
      console.log(`\ncleanup: deleted ${del.rowCount} sentinel kyc_customers row(s).`);
    } catch (e: any) {
      console.log(`cleanup kyc_customers error: ${e?.message ?? e}`);
    }
    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]],
    ] as [string, string, any[]][]) {
      try {
        await owner.query(q, p);
      } catch (e: any) {
        console.log(`cleanup ${label} (non-fatal): ${e?.message ?? e}`);
      }
    }
    // Hard-delete blocked by audit_logs FK (login + create reference this user; chain immutable).
    try {
      await owner.query(
        "UPDATE users SET is_active = false, password = gen_random_uuid()::text WHERE id = $1",
        [demoUserId],
      );
      console.log("cleanup: smoke admin deactivated + credential neutralized (audit chain preserved).");
    } catch (e: any) {
      console.log(`cleanup users deactivate (non-fatal): ${e?.message ?? e}`);
    }
    await owner.end().catch(() => {});
  }

  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 allPass = results.length > 0 && results.every((r) => r.pass);
  console.log(`ACCEPTANCE: ${passed}/${results.length} checks PASS — ${allPass ? "ALL PASS" : "SEE FAILURES ABOVE"}`);
  process.exit(allPass ? 0 : 1);
}

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