/**
 * Rate-limit bucket queries.
 *
 * Token-bucket state lives in `rate_limit_buckets`. Active buckets refill on
 * each request hit; abandoned buckets (no longer hit) accumulate forever
 * unless GC'd. A daily cron calls `purgeStale` to delete buckets whose
 * `refilledAt` is older than the retention window (7 days).
 */

import { lt, sql } from 'drizzle-orm';
import type { DrizzleClient } from '../client.js';
import { rateLimitBuckets } from '../schema.js';

/** Retention window for stale rate-limit buckets (7 days). */
export const STALE_BUCKET_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;

export async function consumeRateLimit(
  db: DrizzleClient,
  key: string,
  now: Date,
  windowEnd: Date,
): Promise<{ count: number; refilledAt: Date } | null> {
  const [bucket] = await db
    .insert(rateLimitBuckets)
    .values({ key, count: 1, windowStartAt: now, refilledAt: windowEnd })
    .onConflictDoUpdate({
      target: rateLimitBuckets.key,
      set: {
        count: sql`CASE WHEN ${rateLimitBuckets.refilledAt} <= ${now.toISOString()} THEN 1 ELSE ${rateLimitBuckets.count} + 1 END`,
        windowStartAt: sql`CASE WHEN ${rateLimitBuckets.refilledAt} <= ${now.toISOString()} THEN ${now.toISOString()} ELSE ${rateLimitBuckets.windowStartAt} END`,
        refilledAt: sql`CASE WHEN ${rateLimitBuckets.refilledAt} <= ${now.toISOString()} THEN ${windowEnd.toISOString()} ELSE ${rateLimitBuckets.refilledAt} END`,
      },
    })
    .returning({ count: rateLimitBuckets.count, refilledAt: rateLimitBuckets.refilledAt });
  return bucket ?? null;
}

/**
 * Delete rate-limit buckets whose last refill is older than the retention
 * window. Returns the number of rows deleted.
 */
export async function purgeStale(db: DrizzleClient, now: Date = new Date()): Promise<number> {
  const cutoff = new Date(now.getTime() - STALE_BUCKET_RETENTION_MS);
  const result = await db
    .delete(rateLimitBuckets)
    .where(lt(rateLimitBuckets.refilledAt, cutoff))
    .returning({ key: rateLimitBuckets.key });
  return result.length;
}
