import "server-only";
import { createHmac } from "node:crypto";
import { isIP } from "node:net";
import { z } from "zod";
export const WINDOW_MS = 10 * 60 * 1000;
export const ATTEMPT_LIMIT = 5;
export const bucketSchema = z.object({
  count: z.number().int().nonnegative(),
  resetAt: z.number().int().nonnegative(),
});
export function clientDigest(
  request: Request,
  secret: string,
  onVercel: boolean,
) {
  const ip = onVercel
    ? request.headers.get("x-vercel-forwarded-for")?.trim()
    : "127.0.0.1";
  if (!ip || !isIP(ip)) throw new Error("Trusted client address unavailable");
  const canonical = isIP(ip) === 6 ? new URL(`http://[${ip}]`).hostname : ip;
  return createHmac("sha256", secret).update(canonical).digest("hex");
}
export function nextBucket(raw: unknown, now: number) {
  const current =
    raw === undefined
      ? { count: 0, resetAt: now + WINDOW_MS }
      : bucketSchema.parse(raw);
  const bucket =
    current.resetAt <= now ? { count: 0, resetAt: now + WINDOW_MS } : current;
  const allowed = bucket.count < ATTEMPT_LIMIT;
  return {
    allowed,
    retryAfter: Math.max(1, Math.ceil((bucket.resetAt - now) / 1000)),
    bucket: { ...bucket, count: allowed ? bucket.count + 1 : bucket.count },
  };
}
export const BUCKET_STORE_CAP = 10_000;
export function createBucketStore(cap: number = BUCKET_STORE_CAP) {
  const buckets = new Map<string, { count: number; resetAt: number }>();
  return {
    get size() {
      return buckets.size;
    },
    has(key: string) {
      return buckets.has(key);
    },
    consume(key: string, now: number) {
      if (buckets.size >= cap) {
        for (const [existing, bucket] of buckets)
          if (bucket.resetAt <= now) buckets.delete(existing);
        while (buckets.size >= cap) {
          const oldest = buckets.keys().next();
          if (oldest.done || oldest.value === undefined) break;
          buckets.delete(oldest.value);
        }
      }
      const result = nextBucket(buckets.get(key), now);
      buckets.set(key, result.bucket);
      return result;
    },
  };
}
