import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';

// In-memory stores for tracking IPs (use Redis in production)
const ipAttempts = new Map<string, { count: number; lastAttempt: number }>();
const ip404Attempts = new Map<string, { count: number; lastAttempt: number }>();
// Map of ip -> ban expiry timestamp. Avoids setTimeout closures leaking on hot reload.
const bannedIPs = new Map<string, number>();

// Suspicious patterns that indicate malicious activity
const MALICIOUS_PATTERNS = [
  // Your existing patterns
  /web-inf/i,
  /\.jsp/i,
  /\.php/i,
  /puttest/i,
  /WEB-INF/i,
  /\.xml$/i,
  /perl/i,
  /xampp/i,
  /phpwebgallery/i,
  /FileManager/i,
  /standalonemanager/i,
  /h2console/i,
  /WebAdmin/i,
  /login_form\.php/i,
  /%2e/i,
  /%u002e/i,
  /\.%00/i,
  /\.\./,
  /lcgi/i,

  // New patterns from your logs
  /\/appliance\//i,
  /bomgar/i,
  /netburner-logo/i,
  /\/ui\/images\//i,
  /logon_merge/i,
  /logon_t\.gif/i,
  /login_top\.gif/i,
  /theme1\/images/i,
  /\.well-known\/acme-challenge\/.*\.jpg$/i,
  /\.well-known\/pki-validation\/.*\.jpg$/i,

  // Path traversal and system file access patterns
  /\/etc\/passwd/i,
  /\/etc%2fpasswd/i,
  /\/etc%5cpasswd/i,
  /\/\/+etc/i,
  /\\\\+.*etc/i,
  /%2f%2f/i,
  /%5c%5c/i,
  /\/\/+/,
  /\\\\+/,
  /%00/i,
  /%23/i,

  // Encoded path traversal attempts
  /%2e%2e/i,
  /%252e/i,
  /%c0%ae/i,
  /%c1%9c/i,
];

// Suspicious HTTP methods
const SUSPICIOUS_METHODS = ['TRACE', 'PUT', 'DELETE', 'PATCH'];

const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
const MAX_ATTEMPTS = 10; // Max suspicious requests per window
const BAN_DURATION = 30 * 60 * 1000; // 30 minutes

// 404 rate limiting settings
const RATE_404_WINDOW = 2 * 60 * 1000; // 2 minutes
const MAX_404_ATTEMPTS = 10; // Max 404s before ban

let lastCleanup = Date.now();
const CLEANUP_INTERVAL = 5 * 60 * 1000; // 5 minutes

// Lazily purge stale entries so Maps don't grow without bound.
// Called on every request but only iterates Maps every CLEANUP_INTERVAL.
const cleanupStaleMaps = () => {
  const now = Date.now();
  if (now - lastCleanup < CLEANUP_INTERVAL) return;
  lastCleanup = now;
  for (const [ip, data] of ipAttempts.entries()) {
    if (now - data.lastAttempt > RATE_LIMIT_WINDOW) ipAttempts.delete(ip);
  }
  for (const [ip, data] of ip404Attempts.entries()) {
    if (now - data.lastAttempt > RATE_404_WINDOW) ip404Attempts.delete(ip);
  }
  for (const [ip, expiry] of bannedIPs.entries()) {
    if (now > expiry) bannedIPs.delete(ip);
  }
};

const isIPBanned = (ip: string): boolean => {
  const expiry = bannedIPs.get(ip);
  if (expiry === undefined) return false;
  if (Date.now() > expiry) {
    bannedIPs.delete(ip);
    return false;
  }
  return true;
};

const getClientIP = (request: NextRequest): string => {
  const forwarded = request.headers.get('x-forwarded-for');
  const realIP = request.headers.get('x-real-ip');
  const cfConnectingIP = request.headers.get('cf-connecting-ip');

  if (forwarded) return (forwarded.split(',')[0] ?? '').trim();
  if (realIP) return realIP;
  if (cfConnectingIP) return cfConnectingIP;
  return request.headers.get('host') ?? 'unknown';
};

const isPathSuspicious = (pathname: string): boolean => {
  return MALICIOUS_PATTERNS.some((pattern) => pattern.test(pathname));
};

const isMethodSuspicious = (method: string): boolean => {
  return SUSPICIOUS_METHODS.includes(method);
};

const updateIPAttempts = (ip: string): boolean => {
  const now = Date.now();
  const attempts = ipAttempts.get(ip);

  if (!attempts || now - attempts.lastAttempt > RATE_LIMIT_WINDOW) {
    ipAttempts.set(ip, { count: 1, lastAttempt: now });
    return false;
  }

  attempts.count++;
  attempts.lastAttempt = now;

  if (attempts.count > MAX_ATTEMPTS) {
    bannedIPs.set(ip, Date.now() + BAN_DURATION);
    ipAttempts.delete(ip);
    return true;
  }

  return false;
};

const update404Attempts = (ip: string): boolean => {
  const now = Date.now();
  const attempts = ip404Attempts.get(ip);

  if (!attempts || now - attempts.lastAttempt > RATE_404_WINDOW) {
    ip404Attempts.set(ip, { count: 1, lastAttempt: now });
    return false;
  }

  attempts.count++;
  attempts.lastAttempt = now;

  if (attempts.count > MAX_404_ATTEMPTS) {
    bannedIPs.set(ip, Date.now() + BAN_DURATION);
    ip404Attempts.delete(ip);

    console.log(
      `🔨 IP ${ip} banned for excessive 404 requests (${attempts.count} in ${RATE_404_WINDOW / 1000}s)`,
    );

    return true;
  }

  return false;
};

export const banSuspiciousIPs = (request: NextRequest): NextResponse | null => {
  cleanupStaleMaps();

  const { pathname } = request.nextUrl;
  const method = request.method;
  const ip = getClientIP(request);

  // Check if IP is already banned
  if (isIPBanned(ip)) {
    return new NextResponse('Access denied.', { status: 403 });
  }

  const isSuspiciousPath = isPathSuspicious(pathname);
  const isSuspiciousMethod = isMethodSuspicious(method);

  // Handle suspicious activity
  if (isSuspiciousPath || isSuspiciousMethod) {
    const shouldBan = updateIPAttempts(ip);

    if (shouldBan) {
      console.log(`🔨 IP ${ip} has been banned for suspicious activity`);
      return new NextResponse('Access denied - IP banned. Please fuck off.', {
        status: 403,
      });
    }

    return new NextResponse('Not Found', { status: 404 });
  }

  return null;
};

// Call this function when you detect a 404 response
export const handle404Response = (
  request: NextRequest,
): NextResponse | null => {
  const ip = getClientIP(request);

  if (isIPBanned(ip)) {
    return new NextResponse('Access denied.', { status: 403 });
  }

  const shouldBan = update404Attempts(ip);

  if (shouldBan) {
    return new NextResponse('Access denied - IP banned for excessive 404s.', {
      status: 403,
    });
  }

  return null;
};
