import type { RateLimiter } from './rate-limiter-types'
import { DEFAULT_RATE_LIMIT_CAPACITY, DEFAULT_RATE_LIMIT_WINDOW_MS, STALE_BUCKET_MULTIPLIER } from '../constants/resilience'

/**
 * Token bucket state
 */
interface TokenBucket {
  tokens: number
  lastRefill: number
}

/**
 * In-memory token bucket rate limiter
 *
 * Implements token bucket algorithm for rate limiting integration API calls.
 * Default: 60 requests per minute per organization per integration.
 *
 * Pattern: Stateful class (justified - manages rate limit state)
 *
 * @example
 * const limiter = new InMemoryRateLimiter(60, 60000) // 60 req/min
 * const allowed = await limiter.allow('org123:gmail')
 * if (!allowed) {
 *   throw new ToolingError('rate_limit_exceeded', 'Rate limit exceeded')
 * }
 */
export class InMemoryRateLimiter implements RateLimiter {
  private buckets = new Map<string, TokenBucket>()
  private lastEviction = Date.now()

  /**
   * @param capacity - Maximum tokens (requests) allowed
   * @param refillIntervalMs - Refill interval in milliseconds
   */
  constructor(
    private readonly capacity: number = DEFAULT_RATE_LIMIT_CAPACITY,
    private readonly refillIntervalMs: number = DEFAULT_RATE_LIMIT_WINDOW_MS
  ) {}

  /**
   * Check if request is allowed and consume token if available
   */
  async allow(key: string): Promise<boolean> {
    const bucket = this.getOrCreateBucket(key)
    this.refillBucket(bucket)

    if (bucket.tokens > 0) {
      bucket.tokens -= 1
      return true
    }

    return false
  }

  /**
   * Get remaining tokens for key
   */
  async remaining(key: string): Promise<number> {
    const bucket = this.getOrCreateBucket(key)
    this.refillBucket(bucket)
    return Math.floor(bucket.tokens)
  }

  /**
   * Reset rate limit for key (removes bucket)
   */
  async reset(key: string): Promise<void> {
    this.buckets.delete(key)
  }

  /**
   * Get or create token bucket for key
   * Lazily evicts stale buckets to prevent memory leaks
   */
  private getOrCreateBucket(key: string): TokenBucket {
    this.evictStaleBuckets()

    let bucket = this.buckets.get(key)
    if (!bucket) {
      bucket = {
        tokens: this.capacity,
        lastRefill: Date.now()
      }
      this.buckets.set(key, bucket)
    }
    return bucket
  }

  /**
   * Evict buckets that haven't been accessed in 5x the refill interval
   * Runs at most once per refill interval to avoid overhead
   */
  private evictStaleBuckets(): void {
    const now = Date.now()
    if (now - this.lastEviction < this.refillIntervalMs) return
    this.lastEviction = now

    const staleThreshold = this.refillIntervalMs * STALE_BUCKET_MULTIPLIER
    for (const [key, bucket] of this.buckets) {
      if (now - bucket.lastRefill > staleThreshold) {
        this.buckets.delete(key)
      }
    }
  }

  /**
   * Refill bucket based on time elapsed
   * Uses linear refill: tokens added = (elapsed / interval) * capacity
   */
  private refillBucket(bucket: TokenBucket): void {
    const now = Date.now()
    const elapsed = now - bucket.lastRefill

    if (elapsed >= this.refillIntervalMs) {
      // Full refill if interval passed
      bucket.tokens = this.capacity
      bucket.lastRefill = now
    } else {
      // Partial refill based on elapsed time
      const tokensToAdd = (elapsed / this.refillIntervalMs) * this.capacity
      bucket.tokens = Math.min(this.capacity, bucket.tokens + tokensToAdd)
      bucket.lastRefill = now
    }
  }

  /**
   * Clear all buckets (for testing)
   */
  clearAll(): void {
    this.buckets.clear()
  }
}

/**
 * Create default rate limiter instance
 * 60 requests per minute per organization per integration
 */
export function createRateLimiter(): RateLimiter {
  return new InMemoryRateLimiter(DEFAULT_RATE_LIMIT_CAPACITY, DEFAULT_RATE_LIMIT_WINDOW_MS)
}
