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

const createdUserIds = [];
const createdUsernames = [];

function uniqueName(prefix) {
  return `${prefix}_${Date.now().toString(36)}_${Math.random()
    .toString(36)
    .slice(2, 8)}`;
}

async function createUser(prefix) {
  const username = uniqueName(prefix);
  const { rows } = await pool.query(
    `INSERT INTO users (username, email, password_hash, display_name_en, preferred_language, is_active)
     VALUES ($1, $2, $3, 'Session Test', 'en', true) RETURNING id`,
    [username, `${username}@example.com`, TEST_PASSWORD_HASH],
  );
  const id = rows[0].id;
  createdUserIds.push(id);
  createdUsernames.push(username);
  await pool.query(
    `INSERT INTO user_roles (user_id, role_id)
     SELECT $1, id FROM roles WHERE name = 'user'`,
    [id],
  );
  return { id, username };
}

function extractSidCookie(res) {
  const setCookie = res.headers.get("set-cookie");
  if (!setCookie) return null;
  return setCookie
    .split(",")
    .map((c) => c.split(";")[0].trim())
    .find((c) => c.startsWith("connect.sid=")) ?? null;
}

after(async () => {
  if (createdUserIds.length > 0) {
    await pool.query(`DELETE FROM user_roles WHERE user_id = ANY($1::int[])`, [
      createdUserIds,
    ]);
    await pool.query(`DELETE FROM users WHERE id = ANY($1::int[])`, [
      createdUserIds,
    ]);
  }
  // Also drop registration-created users (whose ids we don't know up front).
  if (createdUsernames.length > 0) {
    await pool.query(
      `DELETE FROM user_roles WHERE user_id IN
         (SELECT id FROM users WHERE username = ANY($1::text[]))`,
      [createdUsernames],
    );
    await pool.query(`DELETE FROM users WHERE username = ANY($1::text[])`, [
      createdUsernames,
    ]);
  }
  await pool.end();
});

test("login session is persisted before response so an immediate /auth/me succeeds", async () => {
  const user = await createUser("session_login");

  const loginRes = await fetch(`${API_BASE}/api/auth/login`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username: user.username, password: TEST_PASSWORD }),
  });
  assert.equal(loginRes.status, 200, "login should succeed");
  const cookie = extractSidCookie(loginRes);
  assert.ok(cookie, "login must set connect.sid cookie");

  // Immediate follow-up authenticated request — must already see the session.
  const meRes = await fetch(`${API_BASE}/api/auth/me`, {
    headers: { Cookie: cookie },
  });
  assert.equal(
    meRes.status,
    200,
    "fast follow-up /auth/me after login must be authenticated",
  );
  const me = await meRes.json();
  assert.equal(me.username, user.username);
});

test("registration session is persisted before response so an immediate /auth/me succeeds", async () => {
  const username = uniqueName("session_register");
  createdUsernames.push(username);

  const regRes = await fetch(`${API_BASE}/api/auth/register`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      username,
      email: `${username}@example.com`,
      password: TEST_PASSWORD,
      displayNameEn: "Session Register",
      preferredLanguage: "en",
    }),
  });
  // Registration may be closed in some environments; if so, skip the assertion
  // about persistence — there is nothing to verify in that case.
  if (regRes.status === 403) {
    return;
  }
  assert.equal(regRes.status, 201, "register should succeed");
  const cookie = extractSidCookie(regRes);
  assert.ok(cookie, "register must set connect.sid cookie");

  const meRes = await fetch(`${API_BASE}/api/auth/me`, {
    headers: { Cookie: cookie },
  });
  assert.equal(
    meRes.status,
    200,
    "fast follow-up /auth/me after register must be authenticated",
  );
});

test("logout destroys the session before response so an immediate /auth/me is rejected", async () => {
  const user = await createUser("session_logout");

  const loginRes = await fetch(`${API_BASE}/api/auth/login`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ username: user.username, password: TEST_PASSWORD }),
  });
  assert.equal(loginRes.status, 200);
  const cookie = extractSidCookie(loginRes);
  assert.ok(cookie);

  const logoutRes = await fetch(`${API_BASE}/api/auth/logout`, {
    method: "POST",
    headers: { Cookie: cookie },
  });
  assert.equal(logoutRes.status, 200, "logout should succeed");

  // Immediate follow-up using the same (now-destroyed) sid must be unauthorized.
  const meRes = await fetch(`${API_BASE}/api/auth/me`, {
    headers: { Cookie: cookie },
  });
  assert.equal(
    meRes.status,
    401,
    "fast follow-up /auth/me after logout must be unauthorized",
  );
});
