import { EventEmitter } from 'events';

export interface LeaderboardEntry {
  playerId: string;
  playerName: string;
  rank: number;
  score: number;
  previousRank?: number;
  scoreChange?: number;
  lastUpdated: Date;
}

export interface RankChangeEvent {
  playerId: string;
  playerName: string;
  oldRank: number;
  newRank: number;
  scoreChange: number;
  timestamp: Date;
}

export interface LeaderboardSnapshot {
  timestamp: Date;
  entries: LeaderboardEntry[];
  totalPlayers: number;
}

/**
 * Live Leaderboard Service
 * Manages real-time leaderboard updates with WebSocket support
 */
export class LiveLeaderboardService extends EventEmitter {
  private leaderboards: Map<string, LeaderboardEntry[]> = new Map(); // leaderboardId -> entries
  private leaderboardSnapshots: Map<string, LeaderboardSnapshot[]> = new Map(); // leaderboardId -> snapshots
  private rankChangeHistory: RankChangeEvent[] = [];
  private subscribers: Map<string, Set<string>> = new Map(); // leaderboardId -> playerIds
  private updateIntervals: Map<string, NodeJS.Timer> = new Map();

  constructor() {
    super();
    console.log('[LiveLeaderboard] Service initialized');
  }

  /**
   * Initialize leaderboard
   */
  initializeLeaderboard(leaderboardId: string, entries: LeaderboardEntry[] = []): void {
    if (!this.leaderboards.has(leaderboardId)) {
      this.leaderboards.set(leaderboardId, entries);
      this.leaderboardSnapshots.set(leaderboardId, []);
      this.subscribers.set(leaderboardId, new Set());

      this.emit('leaderboardInitialized', { leaderboardId, entryCount: entries.length });
      console.log(`[LiveLeaderboard] Leaderboard initialized: ${leaderboardId}`);
    }
  }

  /**
   * Subscribe to leaderboard updates
   */
  subscribe(leaderboardId: string, playerId: string): void {
    if (!this.subscribers.has(leaderboardId)) {
      this.subscribers.set(leaderboardId, new Set());
    }

    this.subscribers.get(leaderboardId)!.add(playerId);

    this.emit('playerSubscribed', { leaderboardId, playerId });
    console.log(`[LiveLeaderboard] Player ${playerId} subscribed to ${leaderboardId}`);
  }

  /**
   * Unsubscribe from leaderboard updates
   */
  unsubscribe(leaderboardId: string, playerId: string): void {
    const subscribers = this.subscribers.get(leaderboardId);
    if (subscribers) {
      subscribers.delete(playerId);
    }

    this.emit('playerUnsubscribed', { leaderboardId, playerId });
  }

  /**
   * Update player score
   */
  updatePlayerScore(leaderboardId: string, playerId: string, newScore: number, playerName: string = 'Player'): RankChangeEvent | null {
    const entries = this.leaderboards.get(leaderboardId);
    if (!entries) return null;

    let entry = entries.find((e) => e.playerId === playerId);

    if (!entry) {
      entry = {
        playerId,
        playerName,
        rank: entries.length + 1,
        score: newScore,
        lastUpdated: new Date(),
      };
      entries.push(entry);
    } else {
      entry.previousRank = entry.rank;
      entry.scoreChange = newScore - entry.score;
      entry.score = newScore;
      entry.lastUpdated = new Date();
    }

    // Re-rank
    this.updateRanks(leaderboardId);

    // Emit rank change if applicable
    let rankChangeEvent: RankChangeEvent | null = null;
    if (entry.previousRank !== undefined && entry.previousRank !== entry.rank) {
      rankChangeEvent = {
        playerId,
        playerName,
        oldRank: entry.previousRank,
        newRank: entry.rank,
        scoreChange: entry.scoreChange || 0,
        timestamp: new Date(),
      };

      this.rankChangeHistory.push(rankChangeEvent);

      // Keep only last 1000 events
      if (this.rankChangeHistory.length > 1000) {
        this.rankChangeHistory.shift();
      }

      this.emit('rankChanged', rankChangeEvent);
      this.broadcastUpdate(leaderboardId, 'rankChanged', rankChangeEvent);
    }

    this.emit('scoreUpdated', { leaderboardId, playerId, newScore });

    return rankChangeEvent;
  }

  /**
   * Update ranks
   */
  private updateRanks(leaderboardId: string): void {
    const entries = this.leaderboards.get(leaderboardId);
    if (!entries) return;

    entries.sort((a, b) => b.score - a.score);
    entries.forEach((entry, index) => {
      entry.rank = index + 1;
    });
  }

  /**
   * Get leaderboard
   */
  getLeaderboard(leaderboardId: string, limit: number = 100): LeaderboardEntry[] {
    const entries = this.leaderboards.get(leaderboardId) || [];
    return entries.slice(0, limit);
  }

