import { config } from './config.js';
import { base64url, fromBase64url, hmac, nowIso, nowSeconds, safeEqual, sha256, uuid } from './utils.js';

function signPayload(payload) {
  const header = base64url(JSON.stringify({ alg: 'HS256', typ: 'OCT' }));
  const body = base64url(JSON.stringify(payload));
  const signature = hmac(config.authSecret, `${header}.${body}`);
  return `${header}.${body}.${signature}`;
}

export function issueSession(db, { userId = null, subject = '', role = 'user', ttlSeconds = role === 'admin' ? 4 * 3600 : 7 * 86400 }) {
  const sessionId = uuid();
  const payload = {
    sid: sessionId,
    sub: subject || userId || 'admin',
    role,
    iat: nowSeconds(),
    exp: nowSeconds() + ttlSeconds
  };
  const token = signPayload(payload);
  db.prepare(`INSERT INTO sessions(id,token_hash,user_id,role,expires_at,created_at) VALUES(?,?,?,?,?,?)`)
    .run(sessionId, sha256(token), role === 'user' ? userId : null, role, payload.exp, nowIso());
  return { token, expiresAt: payload.exp, role };
}

export function verifyToken(db, token, expectedRole = null) {
  if (!token || typeof token !== 'string') throw Object.assign(new Error('Authentication required.'), { code: 'AUTH_REQUIRED', status: 401 });
  const parts = token.split('.');
  if (parts.length !== 3) throw Object.assign(new Error('Invalid token.'), { code: 'INVALID_TOKEN', status: 401 });
  const [header, body, signature] = parts;
  const expected = hmac(config.authSecret, `${header}.${body}`);
  if (!safeEqual(signature, expected)) throw Object.assign(new Error('Invalid token signature.'), { code: 'INVALID_TOKEN', status: 401 });
  let payload;
  try { payload = JSON.parse(fromBase64url(body)); } catch { throw Object.assign(new Error('Invalid token payload.'), { code: 'INVALID_TOKEN', status: 401 }); }
  if (!payload.exp || payload.exp <= nowSeconds()) throw Object.assign(new Error('Session expired.'), { code: 'SESSION_EXPIRED', status: 401 });
  if (expectedRole && payload.role !== expectedRole) throw Object.assign(new Error('Forbidden.'), { code: 'FORBIDDEN', status: 403 });
  const session = db.prepare('SELECT * FROM sessions WHERE id=? AND token_hash=? AND expires_at>?').get(payload.sid, sha256(token), nowSeconds());
  if (!session) throw Object.assign(new Error('Session revoked.'), { code: 'SESSION_REVOKED', status: 401 });
  return payload;
}

export function bearerToken(req) {
  const header = req.headers.authorization || '';
  return header.startsWith('Bearer ') ? header.slice(7).trim() : '';
}

export function revokeSession(db, sessionId) {
  db.prepare('DELETE FROM sessions WHERE id=?').run(sessionId);
}
