/**
 * E2E tests for migration v35: auto_rls_event_trigger.
 *
 * Verifies the event trigger auto-enables RLS on newly created public.* tables
 * across CREATE TABLE / CREATE TABLE AS / SELECT INTO, plus the one-time
 * backfill of existing public.* tables without RLS (modulo the GBRAIN:RLS_EXEMPT
 * exemption that doctor honors). Postgres-only — PGLite has no RLS or event
 * triggers; that no-op is asserted in test/migrate.test.ts.
 *
 * setupDB() runs db.initSchema() which applies all migrations including v35,
 * so the trigger and the backfill have already executed by the time these
 * tests start.
 *
 * Run: DATABASE_URL=... bun test test/e2e/migration-v35-auto-rls.test.ts
 */

import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { hasDatabase, setupDB, teardownDB, getConn, getEngine, runMigrationsUpTo } from './helpers.ts';
import { MIGRATIONS, LATEST_VERSION } from '../../src/core/migrate.ts';

const skip = !hasDatabase();
const describeE2E = skip ? describe.skip : describe;

if (skip) {
  console.log('Skipping auto-RLS E2E tests (DATABASE_URL not set)');
}

// Migration v35 lives at index 34 (0-based) in MIGRATIONS.
const v35 = MIGRATIONS.find(m => m.version === 35);
const v35Sql = (v35?.sqlFor as any)?.postgres ?? '';

