// ── Login brute-force protection ───────────────────────────────────────────
//
// Three independent layers guard the login endpoint:
//
//   1. slowDown   — express-slow-down adds an increasing delay to each request
//                   once a few have been made in the window (before the hard cap
//                   bites), so a scripted burst is throttled but a human typing a
//                   wrong password twice barely notices.
//   2. loginLimiter (in rateLimit.js) — a hard 10-per-15-min cap per IP.
//   3. backoffGuard — a per-IP exponential backoff tracked in a SEPARATE store
//                   from the rate limiter, keyed on *failed* attempts. Because it
//                   is separate it persists across the rate limiter's window
//                   reset: an IP that keeps failing keeps getting locked out for
//                   longer, independent of the sliding 15-min window.
//
// The backoff store is in-memory (single instance). Times take an optional `now`
// so the escalation/decay is deterministic to test.

const slowDown = require('express-slow-down')

const log = require('../utils/logger')('loginprotect')

// Progressive delay: no delay for the first few attempts, then +0.5s each,
// capped so a request never hangs too long.
const slowLogin = slowDown({
  windowMs: 15 * 60 * 1000,
  delayAfter: 3,
  delayMs: (used) => (used - 3) * 500,
  maxDelayMs: 20 * 1000,
})

// Exponential backoff on consecutive failures.
const BASE_MS = 1000 // first failure locks ~1s
const MAX_MS = 15 * 60 * 1000 // cap a single lock at 15 min
const RESET_MS = 30 * 60 * 1000 // forget the streak after this much quiet

// ip -> { count, blockedUntil, lastFailure }
const store = new Map()

// Record a failed login and lengthen this IP's lockout. Returns ms locked.
function recordFailure(ip, now = Date.now()) {
  let e = store.get(ip)
  if (!e || now - e.lastFailure > RESET_MS) {
    e = { count: 0, blockedUntil: 0, lastFailure: 0 }
    store.set(ip, e)
  }
  e.count += 1
  e.lastFailure = now
  const delay = Math.min(BASE_MS * 2 ** (e.count - 1), MAX_MS)
  e.blockedUntil = now + delay
  log.warn('login failure recorded', { ip, count: e.count, lockMs: delay })
  return delay
}

// Clear an IP's failure streak after a successful login.
function recordSuccess(ip) {
  store.delete(ip)
}

// How long (ms) this IP is still locked out for, 0 if not locked.
function retryAfterMs(ip, now = Date.now()) {
  const e = store.get(ip)
  if (!e || e.blockedUntil <= now) return 0
  return e.blockedUntil - now
}

// Middleware: reject while the IP is in its backoff window. Generic message —
// never reveals whether the username or the password was the problem.
function backoffGuard(req, res, next) {
  const wait = retryAfterMs(req.ip)
  if (wait > 0) {
    res.set('Retry-After', String(Math.ceil(wait / 1000)))
    log.warn('login blocked by backoff', { ip: req.ip, retryAfterMs: wait })
    return res.status(429).json({ message: 'Too many login attempts. Please try again later.' })
  }
  return next()
}

// Test helpers.
function _reset() {
  store.clear()
}

module.exports = {
  slowLogin,
  backoffGuard,
  recordFailure,
  recordSuccess,
  retryAfterMs,
  _reset,
  BASE_MS,
  MAX_MS,
  RESET_MS,
}
