/**
 * Rate Limiting System
 *
 * Protects authentication endpoints from brute force attacks and DoS.
 * Primary: Convex rate_limit_windows table (distributed).
 * Fallback: In-memory sliding window (single-instance).
 *
 * Migrated from Upstash Redis to Convex (T15)
 */

import { NextRequest, NextResponse } from "next/server";
import { convexQuery, convexMutation, isConvexConfigured } from "@/lib/convex-client";

// ── In-memory fallback store ─────────────────────────────────────────

const memoryStore = new Map<string, { count: number; reset: number }>();

// Periodically clean up expired entries (every 5 minutes)
if (typeof setInterval !== "undefined") {
  setInterval(() => {
    const now = Date.now();
    for (const [key, record] of memoryStore) {
      if (now > record.reset) {
        memoryStore.delete(key);
      }
    }
  }, 5 * 60 * 1000);
}

// ── Types ────────────────────────────────────────────────────────────

interface RateLimitResult {
  success: boolean;
  limit: number;
  reset: number;
  remaining: number;
}

interface RateLimiter {
  limit: (identifier: string) => Promise<RateLimitResult>;
}

// ── Helpers ──────────────────────────────────────────────────────────

function parseWindow(window: string): number {
  const match = window.match(/(\d+)\s*(s|m|h|d)/);
  if (!match) return 60000; // default 1 minute

  const [, value, unit] = match;
  const num = parseInt(value);

  switch (unit) {
    case "s":
      return num * 1000;
    case "m":
      return num * 60 * 1000;
    case "h":
      return num * 60 * 60 * 1000;
    case "d":
      return num * 24 * 60 * 60 * 1000;
    default:
      return 60000;
  }
}

function inMemoryLimit(
  key: string,
  requests: number,
  windowMs: number
): RateLimitResult {
  const now = Date.now();
  const record = memoryStore.get(key);

  if (!record || now > record.reset) {
    memoryStore.set(key, { count: 1, reset: now + windowMs });
    return {
      success: true,
      limit: requests,
      reset: now + windowMs,
      remaining: requests - 1,
    };
  }

  if (record.count >= requests) {
    return {
      success: false,
      limit: requests,
      reset: record.reset,
      remaining: 0,
    };
  }

  record.count++;
  memoryStore.set(key, record);
  return {
    success: true,
    limit: requests,
    reset: record.reset,
    remaining: requests - record.count,
  };
}

// ── Limiter factory ──────────────────────────────────────────────────

function createLimiter(
  requests: number,
  window: string,
  prefix: string
): RateLimiter {
  const windowMs = parseWindow(window);
  const limiterType = prefix;

  return {
    limit: async (identifier: string): Promise<RateLimitResult> => {
      // Try Convex first (distributed rate limiting)
      if (isConvexConfigured()) {
        try {
          const result = await convexQuery<{
            allowed: boolean;
            remaining: number;
            resetAt: number;
          }>("rate_limiting/queries:checkRateLimit", {
            key: identifier,
            limiterType,
            limit: requests,
            windowMs,
          });

          if (result) {
            if (result.allowed) {
              // Increment the counter since request is allowed
              await convexMutation(
                "rate_limiting/mutations:incrementRateLimit",
                {
                  key: identifier,
                  limiterType,
                  windowMs,
                }
              );
            }
            return {
              success: result.allowed,
              limit: requests,
              reset: result.resetAt,
              remaining: result.remaining,
            };
          }
        } catch (err) {
          console.error(
            "[RateLimit] Convex error, falling back to in-memory:",
            err
          );
        }
      }

      // Fallback to in-memory
      return inMemoryLimit(`${limiterType}:${identifier}`, requests, windowMs);
    },
  };
}

// ── Rate limit configurations by endpoint type ──────────────────────

