/**
 * creatorAuth.js – Autenticación ligera para el panel del creador.
 *
 * El código de creador es PÚBLICO (viaja en los links ?ref=), así que el login
 * requiere código + PIN. El PIN lo define el admin; solo se guarda su hash.
 * La sesión es un token HMAC stateless (sobrevive reinicios, expira en 7 días).
 */

import crypto from 'crypto';
import { ADMIN_PASSWORD } from '../config.js';

const TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 días

// Secreto del server-side HMAC. Si no hay admin password configurada,
// usamos un secreto fijo + advertencia (entorno dev).
const SECRET = crypto
  .createHash('sha256')
  .update(`creator-auth:${ADMIN_PASSWORD || 'dev-secret-change-me'}`)
  .digest();

export function hashPin(pin) {
  return crypto.createHash('sha256').update(String(pin)).digest('hex');
}

function base64url(buf) {
  return Buffer.from(buf).toString('base64url');
}

/** Devuelve `payloadHex.signatureHex` firmado con HMAC-SHA256. */
export function signToken(code) {
  const payload = base64url(JSON.stringify({ code, exp: Date.now() + TOKEN_TTL_MS }));
  const sig = crypto.createHmac('sha256', SECRET).update(payload).digest('base64url');
  return `${payload}.${sig}`;
}

/** Verifica firma + expiración. Devuelve { code } o null. Comparación timing-safe. */
export function verifyToken(token) {
  if (typeof token !== 'string' || !token.includes('.')) return null;
  const [payload, sig] = token.split('.');
  if (!payload || !sig) return null;
  const expected = crypto.createHmac('sha256', SECRET).update(payload).digest();
  let given;
  try {
    given = Buffer.from(sig, 'base64url');
  } catch (_e) {
    return null;
  }
  if (given.length !== expected.length || !crypto.timingSafeEqual(given, expected)) return null;

  try {
    const data = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
    if (!data?.code || typeof data.exp !== 'number' || Date.now() > data.exp) return null;
    return { code: String(data.code) };
  } catch (_e) {
    return null;
  }
}

// ─── Rate limiting simple de login (en memoria) ────────────────────────────
// 5 fallos por IP+código → bloqueo 15 minutos. Suficiente para frenar fuerza
// bruta casual sin dependencias externas.
const LOGIN_MAX_FAILS = 5;
const LOCK_MS = 15 * 60 * 1000;
const __attempts = new Map(); // key → { fails, lockedUntil }

export function checkLoginAllowed(key) {
  const rec = __attempts.get(key);
  if (rec && rec.lockedUntil && Date.now() < rec.lockedUntil) {
    return { allowed: false, retryInSeconds: Math.ceil((rec.lockedUntil - Date.now()) / 1000) };
  }
  return { allowed: true };
}

export function registerLoginFail(key) {
  const rec = __attempts.get(key) || { fails: 0, lockedUntil: 0 };
  rec.fails += 1;
  if (rec.fails >= LOGIN_MAX_FAILS) {
    rec.lockedUntil = Date.now() + LOCK_MS;
    rec.fails = 0;
  }
  __attempts.set(key, rec);
}

export function clearLoginFails(key) {
  __attempts.delete(key);
}
