/**
 * Per-key rate limiting middleware — zync-public-api (tenant-public-api wave-11 leaf-D).
 * 100 requests per minute per API key, enforced via RATELIMIT_KV.
 *
 * Fixed (S10-i2-001): every put includes expirationTtl so the window always expires.
 *   We store {count, windowEnd} as JSON; windowEnd lets us compute the remaining TTL
 *   on subsequent puts so the window is anchored to the first request, not reset.
 * Fixed (S10-i2-002): key is rl:{tenantId}:{authKind}:{uniqueId} — for OAuth tokens
 *   uniqueId is clientId (not keyId which is undefined for OAuth).
 */
import type { Context, Next } from 'hono'
import { errRateLimited } from '@zync/public-api'
import type { Env } from '../env'
import type { ResolvedAuth } from './auth'

const MAX_REQUESTS = 100
const WINDOW_SECONDS = 60

interface RlEntry {
  count: number
  windowEnd: number // unix ms
}

export async function perKeyRateLimit(c: Context<{ Bindings: Env }>, next: Next): Promise<Response | void> {
  const auth = c.get('apiKey' as never) as ResolvedAuth | undefined
  if (!auth) {
    // Should not reach here if apiKeyAuth ran first
    return next()
  }

  // Build a unique, non-undefined KV key per caller (S10-i2-002)
  const uniqueId = auth.authKind === 'api_key' ? auth.keyId : auth.clientId
  const kvKey = `rl:${auth.tenantId}:${auth.authKind}:${uniqueId}`

  const raw = await c.env.RATELIMIT_KV.get(kvKey)
  const now = Date.now()

  let entry: RlEntry
  if (raw) {
    entry = JSON.parse(raw) as RlEntry
    // If the window has somehow passed (clock drift / KV lag), treat as new window
    if (entry.windowEnd <= now) {
      entry = { count: 0, windowEnd: now + WINDOW_SECONDS * 1000 }
    }
  } else {
    entry = { count: 0, windowEnd: now + WINDOW_SECONDS * 1000 }
  }

  if (entry.count >= MAX_REQUESTS) {
    return errRateLimited(WINDOW_SECONDS)
  }

  // Always write with expirationTtl anchored to the window end (S10-i2-001)
  const remainingTtl = Math.max(1, Math.ceil((entry.windowEnd - now) / 1000))
  entry.count += 1
  await c.env.RATELIMIT_KV.put(kvKey, JSON.stringify(entry), { expirationTtl: remainingTtl })

  await next()
}
