/**
 * Usage-counter query helpers — foundation-auth-rbac.
 *  - incrementCounter: atomic INSERT ... ON CONFLICT DO UPDATE (single statement).
 *  - checkCounterLimit: reads current count, throws QuotaExceededError at the cap.
 *  - QuotaExceededError carries key/limit/current for the 402 response upstream.
 */
import { and, eq, sql } from 'drizzle-orm'
import type { TenantId } from '@zync/types'
import type { Db } from '../client'
import { usageCounters } from '../schema'

export class QuotaExceededError extends Error {
  readonly key: string
  readonly limit: number
  readonly current: number

  constructor(key: string, limit: number, current: number) {
    super(`Quota exceeded for '${key}': ${current} >= ${limit}`)
    this.name = 'QuotaExceededError'
    this.key = key
    this.limit = limit
    this.current = current
  }
}

/**
 * Atomically increment a counter and return the NEW count. Concurrent calls do
 * not lose increments — this is one SQL statement
 * (INSERT ... ON CONFLICT (tenant_id, counter_key, period) DO UPDATE
 *  SET count = usage_counters.count + 1 RETURNING count).
 */
export async function incrementCounter(
  db: Db,
  tenantId: TenantId,
  key: string,
  period: string,
): Promise<number> {
  const [row] = await db
    .insert(usageCounters)
    .values({ tenantId, counterKey: key, period, count: 1 })
    .onConflictDoUpdate({
      target: [usageCounters.tenantId, usageCounters.counterKey, usageCounters.period],
      set: { count: sql`${usageCounters.count} + 1` },
    })
    .returning({ count: usageCounters.count })
  return row?.count ?? 0
}

/**
 * Read the current count for (tenant, key, period) and throw QuotaExceededError
 * if it has reached `limit` (count >= limit). Missing row = current 0.
 * Call this BEFORE processing a metered action, then incrementCounter on success.
 */
export async function checkCounterLimit(
  db: Db,
  tenantId: TenantId,
  key: string,
  period: string,
  limit: number,
): Promise<void> {
  if (!Number.isFinite(limit)) return // Infinity = unlimited
  const [row] = await db
    .select({ count: usageCounters.count })
    .from(usageCounters)
    .where(
      and(
        eq(usageCounters.tenantId, tenantId),
        eq(usageCounters.counterKey, key),
        eq(usageCounters.period, period),
      ),
    )
    .limit(1)
  const current = row?.count ?? 0
  if (current >= limit) throw new QuotaExceededError(key, limit, current)
}

// zync-subscription: get current counter value without throwing
export async function getCounterValue(
  db: Db,
  tenantId: string,
  key: string,
  period: string,
): Promise<number> {
  const [row] = await db
    .select({ count: usageCounters.count })
    .from(usageCounters)
    .where(
      and(
        eq(usageCounters.tenantId, tenantId),
        eq(usageCounters.counterKey, key),
        eq(usageCounters.period, period),
      ),
    )
    .limit(1)
  return row?.count ?? 0
}

// zync-subscription: decrement counter (floors at 0)
export async function decrementCounter(
  db: Db,
  tenantId: string,
  key: string,
  period: string,
  delta: number,
): Promise<number> {
  const [row] = await db
    .insert(usageCounters)
    .values({ tenantId, counterKey: key, period, count: 0 })
    .onConflictDoUpdate({
      target: [usageCounters.tenantId, usageCounters.counterKey, usageCounters.period],
      set: { count: sql`GREATEST(${usageCounters.count} - ${delta}, 0)` },
    })
    .returning({ count: usageCounters.count })
  return row?.count ?? 0
}
