import { ApiV1Error } from "@/lib/api-v1/errors";

type Bucket = {
  count: number;
  resetAt: number;
};

type Store = Map<string, Bucket>;

export type RateLimitPolicy = {
  namespace: string;
  windowMs: number;
  maxPerIp: number;
  maxPerUser?: number;
};

export type RateLimitResult = {
  headers: Record<string, string>;
  remaining: number;
  resetAt: number;
};

declare global {
  // eslint-disable-next-line no-var
  var __ktaloguApiV1RateLimit: Store | undefined;
}

function getStore(): Store {
  if (!globalThis.__ktaloguApiV1RateLimit) {
    globalThis.__ktaloguApiV1RateLimit = new Map<string, Bucket>();
  }
  return globalThis.__ktaloguApiV1RateLimit;
}

export function applyRateLimit(key: string, limit: number) {
  return applyRateLimitWindow(key, limit, 60_000);
}

function applyRateLimitWindow(key: string, limit: number, windowMs: number) {
  const now = Date.now();
  const store = getStore();
  const current = store.get(key);

  if (!current || current.resetAt <= now) {
    const created = {
      count: 1,
      resetAt: now + windowMs,
    };
    store.set(key, created);
    return {
      remaining: Math.max(0, limit - 1),
      resetAt: created.resetAt,
    };
  }

  if (current.count >= limit) {
    throw new ApiV1Error(429, "rate_limited", "Limite de requisicoes excedido.", {
      limit,
      reset_at: current.resetAt,
    });
  }

  current.count += 1;
  store.set(key, current);
  return {
    remaining: Math.max(0, limit - current.count),
    resetAt: current.resetAt,
  };
}

export function getRateLimitKey(request: Request, namespace: string) {
  const ip = getRequestIp(request);
  const userAgent = request.headers.get("user-agent") || "unknown";
  return `${namespace}:${ip}:${userAgent}`;
}

export function getRequestIp(request: Request) {
  const forwardedFor = request.headers.get("x-forwarded-for") || "";
  const realIp = request.headers.get("x-real-ip") || "";
  return forwardedFor.split(",")[0]?.trim() || realIp || "local";
}

export function applyApiRateLimit(request: Request, policy: RateLimitPolicy, userId?: string): RateLimitResult {
  const ip = getRequestIp(request);
  const ipResult = applyRateLimitWindow(`ip:${policy.namespace}:${ip}`, policy.maxPerIp, policy.windowMs);

  let userResult = ipResult;
  if (userId && policy.maxPerUser) {
    userResult = applyRateLimitWindow(`user:${policy.namespace}:${userId}`, policy.maxPerUser, policy.windowMs);
  }

  const remaining = Math.min(ipResult.remaining, userResult.remaining);
  const resetAt = Math.max(ipResult.resetAt, userResult.resetAt);

  return {
    remaining,
    resetAt,
    headers: {
      "X-RateLimit-Limit-IP": String(policy.maxPerIp),
      "X-RateLimit-Limit-User": String(policy.maxPerUser ?? policy.maxPerIp),
      "X-RateLimit-Remaining": String(remaining),
      "X-RateLimit-Reset": String(Math.floor(resetAt / 1000)),
      "Retry-After": String(Math.max(0, Math.ceil((resetAt - Date.now()) / 1000))),
    },
  };
}
