// Per-session cooldown for FAILED credential logins against iDempiere
// `auth/tokens`. A dead session (stale cookies) polled by an open tab used to
// hammer a credential login on every request/poll tick — thousands of
// AuthFailure.log entries. This ledger throttles retries with exponential
// backoff so at most one real login attempt goes out per backoff window.
//
// Per-process Map — PM2 runs 2 cluster instances, so the effective allowed
// rate doubles (same accepted caveat as notificationBus/chatPresence).
// Only the credential FALLBACK is throttled; the interactive login route is
// deliberately not (users mistyping a password must not get locked out here —
// iDempiere has its own lockout).

import { getRequestIP } from 'h3'

type CooldownEntry = { fails: number; nextAt: number; lastAt: number }

const attempts = new Map<string, CooldownEntry>()
const BASE_MS = 30_000              // 1st failure → 30s
const MAX_MS = 30 * 60_000          // cap: 30 min (30s, 1m, 2m, 4m, 8m, 16m, 30m, …)
const ENTRY_TTL = 60 * 60_000       // forget a key 1h after its last failure
const SWEEP_THRESHOLD = 500         // lazy cleanup to bound memory

export function cooldownKey(event: any): string {
  // logship_session is set at login and survives refreshes; it can be absent
  // (cleared cookies, direct API hits) → fall back to the base64 username
  // (stable per user), then to client IP.
  return getCookie(event, 'logship_session')
    || getCookie(event, 'logship_xu')
    || 'ip:' + (getRequestIP(event, { xForwardedFor: true }) ?? 'unknown')
}

export function assertNotCoolingDown(key: string): void {
  sweep()
  const entry = attempts.get(key)
  if (entry && Date.now() < entry.nextAt) {
    throw createError({
      statusCode: 429,
      statusMessage: 'Credential login suppressed',
      data: {
        status: 429,
        message: `Credential login suppressed after ${entry.fails} failed attempt(s); retry in ${Math.ceil((entry.nextAt - Date.now()) / 1000)}s`
      }
    })
  }
}

export function recordFailure(key: string): void {
  const entry = attempts.get(key) ?? { fails: 0, nextAt: 0, lastAt: 0 }
  entry.fails += 1
  entry.lastAt = Date.now()
  entry.nextAt = entry.lastAt + Math.min(BASE_MS * 2 ** (entry.fails - 1), MAX_MS)
  attempts.set(key, entry)
}

export function recordSuccess(key: string): void {
  attempts.delete(key)
}

function sweep(): void {
  if (attempts.size < SWEEP_THRESHOLD) return
  const now = Date.now()
  for (const [key, entry] of attempts) {
    if (now - entry.lastAt > ENTRY_TTL) attempts.delete(key)
  }
}