  /**
   * Get player rank
   */
  getPlayerRank(leaderboardId: string, playerId: string): LeaderboardEntry | undefined {
    const entries = this.leaderboards.get(leaderboardId) || [];
    return entries.find((e) => e.playerId === playerId);
  }

  /**
   * Get rank around player
   */
  getRankAround(leaderboardId: string, playerId: string, range: number = 5): LeaderboardEntry[] {
    const entries = this.leaderboards.get(leaderboardId) || [];
    const playerEntry = entries.find((e) => e.playerId === playerId);

    if (!playerEntry) return [];

    const startIndex = Math.max(0, playerEntry.rank - range - 1);
    const endIndex = Math.min(entries.length, playerEntry.rank + range);

    return entries.slice(startIndex, endIndex);
  }

  /**
   * Broadcast update to subscribers
   */
  private broadcastUpdate(leaderboardId: string, eventType: string, data: any): void {
    const subscribers = this.subscribers.get(leaderboardId);
    if (!subscribers) return;

    this.emit('broadcast', {
      leaderboardId,
      eventType,
      data,
      subscribers: Array.from(subscribers),
    });
  }

  /**
   * Start live updates
   */
  startLiveUpdates(leaderboardId: string, interval: number = 5000): void {
    if (this.updateIntervals.has(leaderboardId)) {
      console.log(`[LiveLeaderboard] Already updating ${leaderboardId}`);
      return;
    }

    const timer = setInterval(() => {
      const snapshot: LeaderboardSnapshot = {
        timestamp: new Date(),
        entries: this.getLeaderboard(leaderboardId, 100),
        totalPlayers: this.leaderboards.get(leaderboardId)?.length || 0,
      };

      const snapshots = this.leaderboardSnapshots.get(leaderboardId) || [];
      snapshots.push(snapshot);

      // Keep only last 100 snapshots
      if (snapshots.length > 100) {
        snapshots.shift();
      }

      this.broadcastUpdate(leaderboardId, 'snapshot', snapshot);
    }, interval);

    this.updateIntervals.set(leaderboardId, timer);

    this.emit('liveUpdatesStarted', { leaderboardId, interval });
    console.log(`[LiveLeaderboard] Live updates started for ${leaderboardId}`);
  }

  /**
   * Stop live updates
   */
  stopLiveUpdates(leaderboardId: string): void {
    const timer = this.updateIntervals.get(leaderboardId);
    if (timer) {
      clearInterval(timer);
      this.updateIntervals.delete(leaderboardId);

      this.emit('liveUpdatesStopped', { leaderboardId });
      console.log(`[LiveLeaderboard] Live updates stopped for ${leaderboardId}`);
    }
  }

  /**
   * Get rank change history
   */
  getRankChangeHistory(limit: number = 50): RankChangeEvent[] {
    return this.rankChangeHistory.slice(-limit);
  }

  /**
   * Get leaderboard statistics
   */
  getLeaderboardStats(leaderboardId: string) {
    const entries = this.leaderboards.get(leaderboardId) || [];
    const snapshots = this.leaderboardSnapshots.get(leaderboardId) || [];

    const avgScore = entries.length > 0 ? entries.reduce((sum, e) => sum + e.score, 0) / entries.length : 0;
    const maxScore = entries.length > 0 ? Math.max(...entries.map((e) => e.score)) : 0;
    const minScore = entries.length > 0 ? Math.min(...entries.map((e) => e.score)) : 0;

    return {
      leaderboardId,
      totalPlayers: entries.length,
      avgScore,
      maxScore,
      minScore,
      snapshots: snapshots.length,
      subscribers: this.subscribers.get(leaderboardId)?.size || 0,
    };
  }

  /**
   * Get top players
   */
  getTopPlayers(leaderboardId: string, limit: number = 10): LeaderboardEntry[] {
    const entries = this.leaderboards.get(leaderboardId) || [];
    return entries.slice(0, limit);
  }

  /**
   * Get trending players (biggest rank changes)
   */
  getTrendingPlayers(leaderboardId: string, limit: number = 10): RankChangeEvent[] {
    return this.rankChangeHistory
      .filter((e) => e.oldRank > e.newRank) // Only rank ups
      .slice(-limit);
  }

  /**
   * Clear all data
   */
  clear(): void {
    this.updateIntervals.forEach((timer) => clearInterval(timer));
    this.leaderboards.clear();
    this.leaderboardSnapshots.clear();
    this.subscribers.clear();
    this.updateIntervals.clear();
    this.rankChangeHistory = [];
    console.log('[LiveLeaderboard] Service cleared');
  }
}

export const liveLeaderboardService = new LiveLeaderboardService();
