/**
 * LRU cache for player records with auto-save on eviction.
 * Prevents unbounded memory growth by evicting inactive players.
 * Ported from Python player_cache.py
 */

import { Player } from '../models/Player.js';

// ============================================================================
// Types
// ============================================================================

export interface CacheStats {
  size: number;
  max_size: number;
  connected: number;
  disconnected: number;
  memory_mb: number;
  utilization: number;
}

export type OnEvictCallback = (player: Player) => void | Promise<void>;
export type SavePlayerCallback = (player: Player) => void | Promise<void>;

// ============================================================================
// Player Cache Class
// ============================================================================

/**
 * LRU cache for player records.
 *
 * Features:
 * - Automatic eviction of least-recently-used players
 * - Never evicts connected players
 * - Auto-save on eviction (optional)
 * - Memory usage tracking
 *
 * Usage:
 *   const cache = new PlayerCache(100);
 *   cache.set("user123", player);
 *   const player = cache.get("user123");
 */
export class PlayerCache {
  private cache: Map<string, Player>;
  private maxSize: number;
  private autoSave: boolean;
  private savePlayer: SavePlayerCallback | null = null;

  onEvict: OnEvictCallback | null = null;

  /**
   * Initialize player cache.
   *
   * @param maxSize - Maximum number of players to keep in memory
   * @param autoSave - Whether to auto-save evicted players
   */
  constructor(maxSize = 100, autoSave = true) {
    this.cache = new Map();
    this.maxSize = maxSize;
    this.autoSave = autoSave;

    console.log(`PlayerCache initialized: max_size=${maxSize}, auto_save=${autoSave}`);
  }

  /**
   * Set the save player callback for auto-save functionality.
   */
  setSavePlayerCallback(callback: SavePlayerCallback): void {
    this.savePlayer = callback;
  }

  /**
   * Get player from cache (marks as recently used).
   */
  get(userId: string): Player | null {
    const player = this.cache.get(userId);
    if (player) {
      // Move to end (mark as recently used)
      this.cache.delete(userId);
      this.cache.set(userId, player);
      return player;
    }
    return null;
  }

  /**
   * Add player to cache.
   */
  set(userId: string, player: Player): void {
    // If already exists, update and mark as recently used
    if (this.cache.has(userId)) {
      this.cache.delete(userId);
      this.cache.set(userId, player);
      return;
    }

    // Check if cache is full
    if (this.cache.size >= this.maxSize) {
      this.evictLru();
    }

    // Add new player
    this.cache.set(userId, player);
  }

  /**
   * Remove player from cache.
   */
  remove(userId: string): boolean {
    return this.cache.delete(userId);
  }

  /**
   * Check if player is in cache.
   */
  has(userId: string): boolean {
    return this.cache.has(userId);
  }

  /**
   * Evict least recently used player (unless connected).
   */
  private evictLru(): void {
    // Find first disconnected player (from oldest to newest)
    for (const [userId, player] of this.cache.entries()) {
      if (player.connection === 'disconnected') {
        console.log(
          `Evicting player from cache: ${userId} (${player.c?.firstname || 'unknown'})`
        );

        // Save if auto_save enabled
        if (this.autoSave && this.savePlayer) {
          Promise.resolve(this.savePlayer(player)).catch((e) => {
            console.error(`Failed to save evicted player ${userId}:`, e);
          });
        }

        // Call eviction callback
        if (this.onEvict) {
          Promise.resolve(this.onEvict(player)).catch((e) => {
            console.error(`Eviction callback error for ${userId}:`, e);
          });
        }

        // Remove from cache
        this.cache.delete(userId);
        return;
      }
    }

    // If all players are connected, log warning but don't evict
    console.warn(`PlayerCache full (${this.maxSize}) with all connected players`);
  }

  /**
   * Get current cache size.
   */
  size(): number {
    return this.cache.size;
  }

  /**
   * Check if cache is full.
   */
  isFull(): boolean {
    return this.cache.size >= this.maxSize;
  }

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

  /**
   * Get all cached players.
   */
  values(): Player[] {
    return Array.from(this.cache.values());
  }

  /**
   * Iterate over all players.
   */
  forEach(callback: (player: Player, userId: string) => void): void {
    this.cache.forEach((player, userId) => callback(player, userId));
  }

  /**
   * Estimate memory usage of cached players.
   */
  estimateMemoryMb(): number {
    if (this.cache.size === 0) {
      return 0;
    }

    // Rough estimate: ~50KB per player including nested objects
    const estimatedBytesPerPlayer = 50 * 1024;
    const estimatedTotal = estimatedBytesPerPlayer * this.cache.size;

    return estimatedTotal / (1024 * 1024); // Convert to MB
  }

  /**
   * Get cache statistics.
   */
  getStats(): CacheStats {
    let connected = 0;
    for (const player of this.cache.values()) {
      if (player.connection === 'connected') {
        connected++;
      }
    }
    const disconnected = this.cache.size - connected;

    return {
      size: this.cache.size,
      max_size: this.maxSize,
      connected,
      disconnected,
      memory_mb: this.estimateMemoryMb(),
      utilization: (this.cache.size / this.maxSize) * 100,
    };
  }

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

  /**
   * Evict all disconnected players.
   */
  evictDisconnected(): number {
    let evicted = 0;
    for (const [userId, player] of this.cache.entries()) {
      if (player.connection === 'disconnected') {
        // Save if auto_save enabled
        if (this.autoSave && this.savePlayer) {
          Promise.resolve(this.savePlayer(player)).catch((e) => {
            console.error(`Failed to save evicted player ${userId}:`, e);
          });
        }

        this.cache.delete(userId);
        evicted++;
      }
    }
    return evicted;
  }
}

// ============================================================================
// Global Instance
// ============================================================================

let playerCache: PlayerCache | null = null;

export function getPlayerCache(maxSize = 100): PlayerCache {
  if (!playerCache) {
    playerCache = new PlayerCache(maxSize);
  }
  return playerCache;
}

// ============================================================================
// Export
// ============================================================================

export const cache = {
  PlayerCache,
  getPlayerCache,
};
