/**
 * Rate-Limiting-Middleware für sensible Endpoints (Login, Passwort-Reset).
 * Schützt gegen Brute-Force- und Credential-Stuffing-Angriffe.
 *
 * Wenn ein Limit überschritten wird, emit() wir zusätzlich ein
 * SecurityEvent (RATE_LIMIT_HIT) – damit der Monitoring-View und das
 * Alert-System sehen, wenn jemand auf die Tür hämmert.
 */
import rateLimit, { ipKeyGenerator } from 'express-rate-limit';
import { emit as emitSecurityEvent, contextFromRequest } from '../services/securityMonitor.service.js';

function onLimitReached(label: string, severity: 'MEDIUM' | 'HIGH') {
  return (req: any, _res: any) => {
    const ctx = contextFromRequest(req);
    emitSecurityEvent({
      type: 'RATE_LIMIT_HIT',
      severity,
      message: `Rate-Limit überschritten: ${label}`,
      ipAddress: ctx.ipAddress,
      userEmail: req.body?.email,
      endpoint: ctx.endpoint,
      details: { limiter: label },
    });
  };
}

/**
 * Login-Limiter: 10 Fehlversuche pro 15 min PRO (IP + Email)-Tuple.
 *
 * Das Bucket ist gezielt das Paar, nicht IP allein und nicht Email allein:
 *  - IP allein wäre kein Schutz: ein Angreifer wechselt Proxy, hat wieder
 *    10 freie Versuche gegen den gleichen Account.
 *  - Email allein erzeugt False-Positives (Familie hinter NAT: Max
 *    vertippt sich → Nina kommt von gleicher IP nicht mehr rein) und
 *    macht Account-Lockout-DoS möglich (Angreifer sperrt fremde Accounts
 *    aus, indem er von beliebigen IPs falsche PWs gegen sie probiert).
 *  - Tuple (IP, Email): Max kann sich nicht mehr einloggen, Nina von
 *    gleicher IP schon. Max von einer anderen IP auch, solange er das
 *    richtige PW hat – ihre eigene Spur in den Buckets ist sauber.
 *
 * keyGenerator → `${ip}|${email-lowercase}`. Bei fehlender Email
 * (z.B. komplett leerer Body) Fallback nur auf IP, damit kein
 * Single-Shared-Bucket entsteht.
 */
export const loginRateLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 10,
  standardHeaders: 'draft-7',
  legacyHeaders: false,
  message: {
    success: false,
    error: 'Zu viele Login-Versuche für diese Kombination aus Account und IP. Bitte in 15 Minuten erneut versuchen.',
  },
  skipSuccessfulRequests: true,
  keyGenerator: (req): string => {
    const email = (req.body?.email || '').toString().trim().toLowerCase();
    // IPv6-Härtung: ipKeyGenerator normalisiert IPv6 auf das Subnetz
    // (Default /56), damit ein Angreifer nicht durch Rotation innerhalb
    // seines zugeteilten IPv6-Blocks das Per-IP-Limit umgeht. IPv4 bleibt
    // unverändert. Die Limiter OHNE eigenen keyGenerator (Passwort-Reset,
    // Consent) machen das schon über den Library-Default – hier holen wir
    // die Custom-keyGenerator-Limiter auf denselben Stand.
    const ip = ipKeyGenerator(req.ip || 'unknown');
    return email ? `${ip}|${email}` : `${ip}|<no-email>`;
  },
  handler: (req, res, _next, options) => {
    onLimitReached('login', 'HIGH')(req, res);
    res.status(options.statusCode).json(options.message);
  },
});

/**
 * Passwort-Reset-Anfrage: 5 Versuche pro Stunde pro IP.
 * Verhindert Mail-Flut und gezielte Brute-Force über Reset-Links.
 */
export const passwordResetRateLimiter = rateLimit({
  windowMs: 60 * 60 * 1000,    // 1 Stunde
  limit: 5,
  standardHeaders: 'draft-7',
  legacyHeaders: false,
  message: {
    success: false,
    error: 'Zu viele Passwort-Reset-Anfragen. Bitte in einer Stunde erneut versuchen.',
  },
  handler: (req, res, _next, options) => {
    onLimitReached('password-reset', 'MEDIUM')(req, res);
    res.status(options.statusCode).json(options.message);
  },
});

