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

const API_BASE = process.env.TEST_API_BASE ?? "http://localhost:8080";
const DATABASE_URL = process.env.DATABASE_URL;
if (!DATABASE_URL) {
  throw new Error("DATABASE_URL must be set to run these tests");
}

const TEST_PASSWORD = "TestPass123!";
const TEST_PASSWORD_HASH =
  "$2b$10$Bs636ukPMyz01nKrsi.5m.JlDXSN22AVCvn8cgPWWDbo5yJRQX2vu";

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

// Per-run uniqueness: every row this test creates is tagged with this stamp
// so the after-hook can clean up (and a defensive sweep can mop up any
// leftovers from a prior aborted run).
const STAMP = `${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
const USERNAME_PREFIX = `emvis_${STAMP}_`;

let receiverUserId;
let receiverUsername;
let receiverCookie;

async function login(username, password) {
  const res = await fetch(`${API_BASE}/api/auth/login`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username, password }),
  });
  assert.equal(res.status, 200, `login expected 200, got ${res.status}`);
  const setCookie = res.headers.get("set-cookie");
  assert.ok(setCookie, "login response missing set-cookie header");
  const cookie = setCookie
    .split(",")
    .map((c) => c.split(";")[0].trim())
    .find((c) => c.startsWith("connect.sid="));
  assert.ok(cookie, "no connect.sid cookie returned by login");
  return cookie;
}

async function listApps(cookie) {
  const res = await fetch(`${API_BASE}/api/apps`, {
    headers: { cookie },
  });
  assert.equal(res.status, 200, `apps expected 200, got ${res.status}`);
  return res.json();
}

before(async () => {
  // Defensive sweep: only remove leftovers from this exact run's stamp,
  // which is fresh per process. Avoids touching unrelated fixtures that
  // happen to share the `emvis_` family in a shared test DB.
  await pool.query(
    `DELETE FROM users WHERE username LIKE $1`,
    [`${USERNAME_PREFIX}%`],
  );

  receiverUsername = `${USERNAME_PREFIX}receiver`;
  const u = await pool.query(
    `INSERT INTO users (username, email, password_hash, display_name_en,
       preferred_language, is_active)
     VALUES ($1, $2, $3, 'EM Visibility Test', 'en', true)
     RETURNING id`,
    [receiverUsername, `${receiverUsername}@ex.com`, TEST_PASSWORD_HASH],
  );
  receiverUserId = u.rows[0].id;

  // Give them only the 'order_receiver' role — explicitly NOT any
  // executive_* role. They should also not be in any admin group.
  await pool.query(
    `INSERT INTO user_roles (user_id, role_id)
     SELECT $1, id FROM roles WHERE name = 'order_receiver'`,
    [receiverUserId],
  );

  receiverCookie = await login(receiverUsername, TEST_PASSWORD);
});

after(async () => {
  if (receiverUserId) {
    await pool.query(
      `DELETE FROM user_roles WHERE user_id = $1`,
      [receiverUserId],
    );
    await pool.query(
      `DELETE FROM user_groups WHERE user_id = $1`,
      [receiverUserId],
    );
    await pool.query(`DELETE FROM users WHERE id = $1`, [receiverUserId]);
  }
  await pool.end();
});

test("user with only order_receiver role does NOT see the executive-meetings app", async () => {
  const apps = await listApps(receiverCookie);
  const slugs = apps.map((a) => a.slug);
  assert.ok(
    !slugs.includes("executive-meetings"),
    `expected executive-meetings NOT in apps for non-executive user; got slugs: ${slugs.join(", ")}`,
  );
});

test("after granting executive_coordinator, the same user DOES see executive-meetings", async () => {
  await pool.query(
    `INSERT INTO user_roles (user_id, role_id)
     SELECT $1, id FROM roles WHERE name = 'executive_coordinator'
     ON CONFLICT DO NOTHING`,
    [receiverUserId],
  );

  // Re-login so the new role is reflected in the session's permission cache
  // (some setups cache derived permissions on the session object).
  receiverCookie = await login(receiverUsername, TEST_PASSWORD);

  const apps = await listApps(receiverCookie);
  const slugs = apps.map((a) => a.slug);
  assert.ok(
    slugs.includes("executive-meetings"),
    `expected executive-meetings in apps after granting executive_coordinator; got slugs: ${slugs.join(", ")}`,
  );
});