describeE2E('migration v35: auto_rls_event_trigger', () => {
  beforeAll(async () => {
    await setupDB();
    // setupDB() runs db.initSchema() (SCHEMA_SQL only, no migrations).
    // Advance through every migration so v35 is actually installed.
    await runMigrationsUpTo(getEngine(), LATEST_VERSION);
  }, 30_000);

  afterAll(async () => {
    const conn = getConn();
    // Clean up every artifact this file creates. Order matters because
    // _test_v35_scope lives in a non-public schema we drop wholesale.
    await conn.unsafe(`DROP TABLE IF EXISTS _test_auto_rls_check`);
    await conn.unsafe(`DROP TABLE IF EXISTS _test_ctas`);
    await conn.unsafe(`DROP TABLE IF EXISTS _test_select_into`);
    await conn.unsafe(`DROP TABLE IF EXISTS _test_backfill_plain`);
    await conn.unsafe(`DROP TABLE IF EXISTS _test_backfill_exempt`);
    await conn.unsafe(`DROP TABLE IF EXISTS "_test_BackfillCamelCase"`);
    await conn.unsafe(`DROP SCHEMA IF EXISTS _test_v35_scope CASCADE`);
    await teardownDB();
  });

  test('event trigger exists', async () => {
    const conn = getConn();
    const triggers = await conn`
      SELECT evtname FROM pg_event_trigger
      WHERE evtname = 'auto_rls_on_create_table'
    `;
    expect(triggers.length).toBe(1);
  });

  test('new tables automatically get RLS enabled (CREATE TABLE)', async () => {
    const conn = getConn();
    await conn`CREATE TABLE _test_auto_rls_check (id serial PRIMARY KEY, val text)`;

    const result = await conn`
      SELECT rowsecurity FROM pg_tables
      WHERE schemaname = 'public' AND tablename = '_test_auto_rls_check'
    `;
    expect(result.length).toBe(1);
    expect(result[0].rowsecurity).toBe(true);
  });

  test('auto_enable_rls function exists', async () => {
    const conn = getConn();
    const funcs = await conn`
      SELECT proname FROM pg_proc
      WHERE proname = 'auto_enable_rls'
    `;
    expect(funcs.length).toBe(1);
  });

  test('FORCE RLS is NOT applied (D1: ENABLE only)', async () => {
    // pg_class.relforcerowsecurity reflects FORCE; rowsecurity reflects ENABLE.
    // v35 enables only — operators or future migrations can opt FORCE in
    // explicitly per table if defense-in-depth is desired.
    const conn = getConn();
    const result = await conn`
      SELECT relforcerowsecurity FROM pg_class
      WHERE relname = '_test_auto_rls_check' AND relkind = 'r'
    `;
    expect(result.length).toBe(1);
    expect(result[0].relforcerowsecurity).toBe(false);
  });

  test('CREATE TABLE AS triggers auto-RLS (D6)', async () => {
    // CTAS is a distinct command_tag in Postgres ('CREATE TABLE AS'). The
    // trigger's WHEN TAG list covers it explicitly.
    const conn = getConn();
    await conn`CREATE TABLE _test_ctas AS SELECT 1 AS id, 'hello'::text AS val`;

    const result = await conn`
      SELECT rowsecurity FROM pg_tables
      WHERE schemaname = 'public' AND tablename = '_test_ctas'
    `;
    expect(result.length).toBe(1);
    expect(result[0].rowsecurity).toBe(true);
  });

  test('SELECT INTO triggers auto-RLS (D6)', async () => {
    // SELECT INTO is the older synonym for CTAS. Postgres tags it 'SELECT INTO'.
    const conn = getConn();
    await conn`SELECT 1 AS id, 'world'::text AS val INTO _test_select_into`;

    const result = await conn`
      SELECT rowsecurity FROM pg_tables
      WHERE schemaname = 'public' AND tablename = '_test_select_into'
    `;
    expect(result.length).toBe(1);
    expect(result[0].rowsecurity).toBe(true);
  });

  test('non-public schemas are not touched (D2)', async () => {
    // Build a private schema and a table inside. The trigger filters
    // schema_name = 'public' so this table should remain RLS-off.
    const conn = getConn();
    await conn`CREATE SCHEMA IF NOT EXISTS _test_v35_scope`;
    await conn`CREATE TABLE _test_v35_scope.private_tbl (id int)`;

    const result = await conn`
      SELECT relrowsecurity FROM pg_class c
      JOIN pg_namespace n ON n.oid = c.relnamespace
      WHERE n.nspname = '_test_v35_scope' AND c.relname = 'private_tbl'
    `;
    expect(result.length).toBe(1);
    expect(result[0].relrowsecurity).toBe(false);
  });

  test('replay idempotency: re-running migration leaves exactly one trigger', async () => {
    const conn = getConn();
    expect(v35Sql.length).toBeGreaterThan(0);
    // Re-execute the entire v35 SQL. The DROP EVENT TRIGGER IF EXISTS +
    // CREATE EVENT TRIGGER pattern must be a clean round-trip. The backfill
    // DO block runs again too, but is a no-op since RLS is now on everywhere.
    await conn.unsafe(v35Sql);

    const triggers = await conn`
      SELECT evtname FROM pg_event_trigger
      WHERE evtname = 'auto_rls_on_create_table'
    `;
    expect(triggers.length).toBe(1);

    const funcs = await conn`SELECT proname FROM pg_proc WHERE proname = 'auto_enable_rls'`;
    expect(funcs.length).toBe(1);
  });

  test('backfill enables RLS on pre-existing public.* tables', async () => {
    const conn = getConn();
    // Temporarily drop the trigger so we can create a table WITHOUT RLS,
    // simulating a pre-v35 row.
    await conn`DROP EVENT TRIGGER IF EXISTS auto_rls_on_create_table`;
    try {
      await conn`CREATE TABLE _test_backfill_plain (id serial PRIMARY KEY)`;
      // Belt-and-suspenders: explicitly disable RLS on this fresh table.
      await conn`ALTER TABLE _test_backfill_plain DISABLE ROW LEVEL SECURITY`;

      const before = await conn`
        SELECT rowsecurity FROM pg_tables WHERE tablename = '_test_backfill_plain'
      `;
      expect(before[0].rowsecurity).toBe(false);

      // Re-run v35: trigger comes back AND the backfill DO block flips this
      // table to RLS-on (no exempt comment, schema is public, relkind='r').
      await conn.unsafe(v35Sql);

      const after = await conn`
        SELECT rowsecurity FROM pg_tables WHERE tablename = '_test_backfill_plain'
      `;
      expect(after[0].rowsecurity).toBe(true);
    } finally {
      // Make sure the trigger is restored even if assertions throw.
      const triggers = await conn`
        SELECT evtname FROM pg_event_trigger WHERE evtname = 'auto_rls_on_create_table'
      `;
      if (triggers.length === 0) {
        await conn.unsafe(v35Sql);
      }
    }
  });

  test('backfill respects GBRAIN:RLS_EXEMPT comment (matches doctor regex)', async () => {
    const conn = getConn();
    await conn`DROP EVENT TRIGGER IF EXISTS auto_rls_on_create_table`;
    try {
      await conn`CREATE TABLE _test_backfill_exempt (id serial PRIMARY KEY)`;
      await conn`ALTER TABLE _test_backfill_exempt DISABLE ROW LEVEL SECURITY`;
      // Comment must match doctor.ts EXEMPT_RE: /^GBRAIN:RLS_EXEMPT\s+reason=\S.{3,}/
      // — "test exempt" is 11 chars after `reason=`, well over the .{3,} floor.
      await conn`COMMENT ON TABLE _test_backfill_exempt IS 'GBRAIN:RLS_EXEMPT reason=test exempt'`;

      // Re-run the migration. Backfill should skip this row.
      await conn.unsafe(v35Sql);

      const after = await conn`
        SELECT rowsecurity FROM pg_tables WHERE tablename = '_test_backfill_exempt'
      `;
      expect(after[0].rowsecurity).toBe(false);
    } finally {
      const triggers = await conn`
        SELECT evtname FROM pg_event_trigger WHERE evtname = 'auto_rls_on_create_table'
      `;
      if (triggers.length === 0) {
        await conn.unsafe(v35Sql);
      }
    }
  });

  test('backfill quotes mixed-case identifiers safely (%I.%I)', async () => {
    const conn = getConn();
    await conn`DROP EVENT TRIGGER IF EXISTS auto_rls_on_create_table`;
    try {
      // Mixed-case table names require double-quoting in DDL. If the backfill
      // used %s with raw concat, ALTER TABLE public._test_BackfillCamelCase
      // would fail with "relation does not exist" because Postgres folds
      // unquoted identifiers to lowercase.
      await conn`CREATE TABLE "_test_BackfillCamelCase" (id serial PRIMARY KEY)`;
      await conn`ALTER TABLE "_test_BackfillCamelCase" DISABLE ROW LEVEL SECURITY`;

      await conn.unsafe(v35Sql);

      const after = await conn`
        SELECT relrowsecurity FROM pg_class
        WHERE relname = '_test_BackfillCamelCase' AND relkind = 'r'
      `;
      expect(after.length).toBe(1);
      expect(after[0].relrowsecurity).toBe(true);
    } finally {
      const triggers = await conn`
        SELECT evtname FROM pg_event_trigger WHERE evtname = 'auto_rls_on_create_table'
      `;
      if (triggers.length === 0) {
        await conn.unsafe(v35Sql);
      }
    }
  });

  test('regression guard: trigger function body does NOT contain EXCEPTION WHEN OTHERS', async () => {
    // Codex correctly identified that wrapping the per-table EXECUTE in
    // BEGIN…EXCEPTION WHEN OTHERS… would convert a transactional rollback
    // (loud) into a silent permissive default (quiet). Pin that by reading
    // the function body from pg_proc and grepping it.
    const conn = getConn();
    const rows = await conn`
      SELECT prosrc FROM pg_proc WHERE proname = 'auto_enable_rls'
    `;
    expect(rows.length).toBe(1);
    const body = rows[0].prosrc as string;
    expect(body.toUpperCase()).not.toContain('EXCEPTION WHEN OTHERS');
  });
});
