import { test } from 'node:test';
import assert from 'node:assert/strict';
import { isTestDbConfigured, setupTestDb } from '../helpers/setup-db.js';
import { requireAuth, DEV_USER_ID } from '../../src/middleware/auth.js';
import { generateToken, hashToken } from '../../src/auth/tokens.js';

function mockRes() {
  const res = {
    statusCode: 200, body: null,
    status(n) { this.statusCode = n; return this; },
    json(o)   { this.body = o; return this; },
  };
  return res;
}

function mockReq({ session = null, authHeader = null } = {}) {
  return {
    session,
    headers: authHeader ? { authorization: authHeader } : {},
  };
}

test('AUTH_ENABLED=false → attaches dev user and calls next', async () => {
  delete process.env.AUTH_ENABLED;
  const req = mockReq(); const res = mockRes(); let called = false;
  await requireAuth(req, res, () => { called = true; });
  assert.equal(called, true);
  assert.equal(req.user.id, DEV_USER_ID);
  assert.equal(req.user.username, 'dev');
});

test('AUTH_ENABLED=true + no session + no bearer → 401', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
  const pool = await setupTestDb();
  process.env.AUTH_ENABLED = 'true';
  try {
    const { requireAuth } = await import('../../src/middleware/auth.js?cache=' + Date.now());
    const req = mockReq(); const res = mockRes();
    await requireAuth(req, res, () => {});
    assert.equal(res.statusCode, 401);
    assert.deepEqual(res.body, { error: 'unauthorized' });
  } finally { await pool.end(); }
});

test('valid session within idle/absolute window → next + req.user populated', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
  const pool = await setupTestDb();
  process.env.AUTH_ENABLED = 'true';
  process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
  const { rows } = await pool.query(
    `INSERT INTO users (username, password_hash, display_name, role)
     VALUES ('alice', 'x', 'Alice', 'admin') RETURNING id`);
  const userId = rows[0].id;
  try {
    const { requireAuth } = await import('../../src/middleware/auth.js?v=' + Date.now());
    const now = Date.now();
    const req = mockReq({ session: { user_id: userId, first_seen_at: now - 1000, last_seen_at: now - 500 } });
    const res = mockRes(); let called = false;
    await requireAuth(req, res, () => { called = true; });
    assert.equal(called, true, 'next not called; body=' + JSON.stringify(res.body));
    assert.equal(req.user.id, userId);
    assert.equal(req.user.username, 'alice');
  } finally { await pool.end(); }
});

test('idle-expired session (>1h since last_seen_at) → 401', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
  const pool = await setupTestDb();
  process.env.AUTH_ENABLED = 'true';
  process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
  const { rows } = await pool.query(
    `INSERT INTO users (username, password_hash) VALUES ('b', 'x') RETURNING id`);
  try {
    const { requireAuth } = await import('../../src/middleware/auth.js?v=' + Date.now());
    const now = Date.now();
    const session = { user_id: rows[0].id, first_seen_at: now - 1000, last_seen_at: now - (61 * 60 * 1000), destroy(cb){ cb(); } };
    const req = mockReq({ session }); const res = mockRes();
    await requireAuth(req, res, () => {});
    assert.equal(res.statusCode, 401);
  } finally { await pool.end(); }
});

test('absolute-expired session (>8h since first_seen_at) → 401', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
  const pool = await setupTestDb();
  process.env.AUTH_ENABLED = 'true';
  process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
  const { rows } = await pool.query(
    `INSERT INTO users (username, password_hash) VALUES ('c', 'x') RETURNING id`);
  try {
    const { requireAuth } = await import('../../src/middleware/auth.js?v=' + Date.now());
    const now = Date.now();
    const session = { user_id: rows[0].id, first_seen_at: now - (9 * 3600 * 1000), last_seen_at: now - 100, destroy(cb){ cb(); } };
    const req = mockReq({ session }); const res = mockRes();
    await requireAuth(req, res, () => {});
    assert.equal(res.statusCode, 401);
  } finally { await pool.end(); }
});

test('valid bearer token → next + req.user populated', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
  const pool = await setupTestDb();
  process.env.AUTH_ENABLED = 'true';
  process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
  const { rows: u } = await pool.query(
    `INSERT INTO users (username, password_hash) VALUES ('d', 'x') RETURNING id`);
  const token = generateToken();
  await pool.query(
    `INSERT INTO api_tokens (user_id, name, token_hash, token_prefix)
     VALUES ($1, 'test', $2, $3)`,
    [u[0].id, hashToken(token), token.slice(0, 8)]);
  try {
    const { requireAuth } = await import('../../src/middleware/auth.js?v=' + Date.now());
    const req = mockReq({ authHeader: 'Bearer ' + token });
    const res = mockRes(); let called = false;
    await requireAuth(req, res, () => { called = true; });
    assert.equal(called, true, 'next not called; body=' + JSON.stringify(res.body));
    assert.equal(req.user.username, 'd');
  } finally { await pool.end(); }
});

test('invalid bearer token → 401', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
  const pool = await setupTestDb();
  process.env.AUTH_ENABLED = 'true';
  process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
  try {
    const { requireAuth } = await import('../../src/middleware/auth.js?v=' + Date.now());
    const req = mockReq({ authHeader: 'Bearer dfl_nope' });
    const res = mockRes();
    await requireAuth(req, res, () => {});
    assert.equal(res.statusCode, 401);
  } finally { await pool.end(); }
});

test('bearer token whose user was deleted → 401', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
  const pool = await setupTestDb();
  process.env.AUTH_ENABLED = 'true';
  process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
  const { rows: u } = await pool.query(
    `INSERT INTO users (username, password_hash) VALUES ('e', 'x') RETURNING id`);
  const token = generateToken();
  await pool.query(
    `INSERT INTO api_tokens (user_id, name, token_hash, token_prefix)
     VALUES ($1, 'test', $2, $3)`,
    [u[0].id, hashToken(token), token.slice(0, 8)]);
  await pool.query(`DELETE FROM users WHERE id = $1`, [u[0].id]);
  try {
    const { requireAuth } = await import('../../src/middleware/auth.js?v=' + Date.now());
    const req = mockReq({ authHeader: 'Bearer ' + token });
    const res = mockRes();
    await requireAuth(req, res, () => {});
    assert.equal(res.statusCode, 401);
  } finally { await pool.end(); }
});

import { requireUiHeader } from '../../src/middleware/auth.js';

test('requireUiHeader: GET → next (any header)', () => {
  let called = false;
  requireUiHeader({ method: 'GET', headers: {} }, { status: () => ({ json: () => {} }) }, () => { called = true; });
  assert.equal(called, true);
});

test('requireUiHeader: POST without header → 403', () => {
  const res = { status(n) { this.statusCode = n; return this; }, json(o) { this.body = o; } };
  requireUiHeader({ method: 'POST', headers: {} }, res, () => {});
  assert.equal(res.statusCode, 403);
});

test('requireUiHeader: POST with correct header → next', () => {
  let called = false;
  requireUiHeader({ method: 'POST', headers: { 'x-requested-with': 'dragonflight-ui' } }, {}, () => { called = true; });
  assert.equal(called, true);
});

test('requireUiHeader: POST with bearer auth → next (exempt)', () => {
  let called = false;
  requireUiHeader({ method: 'POST', headers: { authorization: 'Bearer dfl_xxx' } }, {}, () => { called = true; });
  assert.equal(called, true);
});
