/**
 * Simple in-memory cache with TTL support
 *
 * Usage:
 * ```typescript
 * const cache = new Cache<User[]>(5 * 60 * 1000); // 5 minutes TTL
 *
 * const users = await cache.getOrSet('all-users', async () => {
 *   return await db.getAllUsers();
 * });
 * ```
 */

import log from "../logger";

export interface CacheEntry<T> {
  value: T;
  expiresAt: number;
  createdAt: number;
}

export interface CacheStats {
  hits: number;
  misses: number;
  size: number;
  hitRate: number;
}

/**
 * Simple in-memory cache with TTL (Time To Live)
 */
export class Cache<T = any> {
  private store: Map<string, CacheEntry<T>> = new Map();
  private hits = 0;
  private misses = 0;
  private defaultTtl: number;

  /**
   * Create a new cache instance
   *
   * @param defaultTtl - Default time-to-live in milliseconds (default: 5 minutes)
   */
  constructor(defaultTtl: number = 5 * 60 * 1000) {
    this.defaultTtl = defaultTtl;
  }

  /**
   * Get a value from cache
   *
   * @param key - Cache key
   * @returns Cached value or null if expired/not found
   */
  get(key: string): T | null {
    const entry = this.store.get(key);

    if (!entry) {
      this.misses++;
      return null;
    }

    // Check if expired
    if (Date.now() > entry.expiresAt) {
      this.store.delete(key);
      this.misses++;
      log.debug("Cache expired", { key });
      return null;
    }

    this.hits++;
    log.debug("Cache hit", { key });
    return entry.value;
  }

  /**
   * Set a value in cache
   *
   * @param key - Cache key
   * @param value - Value to cache
   * @param ttl - Time-to-live in milliseconds (optional, uses default if not provided)
   */
  set(key: string, value: T, ttl?: number): void {
    const expiresAt = Date.now() + (ttl ?? this.defaultTtl);
    this.store.set(key, {
      value,
      expiresAt,
      createdAt: Date.now(),
    });
    log.debug("Cache set", { key, ttl: ttl ?? this.defaultTtl });
  }

  /**
   * Get a value from cache, or compute and cache it if not found
   *
   * @param key - Cache key
   * @param fn - Function to compute value if not in cache
   * @param ttl - Time-to-live in milliseconds (optional)
   * @returns Cached or computed value
   */
  async getOrSet(key: string, fn: () => Promise<T>, ttl?: number): Promise<T> {
    const cached = this.get(key);
    if (cached !== null) {
      return cached;
    }

    log.debug("Cache miss, computing value", { key });
    const value = await fn();
    this.set(key, value, ttl);
    return value;
  }

  /**
   * Delete a value from cache
   *
   * @param key - Cache key
   * @returns True if deleted, false if not found
   */
  delete(key: string): boolean {
    const deleted = this.store.delete(key);
    log.debug("Cache delete", { key, deleted });
    return deleted;
  }

  /**
   * Clear all entries from cache
   */
  clear(): void {
    const size = this.store.size;
    this.store.clear();
    this.hits = 0;
    this.misses = 0;
    log.info("Cache cleared", { entriesCleared: size });
  }

  /**
   * Get cache statistics
   */
  getStats(): CacheStats {
    const total = this.hits + this.misses;
    return {
      hits: this.hits,
      misses: this.misses,
      size: this.store.size,
      hitRate: total > 0 ? this.hits / total : 0,
    };
  }

  /**
   * Remove expired entries from cache
   */
  cleanup(): number {
    const now = Date.now();
    let removed = 0;

    for (const [key, entry] of Array.from(this.store.entries())) {
      if (now > entry.expiresAt) {
        this.store.delete(key);
        removed++;
      }
    }

    if (removed > 0) {
      log.info("Cache cleanup completed", { entriesRemoved: removed });
    }

    return removed;
  }

  /**
   * Check if a key exists in cache (and is not expired)
   */
  has(key: string): boolean {
    const entry = this.store.get(key);
    if (!entry) return false;

    if (Date.now() > entry.expiresAt) {
      this.store.delete(key);
      return false;
    }

    return true;
  }

  /**
   * Get all keys in cache
   */
  keys(): string[] {
    return Array.from(this.store.keys());
  }

  /**
   * Get cache size (number of entries)
   */
  size(): number {
    return this.store.size;
  }
}

/**
 * Global cache instances for common use cases
 */

// User data cache (5 minutes)
export const userCache = new Cache<any>(5 * 60 * 1000);

// Fitness goals cache (2 minutes)
export const fitnessGoalsCache = new Cache<any>(2 * 60 * 1000);

// Recommendations cache (3 minutes)
export const recommendationsCache = new Cache<any>(3 * 60 * 1000);

// Dashboard stats cache (1 minute - frequently changing data)
export const dashboardStatsCache = new Cache<any>(1 * 60 * 1000);

/**
 * Setup automatic cache cleanup (run every 5 minutes)
 */
export function setupCacheCleanup(): NodeJS.Timeout {
  const interval = setInterval(
    () => {
      log.debug("Running scheduled cache cleanup");
      userCache.cleanup();
      fitnessGoalsCache.cleanup();
      recommendationsCache.cleanup();
      dashboardStatsCache.cleanup();
    },
    5 * 60 * 1000,
  ); // Every 5 minutes

  return interval;
}

/**
 * Clear all caches
 */
export function clearAllCaches(): void {
  userCache.clear();
  fitnessGoalsCache.clear();
  recommendationsCache.clear();
  dashboardStatsCache.clear();
  log.info("All caches cleared");
}

/**
 * Get stats for all caches
 */
export function getAllCacheStats(): Record<string, CacheStats> {
  return {
    users: userCache.getStats(),
    fitnessGoals: fitnessGoalsCache.getStats(),
    recommendations: recommendationsCache.getStats(),
    dashboardStats: dashboardStatsCache.getStats(),
  };
}
