import { test } from 'node:test';
import assert from 'node:assert/strict';
import express from 'express';
import session from 'express-session';
import { isTestDbConfigured, setupTestDb } from '../helpers/setup-db.js';
import tokensRouter from '../../src/routes/tokens.js';
import authRouter   from '../../src/routes/auth.js';
import { requireAuth, requireUiHeader } from '../../src/middleware/auth.js';
import { hashPassword } from '../../src/auth/passwords.js';

async function app(pool) {
  process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
  process.env.AUTH_ENABLED = 'true';
  const ConnectPg = (await import('connect-pg-simple')).default(session);
  const a = express();
  a.use(express.json());
  a.use(session({
    store: new ConnectPg({ pool, tableName: 'sessions' }),
    secret: 'test', name: 'dragonflight.sid',
    cookie: { httpOnly: true, sameSite: 'lax', secure: false, maxAge: 8 * 3600 * 1000 },
    rolling: false, resave: false, saveUninitialized: false,
  }));
  a.use('/api/v1', requireUiHeader);
  a.use('/api/v1/auth', authRouter);
  a.use('/api/v1/auth/tokens', requireAuth, tokensRouter);
  a.get('/api/v1/protected/ping', requireAuth, (req, res) => res.json({ user: req.user.username }));
  return new Promise(r => {
    const srv = a.listen(0, '127.0.0.1', () => {
      r({ baseUrl: 'http://127.0.0.1:' + srv.address().port, close: () => new Promise(rs => srv.close(rs)) });
    });
  });
}

async function loginCookie(baseUrl, u, p) {
  const r = await fetch(baseUrl + '/api/v1/auth/login', {
    method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'dragonflight-ui' },
    body: JSON.stringify({ username: u, password: p }),
  });
  return (r.headers.get('set-cookie') || '').split(';')[0];
}

test('tokens: create returns the raw token exactly once; bearer of that token works; revoke 401s subsequent calls', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
  const pool = await setupTestDb();
  await pool.query(`INSERT INTO users (username, password_hash) VALUES ('alice', $1)`, [await hashPassword('correct-horse-battery')]);
  const { baseUrl, close } = await app(pool);
  try {
    const cookie = await loginCookie(baseUrl, 'alice', 'correct-horse-battery');

    // Create
    const create = await fetch(baseUrl + '/api/v1/auth/tokens', {
      method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'dragonflight-ui', cookie },
      body: JSON.stringify({ name: 'Premiere panel' }),
    });
    assert.equal(create.status, 201);
    const created = await create.json();
    assert.match(created.token, /^dfl_[0-9a-f]{64}$/);
    assert.equal(created.prefix, created.token.slice(0, 8));

    // List — should NOT include the raw token, only the prefix.
    const list = await fetch(baseUrl + '/api/v1/auth/tokens', { headers: { cookie } });
    const rows = await list.json();
    assert.equal(rows.length, 1);
    assert.equal(rows[0].prefix, created.prefix);
    assert.equal(rows[0].token, undefined);

    // The raw token authenticates as a bearer.
    const ping = await fetch(baseUrl + '/api/v1/protected/ping', {
      headers: { authorization: 'Bearer ' + created.token },
    });
    assert.equal(ping.status, 200);

    // Revoke.
    const rev = await fetch(baseUrl + '/api/v1/auth/tokens/' + created.id, {
      method: 'DELETE', headers: { cookie },
    });
    assert.equal(rev.status, 204);

    // Same bearer now 401s.
    const ping2 = await fetch(baseUrl + '/api/v1/protected/ping', {
      headers: { authorization: 'Bearer ' + created.token },
    });
    assert.equal(ping2.status, 401);
  } finally { await close(); await pool.end(); }
});

test('tokens: cannot revoke another users token', { skip: !isTestDbConfigured() && 'TEST_DATABASE_URL not set' }, async () => {
  const pool = await setupTestDb();
  await pool.query(`INSERT INTO users (username, password_hash) VALUES ('alice', $1)`, [await hashPassword('correct-horse-battery')]);
  await pool.query(`INSERT INTO users (username, password_hash) VALUES ('bob',   $1)`, [await hashPassword('bob-passphrase-12')]);
  const { baseUrl, close } = await app(pool);
  try {
    const aliceCookie = await loginCookie(baseUrl, 'alice', 'correct-horse-battery');
    const bobCookie   = await loginCookie(baseUrl, 'bob',   'bob-passphrase-12');
    const aliceTok = await (await fetch(baseUrl + '/api/v1/auth/tokens', {
      method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'dragonflight-ui', cookie: aliceCookie },
      body: JSON.stringify({ name: 'alice token' }),
    })).json();
    const r = await fetch(baseUrl + '/api/v1/auth/tokens/' + aliceTok.id, {
      method: 'DELETE', headers: { cookie: bobCookie },
    });
    assert.equal(r.status, 404);  // not found from bob's perspective
  } finally { await close(); await pool.end(); }
});
