import rateLimit from 'express-rate-limit';
import { Request } from 'express';
import { getEnvList } from '../utils/env';
import { matchesAnyPattern } from '../utils/patternMatch';
import { isAuthenticated } from './basicAuth';

/**
 * Extracts the real client IP from X-Forwarded-For header
 * Falls back to req.ip if header is not present
 */
function getClientIp(req: Request): string {
  const forwardedFor = req.headers['x-forwarded-for'];
  if (forwardedFor && typeof forwardedFor === 'string') {
    // Take the first IP if there are multiple
    return forwardedFor.split(',')[0].trim();
  }
  return req.ip as string;
}

function isPrivatePath(reqPath: string): boolean {
  const privateFolders = getEnvList('PRIVATE_FOLDERS');
  if (privateFolders.length === 0) return false;
  const firstSegment = reqPath.replace(/^\//, '').split('/')[0] || '';
  return matchesAnyPattern(firstSegment, privateFolders);
}

/**
 * Rate limiter for Basic Auth brute force protection.
 *
 * DESIGN
 * ------
 * Credentials are pre-validated once upstream (index.ts) and stamped on
 * req.authenticated before this middleware runs. The skip function uses that
 * flag to decide upfront — at request arrival — whether to count the request.
 *
 * This avoids skipSuccessfulRequests, which counts on arrival and un-counts
 * after completion. That approach causes false 429s when multiple large file
 * downloads run in parallel: each HTTP range request increments the counter
 * before any of them finish, easily hitting the limit for a legitimate user.
 *
 * WHAT IS COUNTED
 * ---------------
 * All three must be true for a request to be counted:
 *   1. Authorization header is present  (credentials were actually submitted)
 *   2. Path is private (PRIVATE_FOLDERS) (only relevant endpoint)
 *   3. req.authenticated is false        (credentials are wrong)
 *
 * SCENARIO REFERENCE
 * ------------------
 * Browser's initial request (no credentials)                → skipped  (rule 1)
 * Wrong password                                            → COUNTED
 * Correct password                                          → skipped  (rule 3)
 * Refresh / download after login                            → skipped  (rule 3)
 * Multiple parallel range requests (downloading in chunks)  → skipped  (rule 3)
 * Public path (any credentials)                             → skipped  (rule 2)
 * Blocked IP navigates to public path                      → skipped  (rule 2)
 * Token-protected file                                      → skipped  (rule 2)
 */
export const authRateLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 failed attempts per window per IP
  message: 'Too many authentication attempts, please try again later.',
  standardHeaders: true,
  legacyHeaders: false,
  keyGenerator: (req: Request) => getClientIp(req),
  // Skip if: no credentials, not a private path, or credentials are actually valid.
  // Only requests with wrong credentials on private paths are counted.
  // req.authenticated is set upstream by the pre-validation middleware in index.ts.
  skip: (req: Request) =>
    !req.headers.authorization ||
    !isPrivatePath(req.path) ||
    isAuthenticated(req),
});
