const crypto = require("crypto");
const { sendError } = require("../utils/http");

function normalizeIp(req) {
  return String(req.socket?.remoteAddress || req.ip || "unknown").trim();
}

function findUserIdentifier(req) {
  const candidates = [
    req.params?.username,
    req.params?.gamemode,
    req.body?.username,
    req.body?.ingame_name,
    req.query?.q,
  ];

  for (const candidate of candidates) {
    if (candidate == null) {
      continue;
    }

    const normalized = String(candidate).trim().toLowerCase();
    if (normalized) {
      return normalized.slice(0, 64);
    }
  }

  return "anonymous";
}

function buildBucketKey(raw) {
  return crypto.createHash("sha256").update(String(raw)).digest("hex");
}

function createRateLimiter({ name, windowMs, max, keyFn }) {
  const buckets = new Map();

  function cleanup(now) {
    for (const [key, bucket] of buckets.entries()) {
      if (bucket.resetAt <= now) {
        buckets.delete(key);
      }
    }
  }

  return function rateLimitMiddleware(req, res, next) {
    const now = Date.now();
    const rawKey = keyFn(req);
    const key = buildBucketKey(`${name}:${rawKey}`);
    const current = buckets.get(key);

    if (!current || current.resetAt <= now) {
      buckets.set(key, { count: 1, resetAt: now + windowMs });

      if (buckets.size > 5000) {
        cleanup(now);
      }

      return next();
    }

    if (current.count >= max) {
      const retryAfterSeconds = Math.max(Math.ceil((current.resetAt - now) / 1000), 1);
      res.setHeader("Retry-After", String(retryAfterSeconds));
      res.setHeader("X-RateLimit-Limit", String(max));
      res.setHeader("X-RateLimit-Remaining", "0");
      res.setHeader("X-RateLimit-Reset", String(Math.ceil(current.resetAt / 1000)));

      return sendError(
        res,
        429,
        "RATE_LIMITED",
        "Too many requests. Please retry later.",
        {
          limiter: name,
          retryAfterSeconds,
        }
      );
    }

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

    return next();
  };
}

function createApiRateLimiters({ ipMax = 60, userMax = 60, windowMs = 60 * 1000 }) {
  const byIp = createRateLimiter({
    name: "ip",
    windowMs,
    max: ipMax,
    keyFn: (req) => normalizeIp(req),
  });

  const byUser = createRateLimiter({
    name: "user",
    windowMs,
    max: userMax,
    keyFn: (req) => `${normalizeIp(req)}:${findUserIdentifier(req)}`,
  });

  return [byIp, byUser];
}

module.exports = {
  createApiRateLimiters,
};
