/**
 * services/throttle.js
 * Per-IP and per-account login throttling, with exponential backoff.
 *
 * The login route calls:
 *   throttle.checkIp(ip)        → returns 429 if too many attempts
 *   throttle.recordFailure(ip, email)
 *   throttle.recordSuccess(ip, email)
 *   throttle.checkAccount(user) → returns 423 if user is locked
 */

'use strict';

const { db } = require('../config/db');
const sec   = require('../config/security');

const ipWindow  = db.prepare(`SELECT COUNT(*) AS n FROM login_attempts WHERE ip = ? AND succeeded = 0 AND attempted_at > ?`);
const lastIpTry = db.prepare(`SELECT MAX(attempted_at) AS t FROM login_attempts WHERE ip = ? AND succeeded = 0`);
const recordFail = db.prepare(`INSERT INTO login_attempts (ip, email, succeeded, attempted_at) VALUES (?, ?, 0, ?)`);
const recordOk   = db.prepare(`INSERT INTO login_attempts (ip, email, succeeded, attempted_at) VALUES (?, ?, 1, ?)`);
const cleanup    = db.prepare(`DELETE FROM login_attempts WHERE attempted_at < ?`);

function checkIp(ip) {
  // Cleanup old entries on each check (cheap)
  cleanup.run(Date.now() - 60 * 60 * 1000);
  const since = Date.now() - 60 * 60 * 1000;
  const row = ipWindow.get(ip, since);
  if (row.n >= sec.IP_LOGIN_MAX_PER_HR) {
    return { blocked: true, reason: 'ip_rate', count: row.n, retryAfter: 60 * 60 };
  }
  // Per-attempt spacing
  const last = lastIpTry.get(ip);
  if (last && last.t && (Date.now() - last.t) < sec.IP_LOGIN_COOLDOWN_MS) {
    return { blocked: true, reason: 'ip_cooldown', retryAfter: Math.ceil((sec.IP_LOGIN_COOLDOWN_MS - (Date.now() - last.t)) / 1000) };
  }
  return { blocked: false };
}

function recordFailure(ip, email) {
  recordFail.run(ip, (email || '').toLowerCase(), Date.now());
}

function recordSuccess(ip, email) {
  recordOk.run(ip, (email || '').toLowerCase(), Date.now());
}

function checkAccount(user) {
  if (!user) return { locked: false };
  if (user.locked_until && user.locked_until > Date.now()) {
    return { locked: true, until: user.locked_until };
  }
  return { locked: false };
}

// Exponential backoff for the *account*, not just the IP. Each subsequent
// failure doubles the lockout window until capped.
function recordAccountFailure(userId, priorFailedLogins) {
  const failed = priorFailedLogins + 1;
  if (failed < sec.LOGIN_MAX_ATTEMPTS) return null; // not yet
  const minutes = sec.LOGIN_LOCKOUT_MIN * Math.pow(sec.LOGIN_LOCKOUT_GROWTH, failed - sec.LOGIN_MAX_ATTEMPTS);
  const until = Date.now() + Math.min(minutes * 60 * 1000, sec.LOGIN_LOCKOUT_MAX);
  db.prepare(`UPDATE users SET failed_logins = failed_logins + 1, locked_until = ?, updated_at = ? WHERE id = ?`)
    .run(until, Date.now(), userId);
  return { until, minutes: Math.round((until - Date.now()) / 60000) };
}

function recordAccountSuccess(userId) {
  db.prepare(`UPDATE users SET failed_logins = 0, locked_until = NULL, last_login_at = ?, updated_at = ? WHERE id = ?`)
    .run(Date.now(), Date.now(), userId);
}

module.exports = { checkIp, recordFailure, recordSuccess, checkAccount, recordAccountFailure, recordAccountSuccess };
