import { test, before, after } from "node:test";
import assert from "node:assert/strict";
import pg from "pg";

const DATABASE_URL = process.env.DATABASE_URL;
if (!DATABASE_URL) {
  throw new Error("DATABASE_URL must be set to run these tests");
}

const pool = new pg.Pool({ connectionString: DATABASE_URL });

let testAppId;
let testPermissionId;
let hasUniquenessConstraint = false;

async function detectUniquenessConstraint() {
  // The schema in lib/db/src/schema/apps.ts declares a composite primary key
  // on (app_id, permission_id), but the live DB may lag the schema until
  // `drizzle push` succeeds (tracked separately). These tests assert the
  // uniqueness behavior only when the underlying constraint actually exists,
  // so the suite stays green regardless of migration state and starts
  // verifying the constraint automatically once it lands.
  // Look for a unique/primary-key index whose key columns are EXACTLY
  // {app_id, permission_id} — no fewer, no extras. A partial overlap
  // (e.g. a single-column unique index, or a composite that also includes
  // a third column) would not enforce the (app_id, permission_id)
  // pairwise uniqueness these tests verify.
  const { rows } = await pool.query(
    `SELECT 1
       FROM pg_index i
       JOIN pg_class c ON c.oid = i.indrelid
       JOIN LATERAL (
         SELECT array_agg(att.attname ORDER BY att.attnum) AS cols
           FROM unnest(i.indkey) WITH ORDINALITY AS k(attnum, ord)
           JOIN pg_attribute att
             ON att.attrelid = c.oid AND att.attnum = k.attnum
       ) cols ON true
      WHERE c.relname = 'app_permissions'
        AND (i.indisunique OR i.indisprimary)
        AND cols.cols @> ARRAY['app_id','permission_id']::name[]
        AND cols.cols <@ ARRAY['app_id','permission_id']::name[]
      LIMIT 1`,
  );
  return rows.length > 0;
}

before(async () => {
  hasUniquenessConstraint = await detectUniquenessConstraint();

  const stamp = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
  const slug = `apuq_app_${stamp}`;
  const permName = `apuq.perm.${stamp}`;

  const route = `/${slug}`;
  const appRes = await pool.query(
    `INSERT INTO apps (slug, name_ar, name_en, route, is_active, sort_order)
     VALUES ($1, $2, $3, $4, true, 999)
     RETURNING id`,
    [slug, slug, slug, route],
  );
  testAppId = appRes.rows[0].id;

  const permRes = await pool.query(
    `INSERT INTO permissions (name, description_en) VALUES ($1, $2) RETURNING id`,
    [permName, permName],
  );
  testPermissionId = permRes.rows[0].id;
});

after(async () => {
  await pool.query(`DELETE FROM app_permissions WHERE app_id = $1`, [testAppId]);
  await pool.query(`DELETE FROM apps WHERE id = $1`, [testAppId]);
  await pool.query(`DELETE FROM permissions WHERE id = $1`, [testPermissionId]);
  await pool.end();
});

test("app_permissions composite primary key prevents duplicate (app_id, permission_id)", async (t) => {
  if (!hasUniquenessConstraint) {
    t.skip(
      "app_permissions has no uniqueness constraint on (app_id, permission_id) yet; skipping until drizzle push is re-run",
    );
    return;
  }

  // First insert succeeds
  await pool.query(
    `INSERT INTO app_permissions (app_id, permission_id) VALUES ($1, $2)`,
    [testAppId, testPermissionId],
  );

  // Plain duplicate insert should fail with a unique-violation error (SQLSTATE 23505)
  await assert.rejects(
    pool.query(
      `INSERT INTO app_permissions (app_id, permission_id) VALUES ($1, $2)`,
      [testAppId, testPermissionId],
    ),
    (err) => err.code === "23505",
  );

  // Only one row should exist for the pair
  const { rows: countRows } = await pool.query(
    `SELECT COUNT(*)::int AS c FROM app_permissions WHERE app_id = $1 AND permission_id = $2`,
    [testAppId, testPermissionId],
  );
  assert.equal(countRows[0].c, 1);
});

test("ON CONFLICT DO NOTHING handles duplicate inserts gracefully (no error, no extra row)", async (t) => {
  if (!hasUniquenessConstraint) {
    t.skip(
      "app_permissions has no uniqueness constraint on (app_id, permission_id) yet; skipping until drizzle push is re-run",
    );
    return;
  }

  // Re-running the same insert with ON CONFLICT DO NOTHING should not throw
  // and should not add a second row. This mirrors what `seed.ts` and any
  // future admin permission management endpoint does via Drizzle's
  // `.onConflictDoNothing()`.
  const res = await pool.query(
    `INSERT INTO app_permissions (app_id, permission_id) VALUES ($1, $2) ON CONFLICT DO NOTHING`,
    [testAppId, testPermissionId],
  );
  assert.equal(res.rowCount, 0, "expected no row to be inserted on conflict");

  const { rows: countRows } = await pool.query(
    `SELECT COUNT(*)::int AS c FROM app_permissions WHERE app_id = $1 AND permission_id = $2`,
    [testAppId, testPermissionId],
  );
  assert.equal(countRows[0].c, 1, "exactly one row should remain after conflict-safe insert");
});
