import { NextResponse } from "next/server";
import { getRateLimitForScopes } from "./keys.js";
import { rateLimitCache, getCacheKey } from "./cache.js";
import {
  checkRateLimit as checkRedisRateLimit,
  maybeRedisConfigured
} from "../rate-limit-redis.js";
function checkRateLimit(identifier, limit = 1e3, windowMs = 6e4) {
  const now = Date.now();
  const cacheKey = getCacheKey("rate_limit", identifier);
  const current = rateLimitCache.get(cacheKey);
  if (!current || current.resetTime < now) {
    const resetTime = now + windowMs;
    const newEntry = { count: 1, resetTime };
    rateLimitCache.set(cacheKey, newEntry, windowMs);
    return {
      allowed: true,
      remaining: limit - 1,
      resetTime,
      limit
    };
  }
  if (current.count >= limit) {
    return {
      allowed: false,
      remaining: 0,
      resetTime: current.resetTime,
      limit
    };
  }
  current.count++;
  const ttl = current.resetTime - now;
  rateLimitCache.set(cacheKey, current, ttl);
  return {
    allowed: true,
    remaining: limit - current.count,
    resetTime: current.resetTime,
    limit
  };
}
async function checkDistributedRateLimit(identifier, type = "api") {
  if (isRateLimitingDisabled()) {
    return { allowed: true, remaining: 999, resetTime: Date.now() + 6e4, limit: 999 };
  }
  if (maybeRedisConfigured()) {
    const result2 = await checkRedisRateLimit(identifier, type);
    const defaultLimits = {
      auth: 5,
      api: 100,
      strict: 10,
      read: 200,
      write: 50,
      webhook: 500
    };
    return {
      allowed: result2.success,
      remaining: result2.remaining,
      resetTime: result2.reset,
      limit: result2.limit ?? defaultLimits[type],
      retryAfter: result2.retryAfter
    };
  }
  const limits = {
    auth: { limit: 5, windowMs: 15 * 60 * 1e3 },
    // 5 requests per 15 minutes
    api: { limit: 100, windowMs: 60 * 1e3 },
    // 100 requests per minute
    strict: { limit: 10, windowMs: 60 * 60 * 1e3 },
    // 10 requests per hour
    read: { limit: 200, windowMs: 60 * 1e3 },
    // 200 requests per minute
    write: { limit: 50, windowMs: 60 * 1e3 },
    // 50 requests per minute
    webhook: { limit: 500, windowMs: 60 * 60 * 1e3 }
    // 500 requests per hour
  };
  const config = limits[type];
  const result = checkRateLimit(`${type}:${identifier}`, config.limit, config.windowMs);
  return {
    ...result,
    retryAfter: result.allowed ? void 0 : Math.ceil((result.resetTime - Date.now()) / 1e3)
  };
}
function createRateLimitErrorResponse(result) {
  return NextResponse.json(
    {
      success: false,
      error: "Rate limit exceeded",
      message: "Too many requests. Please try again later.",
      code: "RATE_LIMIT_EXCEEDED",
      meta: {
        limit: result.limit,
        remaining: result.remaining,
        resetTime: new Date(result.resetTime).toISOString(),
        retryAfter: result.retryAfter ?? Math.ceil((result.resetTime - Date.now()) / 1e3)
      }
    },
    {
      status: 429,
      headers: {
        "X-RateLimit-Limit": result.limit.toString(),
        "X-RateLimit-Remaining": result.remaining.toString(),
        "X-RateLimit-Reset": result.resetTime.toString(),
        "Retry-After": (result.retryAfter ?? Math.ceil((result.resetTime - Date.now()) / 1e3)).toString()
      }
    }
  );
}
function isDistributedRateLimitAvailable() {
  return maybeRedisConfigured();
}
function applyRateLimit(request, keyId, scopes) {
  const rateLimits = getRateLimitForScopes(scopes);
  const rateLimit = checkRateLimit(keyId, rateLimits.requests, rateLimits.windowMs);
  if (!rateLimit.allowed) {
    return NextResponse.json(
      {
        success: false,
        error: "Rate limit exceeded",
        message: "Too many requests. Please try again later.",
        code: "RATE_LIMIT_EXCEEDED",
        meta: {
          limit: rateLimit.limit,
          remaining: rateLimit.remaining,
          resetTime: new Date(rateLimit.resetTime).toISOString(),
          retryAfter: Math.ceil((rateLimit.resetTime - Date.now()) / 1e3)
        }
      },
      {
        status: 429,
        headers: {
          "X-RateLimit-Limit": rateLimit.limit.toString(),
          "X-RateLimit-Remaining": rateLimit.remaining.toString(),
          "X-RateLimit-Reset": rateLimit.resetTime.toString(),
          "Retry-After": Math.ceil((rateLimit.resetTime - Date.now()) / 1e3).toString()
        }
      }
    );
  }
  return null;
}
function addRateLimitHeaders(response, keyId, scopes) {
  const rateLimits = getRateLimitForScopes(scopes);
  const rateLimit = checkRateLimit(keyId, rateLimits.requests, rateLimits.windowMs);
  response.headers.set("X-RateLimit-Limit", rateLimit.limit.toString());
  response.headers.set("X-RateLimit-Remaining", Math.max(0, rateLimit.remaining).toString());
  response.headers.set("X-RateLimit-Reset", rateLimit.resetTime.toString());
  return response;
}
function withRateLimit(handler) {
  return async (request, ...args) => {
    try {
      const keyId = request.headers.get("x-api-key-id");
      const scopesHeader = request.headers.get("x-api-scopes");
      if (!keyId || !scopesHeader) {
        return handler(request, ...args);
      }
      let scopes = [];
      try {
        const parsed = JSON.parse(scopesHeader);
        if (Array.isArray(parsed)) {
          scopes = parsed.filter((s) => typeof s === "string");
        }
      } catch {
        console.warn("[RateLimit] Invalid scopes header format");
      }
      const rateLimitResponse = applyRateLimit(request, keyId, scopes);
      if (rateLimitResponse) {
        return rateLimitResponse;
      }
      const response = await handler(request, ...args);
      return addRateLimitHeaders(response, keyId, scopes);
    } catch (error) {
      console.error("Rate limiting error:", error);
      return handler(request, ...args);
    }
  };
}
function getRateLimitStats(keyId) {
  const cacheKey = getCacheKey("rate_limit", keyId);
  const current = rateLimitCache.get(cacheKey);
  if (!current) {
    return null;
  }
  return {
    currentUsage: current.count,
    resetTime: current.resetTime,
    isNearLimit: current.count > 800
    // 80% del límite por defecto
  };
}
function clearRateLimit(keyId) {
  const cacheKey = getCacheKey("rate_limit", keyId);
  rateLimitCache.delete(cacheKey);
}
function getAllRateLimitStats() {
  const cacheStats = rateLimitCache.getStats();
  return [{
    keyId: "cache_summary",
    count: cacheStats.active,
    resetTime: Date.now() + 6e4
    // Próximo cleanup
  }];
}
function getRateLimitCacheStats() {
  return rateLimitCache.getStats();
}
function getClientIp(request) {
  const cfIp = request.headers.get("cf-connecting-ip");
  if (cfIp) return cfIp;
  const forwardedFor = request.headers.get("x-forwarded-for");
  if (forwardedFor) {
    const ips = forwardedFor.split(",").map((ip) => ip.trim()).filter(Boolean);
    if (ips.length > 0) return ips[ips.length - 1];
  }
  const realIp = request.headers.get("x-real-ip");
  if (realIp) return realIp;
  const trueClientIp = request.headers.get("true-client-ip");
  if (trueClientIp) return trueClientIp;
  return "unknown";
}
function getUserIdentifier(request) {
  const apiKey = request.headers.get("x-api-key");
  if (apiKey) {
    return `apikey:${apiKey.substring(0, 16)}`;
  }
  return null;
}
let rateLimitDisabledWarningLogged = false;
function isRateLimitingDisabled() {
  const disabled = process.env.DISABLE_RATE_LIMITING === "true";
  if (disabled && !rateLimitDisabledWarningLogged) {
    console.warn("[RateLimit] WARNING: Rate limiting is DISABLED via DISABLE_RATE_LIMITING=true. Do not use in production!");
    rateLimitDisabledWarningLogged = true;
  }
  return disabled;
}
function withRateLimitTier(handler, tier = "api") {
  return async (request, ...args) => {
    if (isRateLimitingDisabled()) {
      return handler(request, ...args);
    }
    const userIdentifier = getUserIdentifier(request);
    const clientIp = getClientIp(request);
    const identifier = userIdentifier ? `${tier}:${userIdentifier}` : `${tier}:ip:${clientIp}`;
    const rateLimitResult = await checkDistributedRateLimit(identifier, tier);
    if (!rateLimitResult.allowed) {
      return createRateLimitErrorResponse(rateLimitResult);
    }
    const response = await handler(request, ...args);
    response.headers.set("X-RateLimit-Limit", rateLimitResult.limit.toString());
    response.headers.set("X-RateLimit-Remaining", Math.max(0, rateLimitResult.remaining).toString());
    response.headers.set("X-RateLimit-Reset", rateLimitResult.resetTime.toString());
    return response;
  };
}
export {
  addRateLimitHeaders,
  applyRateLimit,
  checkDistributedRateLimit,
  checkRateLimit,
  clearRateLimit,
  createRateLimitErrorResponse,
  getAllRateLimitStats,
  getRateLimitCacheStats,
  getRateLimitStats,
  isDistributedRateLimitAvailable,
  withRateLimit,
  withRateLimitTier
};
