/**
 * sessionGuard.js — Challenge de un solo uso para proteger endpoints que
 * golpean Garena (player_info). Sin esto, cualquiera puede llamar la API
 * desde curl/Postman y desgastar la cookie del admin.
 *
 * Flujo:
 *   1. Frontend pide GET /api/session/challenge al cargar la página.
 *   2. Backend devuelve { id, token } (HMAC firmado, expira en 10 min).
 *   3. Frontend incluye X-Challenge-Token en la llamada a /api/player_info.
 *   4. Backend valida: existe, no expiró, no fue usado → lo consume y pasa.
 *
 * Cada challenge es de UN SOLO USO. Un atacante que intercepte el token ya
 * no puede reutilizarlo.
 */

import crypto from 'crypto';

const CHALLENGE_TTL_MS = 10 * 60 * 1000; // 10 minutos
const MAX_PENDING = 500; // límite anti-flood del Map

// { id: expiresAt }
const pending = new Map();

function cleanup() {
  const now = Date.now();
  for (const [id, exp] of pending) {
    if (exp < now) pending.delete(id);
  }
}

export function createChallenge(secret) {
  cleanup();
  if (pending.size >= MAX_PENDING) return null;

  const id = crypto.randomUUID();
  const expiresAt = Date.now() + CHALLENGE_TTL_MS;
  pending.set(id, expiresAt);

  const payload = `${id}:${expiresAt}`;
  const sig = crypto.createHmac('sha256', secret).update(payload).digest('hex');

  return { id, token: `${payload}:${sig}` };
}

/** Valida y CONSUME el challenge. Devuelve true si es válido. */
export function validateAndConsume(token, secret) {
  if (!token || typeof token !== 'string') return false;
  const parts = token.split(':');
  if (parts.length !== 3) return false;

  const [id, expiresAtStr, sig] = parts;
  const expiresAt = Number(expiresAtStr);
  if (!Number.isFinite(expiresAt) || Date.now() > expiresAt) {
    pending.delete(id);
    return false;
  }

  // Timing-safe signature check
  const expected = crypto.createHmac('sha256', secret).update(`${id}:${expiresAtStr}`).digest('hex');
  const sigBuf = Buffer.from(sig, 'hex');
  const expBuf = Buffer.from(expected, 'hex');
  if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) return false;

  // Debe existir en pending (one-time use)
  if (!pending.has(id)) return false;
  pending.delete(id);

  return true;
}
