import { NextFunction, Request, Response } from "express";
import { env } from "../../config/env";
import { ApiError } from "../errors";

type RateLimitOptions = {
  name: string;
  windowMs: number;
  max: number;
  key?: (req: Request) => string;
};

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

const buckets = new Map<string, Bucket>();

function clientIp(req: Request) {
  const forwarded = String(req.headers["x-forwarded-for"] || "");
  return forwarded.split(",")[0]?.trim() || req.ip || req.socket.remoteAddress || "unknown";
}

function defaultKey(req: Request) {
  return clientIp(req);
}

setInterval(() => {
  const now = Date.now();
  for (const [key, bucket] of buckets.entries()) {
    if (bucket.resetAt <= now) {
      buckets.delete(key);
    }
  }
}, 60000).unref?.();

export function rateLimit(options: RateLimitOptions) {
  const enabled = String(env.RATE_LIMIT_ENABLED).toLowerCase() !== "false";
  const windowMs = Math.max(options.windowMs || 60000, 1000);
  const max = Math.max(options.max || 60, 1);

  return (req: Request, res: Response, next: NextFunction) => {
    if (!enabled) {
      return next();
    }

    const now = Date.now();
    const key = `${options.name}:${(options.key || defaultKey)(req)}`;
    const existing = buckets.get(key);
    const bucket = existing && existing.resetAt > now ? existing : { count: 0, resetAt: now + windowMs };
    bucket.count += 1;
    buckets.set(key, bucket);

    const remaining = Math.max(max - bucket.count, 0);
    res.setHeader("RateLimit-Limit", String(max));
    res.setHeader("RateLimit-Remaining", String(remaining));
    res.setHeader("RateLimit-Reset", String(Math.ceil(bucket.resetAt / 1000)));

    if (bucket.count > max) {
      res.setHeader("Retry-After", String(Math.ceil((bucket.resetAt - now) / 1000)));
      return next(new ApiError("RATE_LIMITED", "Too many requests, please retry later", 429));
    }

    return next();
  };
}