/**
 * Staff-Password-Set-Limiter (Pentest 48.3, 2026-06-01):
 * POST /api/users/:id/password verlangt seit 47.3 die Eingabe des eigenen
 * Admin-Passworts (`currentPassword`). Ohne Throttle könnte ein Angreifer
 * mit gestohlenem JWT die 25-Zeichen-Passwort-Policy zwar nicht erraten,
 * aber kürzere/typische Admin-Passwörter (z.B. Stagings, kompromittierte
 * Setups) per Brute-Force durchprobieren – und damit den Re-Auth-Fix
 * komplett aushebeln.
 *
 * Bucket: (IP, target-user-id). Damit walked ein Angreifer pro Opfer
 * langsam und kann nicht mit einem stolen-token gegen alle Staff-User
 * parallel anrennen. `skipSuccessfulRequests: true`, weil legitime
 * Passwort-Resets nicht den Counter füllen sollen.
 */
export const staffPasswordReAuthLimiter = rateLimit({
  windowMs: 10 * 60 * 1000, // 10 Minuten
  limit: 5,
  standardHeaders: 'draft-7',
  legacyHeaders: false,
  message: {
    success: false,
    error: 'Zu viele fehlgeschlagene Passwort-Set-Versuche. Bitte in 10 Minuten erneut versuchen.',
  },
  skipSuccessfulRequests: true,
  keyGenerator: (req): string => {
    // IPv6-Härtung wie beim Login-Limiter (siehe dort).
    const ip = ipKeyGenerator(req.ip || 'unknown');
    const targetUserId = (req.params?.id ?? '<missing>').toString();
    return `${ip}|staff-pw|${targetUserId}`;
  },
  handler: (req, res, _next, options) => {
    onLimitReached('staff-password-set', 'HIGH')(req, res);
    res.status(options.statusCode).json(options.message);
  },
});

/**
 * Public-Consent-Endpoints (/api/public/consent/:hash[/grant|/pdf]) sind
 * unauthenticated. Der hash ist 128-bit-UUID → kein Brute-Force-Risk,
 * aber DoS-Vektor: ohne Limit könnte ein Angreifer endlos POSTen und
 * den Service durch Audit-Log-Spam + Mail-Versand belasten.
 * (Pentest 2026-05-20 INFO 28.4). 30 Requests pro 15 min pro IP reicht
 * für legitime Kunden weit aus.
 */
/**
 * Globaler Backstop-Limiter für ALLE /api-Requests (Pentest R148).
 *
 * Hintergrund: Es gab bislang KEINEN generellen /api-Limiter – nur die
 * dedizierten oben (Login, Passwort-Reset, Staff-Re-Auth, Consent). Damit war
 * jeder authentifizierte Endpoint gegen Enumeration/Scripted-Abuse/DoS
 * ungedrosselt. Dieser Limiter ist eine großzügige Obergrenze, KEIN Ersatz für
 * die feineren Limiter (die feuern früher und bleiben aktiv).
 *
 * Key = NUR die (IPv6-/56-normalisierte) IP. Bewusst NICHT (IP+User): den
 * User-Claim könnten wir hier nur unverifiziert aus dem Token lesen (die volle
 * `authenticate`-Prüfung inkl. DB läuft erst pro Route). Ein Angreifer könnte
 * dann mit gefälschten userId-Claims beliebig frische Buckets erzeugen und den
 * Backstop umgehen. Per-Account-Präzision liefern ohnehin die Login-Limiter.
 *
 * Limit per Env `API_RATE_LIMIT_PER_MIN` (Default 1200/min ≈ 20/s pro IP) –
 * für legitime Nutzung (auch mehrere Nutzer hinter NAT) weit ausreichend,
 * bremst aber Flooding massiv. Healthcheck (`/api/health`) ist ausgenommen.
 *
 * KEIN SecurityEvent pro geblocktem Request: Bei einem Flood würde das den
 * Security-/Audit-Store selbst zumüllen (Amplification). Die 429 stehen im
 * Access-Log; die feineren Limiter melden weiterhin an das Monitoring.
 */
const API_RATE_LIMIT_PER_MIN = Math.max(
  parseInt(process.env.API_RATE_LIMIT_PER_MIN || '', 10) || 1200,
  60,
);

export const apiBackstopRateLimiter = rateLimit({
  windowMs: 60 * 1000,
  limit: API_RATE_LIMIT_PER_MIN,
  standardHeaders: 'draft-7',
  legacyHeaders: false,
  message: {
    success: false,
    error: 'Zu viele Anfragen in kurzer Zeit. Bitte einen Moment warten.',
  },
  keyGenerator: (req): string => ipKeyGenerator(req.ip || 'unknown'),
  skip: (req) => req.path === '/health',
});

export const publicConsentRateLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  limit: 30,
  standardHeaders: 'draft-7',
  legacyHeaders: false,
  message: {
    success: false,
    error: 'Zu viele Anfragen. Bitte in 15 Minuten erneut versuchen.',
  },
  handler: (req, res, _next, options) => {
    onLimitReached('public-consent', 'MEDIUM')(req, res);
    res.status(options.statusCode).json(options.message);
  },
});
