import { ExecutionContext, Injectable } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';
import { createHash } from 'node:crypto';

/**
 * Per-credential rate limiter.
 *
 * The default ThrottlerGuard tracks by IP, which is the wrong key for a
 * multi-tenant API: one tenant behind a NAT can throttle another. We key
 * by the Bearer token instead — same key → same bucket, different keys →
 * different buckets. We hash the token (truncated SHA-256) so the tracker
 * is bounded length and never embeds the secret in metrics or memory dumps.
 *
 * Unauthenticated requests still fall through to IP. They never get past
 * the ApiKeyGuard anyway, but bucketing them by IP prevents an unauth
 * flood from spending one tenant's quota.
 *
 * Per-tier overrides: today every credential gets the same default
 * limit. When @inite/auth surfaces tier in the JWT (scopes or claim), the
 * matching path is `req.brainAuth.tier` → distinct ThrottlerOptions array
 * + per-tier @Throttle() decorators on the routes that need it.
 */
@Injectable()
export class TenantThrottlerGuard extends ThrottlerGuard {
  /**
   * Global off-switch. Per-route @Throttle() decorators hardcode their
   * own limits, which the THROTTLE_*_LIMIT env knobs can't override, so
   * e2e suites that legitimately fire >N expensive calls would 429.
   * THROTTLE_DISABLED=1 (set only by the test fixture) skips throttling
   * entirely. Never set in production.
   */
  protected async shouldSkip(context: ExecutionContext): Promise<boolean> {
    // Inert in production even if the flag leaks into a deploy env —
    // validateEnv also hard-errors on it at boot. Defense in depth: a
    // stray THROTTLE_DISABLED must never silently drop the expensive
    // OpenAI-budget caps in prod.
    if (
      process.env.THROTTLE_DISABLED === '1' &&
      process.env.NODE_ENV !== 'production'
    ) {
      return true;
    }
    return super.shouldSkip(context);
  }

  protected async getTracker(req: Record<string, unknown>): Promise<string> {
    const headers = (req.headers as Record<string, string> | undefined) ?? {};
    const auth = headers.authorization;
    if (auth && auth.toLowerCase().startsWith('bearer ')) {
      const token = auth.slice(7).trim();
      const digest = createHash('sha256').update(token).digest('hex').slice(0, 32);
      return `k:${digest}`;
    }
    const ip = (req.ip as string | undefined) ?? 'unknown';
    return `ip:${ip}`;
  }
}