export const rateLimiters = {
  // OTP verification: Very strict (5 attempts per 15 minutes)
  otpVerify: createLimiter(5, "15 m", "ratelimit:otp_verify"),

  // OTP sending: Strict (5 requests per 15 minutes per IP)
  otpSend: createLimiter(5, "15 m", "ratelimit:otp_send"),

  // Login attempts: Moderate (10 attempts per 5 minutes)
  login: createLimiter(10, "5 m", "ratelimit:login"),

  // Registration: Moderate (5 registrations per hour per IP)
  register: createLimiter(5, "1 h", "ratelimit:register"),

  // General auth endpoints: Lenient (20 requests per minute)
  authGeneral: createLimiter(20, "1 m", "ratelimit:auth"),

  // Checkout creation: Moderate (10 requests per 15 minutes per IP)
  checkout: createLimiter(10, "15 m", "ratelimit:checkout"),

  // Demo booking: Moderate (5 bookings per hour per IP)
  demoBooking: createLimiter(5, "1 h", "ratelimit:demo_booking"),

  // Status page: Lenient (20 requests per minute per IP)
  status: createLimiter(20, "1 m", "ratelimit:status"),

  // Password change: Very strict (3 requests per hour per IP)
  passwordChange: createLimiter(3, "1 h", "ratelimit:password_change"),

  // Team invitation: Moderate (10 invitations per hour per IP)
  teamInvitation: createLimiter(10, "1 h", "ratelimit:team_invitation"),

  // Accept invitation: Strict (5 attempts per 15 minutes per IP)
  acceptInvitation: createLimiter(
    5,
    "15 m",
    "ratelimit:accept_invitation"
  ),

  // Validate invitation: Lenient (20 requests per 15 minutes per IP)
  validateInvitation: createLimiter(
    20,
    "15 m",
    "ratelimit:validate_invitation"
  ),

  // Webhook - Stripe: Generous (200 requests per minute per IP, Stripe sends legitimate bursts)
  webhookStripe: createLimiter(200, "1 m", "ratelimit:webhook_stripe"),

  // Webhook - General: Standard (100 requests per minute per IP)
  webhookGeneral: createLimiter(100, "1 m", "ratelimit:webhook_general"),

  // Contact form: Moderate (10 submissions per hour per IP)
  contact: createLimiter(10, "1 h", "ratelimit:contact"),
};

// ── Public helpers ───────────────────────────────────────────────────

/**
 * Get client identifier for rate limiting.
 * Uses IP address as primary identifier.
 */
export function getClientIdentifier(request: NextRequest): string {
  const forwarded = request.headers.get("x-forwarded-for");
  const realIp = request.headers.get("x-real-ip");
  const ip =
    forwarded?.split(",")[0].trim() || realIp || request.ip || "127.0.0.1";
  return ip;
}

/**
 * Apply rate limiting to a request.
 *
 * @param request - NextRequest object
 * @param limiter - Rate limiter instance
 * @param identifier - Optional custom identifier (defaults to IP)
 * @returns NextResponse with 429 status if rate limit exceeded, null if allowed
 */
export async function applyRateLimit(
  request: NextRequest,
  limiter: RateLimiter,
  identifier?: string
): Promise<NextResponse | null> {
  const clientId = identifier || getClientIdentifier(request);

  try {
    const { success, limit, reset } = await limiter.limit(clientId);

    if (!success) {
      const retryAfter = Math.ceil((reset - Date.now()) / 1000);

      return NextResponse.json(
        {
          error: "Too many requests. Please try again later.",
          retryAfter,
        },
        {
          status: 429,
          headers: {
            "X-RateLimit-Limit": limit.toString(),
            "X-RateLimit-Remaining": "0",
            "X-RateLimit-Reset": reset.toString(),
            "Retry-After": retryAfter.toString(),
          },
        }
      );
    }

    return null; // null means rate limit passed
  } catch (error) {
    console.error("[RateLimit] Error checking rate limit:", error);
    // Fail open in case of errors
    return null;
  }
}

/**
 * Rate limiter middleware wrapper for API routes.
 *
 * Usage:
 * export const POST = withRateLimit(handler, rateLimiters.login);
 */
export function withRateLimit(
  handler: (request: NextRequest) => Promise<NextResponse>,
  limiter: RateLimiter,
  identifierFn?: (request: NextRequest) => Promise<string>
) {
  return async (request: NextRequest): Promise<NextResponse> => {
    const identifier = identifierFn ? await identifierFn(request) : undefined;

    const rateLimitResponse = await applyRateLimit(request, limiter, identifier);

    if (rateLimitResponse) {
      return rateLimitResponse;
    }

    return handler(request);
  };
}

/**
 * Email-based rate limiter (for OTP operations).
 * Limits based on email address instead of IP.
 */
export async function getEmailIdentifier(
  request: NextRequest
): Promise<string> {
  try {
    const clonedRequest = request.clone();
    const body = await clonedRequest.json();
    const email = body.email || body.identifier;
    return email ? `email:${email}` : getClientIdentifier(request);
  } catch {
    return getClientIdentifier(request);
  }
}
