/**
 * @bytelyst/field-encrypt — DEK cache
 *
 * In-memory LRU cache with TTL for unwrapped DEKs.
 * Avoids repeated AKV round-trips on every encrypt/decrypt.
 */

interface CacheEntry {
  key: Buffer;
  expiresAt: number;
}

export class DekCache {
  private readonly cache = new Map<string, CacheEntry>();
  private readonly ttlMs: number;
  private readonly maxSize: number;

  constructor(ttlMs: number = 15 * 60 * 1000, maxSize: number = 1000) {
    this.ttlMs = ttlMs;
    this.maxSize = maxSize;
  }

  /** Get an unwrapped DEK from cache. Returns null on miss or expiry. */
  get(dekId: string): Buffer | null {
    const entry = this.cache.get(dekId);
    if (!entry) return null;

    if (Date.now() > entry.expiresAt) {
      this.cache.delete(dekId);
      return null;
    }

    // Move to end (LRU refresh)
    this.cache.delete(dekId);
    this.cache.set(dekId, entry);
    return entry.key;
  }

  /** Store an unwrapped DEK in cache. */
  set(dekId: string, key: Buffer): void {
    // Evict oldest if at max size
    if (this.cache.size >= this.maxSize && !this.cache.has(dekId)) {
      const oldestKey = this.cache.keys().next().value;
      if (oldestKey !== undefined) {
        this.cache.delete(oldestKey);
      }
    }

    this.cache.set(dekId, {
      key,
      expiresAt: Date.now() + this.ttlMs,
    });
  }

  /** Invalidate a specific DEK (e.g., after rotation). */
  invalidate(dekId: string): void {
    this.cache.delete(dekId);
  }

  /** Clear all cached DEKs. */
  clear(): void {
    this.cache.clear();
  }

  /** Current cache size. */
  get size(): number {
    return this.cache.size;
  }

  /** Cache hit rate stats. */
  private _hits = 0;
  private _misses = 0;

  /** Record a cache hit (called internally). */
  recordHit(): void {
    this._hits++;
  }
  /** Record a cache miss (called internally). */
  recordMiss(): void {
    this._misses++;
  }

  /** Get hit rate as a percentage (0-100). */
  get hitRate(): number {
    const total = this._hits + this._misses;
    return total === 0 ? 0 : Math.round((this._hits / total) * 100);
  }

  /** Reset stats counters. */
  resetStats(): void {
    this._hits = 0;
    this._misses = 0;
  }
}
