import { test } from 'node:test';
import assert from 'node:assert/strict';
import { isTestDbConfigured, setupTestDb } from '../helpers/setup-db.js';
import {
  isAdmin,
  accessibleProjectIds,
  projectLevel,
  assertProjectAccess,
} from '../../src/auth/authz.js';

const SKIP = !isTestDbConfigured() && 'TEST_DATABASE_URL not set';

// ── isAdmin (pure, no DB) ───────────────────────────────────────────────────
test('isAdmin true only for role admin', () => {
  assert.equal(isAdmin({ role: 'admin' }), true);
  assert.equal(isAdmin({ role: 'editor' }), false);
  assert.equal(isAdmin({ role: 'viewer' }), false);
  assert.equal(isAdmin(null), false);
  assert.equal(isAdmin(undefined), false);
});

// Seed helpers shared across the DB-backed cases.
async function seed(pool) {
  const proj = async (name) =>
    (await pool.query(
      `INSERT INTO projects (name, s3_prefix) VALUES ($1, $1) RETURNING id`, [name]
    )).rows[0].id;
  const user = async (username, role) =>
    (await pool.query(
      `INSERT INTO users (username, password_hash, role) VALUES ($1, 'x', $2) RETURNING id`,
      [username, role]
    )).rows[0].id;
  const group = async (name) =>
    (await pool.query(`INSERT INTO groups (name) VALUES ($1) RETURNING id`, [name])).rows[0].id;
  const grantUser = (pid, uid, level) =>
    pool.query(
      `INSERT INTO project_access (project_id, subject_type, subject_id, level)
       VALUES ($1, 'user', $2, $3)`, [pid, uid, level]);
  const grantGroup = (pid, gid, level) =>
    pool.query(
      `INSERT INTO project_access (project_id, subject_type, subject_id, level)
       VALUES ($1, 'group', $2, $3)`, [pid, gid, level]);
  const addToGroup = (uid, gid) =>
    pool.query(`INSERT INTO user_groups (user_id, group_id) VALUES ($1, $2)`, [uid, gid]);
  return { proj, user, group, grantUser, grantGroup, addToGroup };
}

test('admin sees all projects, every project at edit', { skip: SKIP }, async () => {
  const pool = await setupTestDb();
  try {
    const s = await seed(pool);
    await s.proj('Alpha'); await s.proj('Beta');
    const admin = { id: await s.user('adm', 'admin'), role: 'admin' };

    const acc = await accessibleProjectIds(admin, pool);
    assert.equal(acc.all, true);
    assert.equal(await projectLevel(admin, '00000000-0000-4000-8000-000000000001', pool), 'edit');
    await assertProjectAccess(admin, '00000000-0000-4000-8000-000000000001', 'edit', pool); // no throw
  } finally { await pool.end(); }
});

test('direct user grant scopes access and respects level', { skip: SKIP }, async () => {
  const pool = await setupTestDb();
  try {
    const s = await seed(pool);
    const alpha = await s.proj('Alpha');
    const beta  = await s.proj('Beta');
    const u = { id: await s.user('bob', 'editor'), role: 'editor' };
    await s.grantUser(alpha, u.id, 'view');

    const acc = await accessibleProjectIds(u, pool);
    assert.equal(acc.all, false);
    assert.deepEqual([...acc.ids], [alpha]);
    assert.equal(await projectLevel(u, alpha, pool), 'view');
    assert.equal(await projectLevel(u, beta, pool), null);

    await assertProjectAccess(u, alpha, 'view', pool); // ok
    await assert.rejects(() => assertProjectAccess(u, alpha, 'edit', pool), e => e.status === 403);
    await assert.rejects(() => assertProjectAccess(u, beta, 'view', pool), e => e.status === 403);
  } finally { await pool.end(); }
});

test('group grant flows through membership', { skip: SKIP }, async () => {
  const pool = await setupTestDb();
  try {
    const s = await seed(pool);
    const alpha = await s.proj('Alpha');
    const u = { id: await s.user('carol', 'viewer'), role: 'viewer' };
    const g = await s.group('broadcasters');
    await s.addToGroup(u.id, g);
    await s.grantGroup(alpha, g, 'edit');

    assert.equal(await projectLevel(u, alpha, pool), 'edit');
    const acc = await accessibleProjectIds(u, pool);
    assert.deepEqual([...acc.ids], [alpha]);
    await assertProjectAccess(u, alpha, 'edit', pool); // ok via group
  } finally { await pool.end(); }
});

test('effective level is the max of direct + group grants', { skip: SKIP }, async () => {
  const pool = await setupTestDb();
  try {
    const s = await seed(pool);
    const alpha = await s.proj('Alpha');
    const u = { id: await s.user('dan', 'editor'), role: 'editor' };
    const g = await s.group('team');
    await s.addToGroup(u.id, g);
    await s.grantUser(alpha, u.id, 'view');   // direct: view
    await s.grantGroup(alpha, g, 'edit');     // group: edit → wins

    assert.equal(await projectLevel(u, alpha, pool), 'edit');
  } finally { await pool.end(); }
});

test('user with no grants sees nothing', { skip: SKIP }, async () => {
  const pool = await setupTestDb();
  try {
    const s = await seed(pool);
    await s.proj('Alpha');
    const u = { id: await s.user('eve', 'viewer'), role: 'viewer' };

    const acc = await accessibleProjectIds(u, pool);
    assert.equal(acc.ids.size, 0);
  } finally { await pool.end(); }
});
