const requestLog = new Map<string, number[]>();

export class ApiRequestError extends Error {
  status: number;

  constructor(message: string, status = 400) {
    super(message);
    this.status = status;
  }
}

export async function readJson<T>(request: Request, maxBytes: number): Promise<T> {
  const declaredLength = Number(request.headers.get("content-length") || 0);
  if (declaredLength > maxBytes) throw new ApiRequestError("Request body is too large", 413);
  if (!request.body) throw new ApiRequestError("Request body is required");
  const reader = request.body.getReader();
  const chunks: Uint8Array[] = [];
  let total = 0;
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      total += value.byteLength;
      if (total > maxBytes) throw new ApiRequestError("Request body is too large", 413);
      chunks.push(value);
    }
  } finally {
    reader.releaseLock();
  }
  try {
    const body = new Uint8Array(total);
    let offset = 0;
    for (const chunk of chunks) {
      body.set(chunk, offset);
      offset += chunk.byteLength;
    }
    return JSON.parse(new TextDecoder().decode(body)) as T;
  } catch {
    throw new ApiRequestError("Invalid JSON");
  }
}

export function checkRateLimit(request: Request, scope: string, limit: number, windowMs = 60_000) {
  const forwarded = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim();
  const key = `${scope}:${request.headers.get("x-real-ip") || forwarded || "unknown"}`;
  const now = Date.now();
  const recent = (requestLog.get(key) || []).filter((timestamp) => now - timestamp < windowMs);
  if (recent.length >= limit) throw new ApiRequestError("Too many requests", 429);
  recent.push(now);
  requestLog.set(key, recent);
  if (requestLog.size > 1000) {
    for (const [storedKey, timestamps] of requestLog) {
      if (!timestamps.some((timestamp) => now - timestamp < windowMs)) requestLog.delete(storedKey);
    }
  }
}
