import { EventEmitter } from 'events';

export interface PlayerPreference {
  playerId: string;
  gameId: string;
  genre: string;
  engagementScore: number;
  playCount: number;
  avgPlaytime: number;
  winRate: number;
  totalSpent: number;
  lastPlayedAt: Date;
}

export interface GameRecommendation {
  gameId: string;
  title: string;
  genre: string;
  score: number;
  reason: string;
  thumbnail: string;
  matchPercentage: number;
}

export interface CollaborativeFilteringResult {
  playerId: string;
  recommendations: GameRecommendation[];
  timestamp: Date;
}

/**
 * Game Recommendation Engine
 * Uses collaborative filtering and player preferences to recommend games
 */
export class GameRecommendationEngine extends EventEmitter {
  private playerPreferences: Map<string, PlayerPreference[]> = new Map();
  private gameLibrary: Map<string, any> = new Map();
  private recommendations: Map<string, CollaborativeFilteringResult> = new Map();

  constructor() {
    super();
    console.log('[GameRecommendation] Engine initialized');
  }

  /**
   * Track player game interaction
   */
  trackPlayerInteraction(playerId: string, gameId: string, interaction: Partial<PlayerPreference>): void {
    if (!this.playerPreferences.has(playerId)) {
      this.playerPreferences.set(playerId, []);
    }

    const preferences = this.playerPreferences.get(playerId)!;
    let existing = preferences.find((p) => p.gameId === gameId);

    if (!existing) {
      existing = {
        playerId,
        gameId,
        genre: interaction.genre || 'unknown',
        engagementScore: 0,
        playCount: 0,
        avgPlaytime: 0,
        winRate: 0,
        totalSpent: 0,
        lastPlayedAt: new Date(),
      };
      preferences.push(existing);
    }

    // Update metrics
    if (interaction.engagementScore !== undefined) existing.engagementScore = interaction.engagementScore;
    if (interaction.playCount !== undefined) existing.playCount = interaction.playCount;
    if (interaction.avgPlaytime !== undefined) existing.avgPlaytime = interaction.avgPlaytime;
    if (interaction.winRate !== undefined) existing.winRate = interaction.winRate;
    if (interaction.totalSpent !== undefined) existing.totalSpent = interaction.totalSpent;
    existing.lastPlayedAt = new Date();

    this.emit('interactionTracked', { playerId, gameId, interaction });
  }

  /**
   * Get player preferences
   */
  getPlayerPreferences(playerId: string): PlayerPreference[] {
    return this.playerPreferences.get(playerId) || [];
  }

  /**
   * Calculate similarity between two players
   */
  private calculatePlayerSimilarity(player1Id: string, player2Id: string): number {
    const prefs1 = this.playerPreferences.get(player1Id) || [];
    const prefs2 = this.playerPreferences.get(player2Id) || [];

    if (prefs1.length === 0 || prefs2.length === 0) return 0;

    // Find common games
    const games1 = new Set(prefs1.map((p) => p.gameId));
    const games2 = new Set(prefs2.map((p) => p.gameId));
    const commonGames = Array.from(games1).filter((g) => games2.has(g));

    if (commonGames.length === 0) return 0;

    // Calculate correlation based on engagement scores
    let sumProduct = 0;
    let sumSq1 = 0;
    let sumSq2 = 0;

    for (const gameId of commonGames) {
      const pref1 = prefs1.find((p) => p.gameId === gameId)!;
      const pref2 = prefs2.find((p) => p.gameId === gameId)!;

      const score1 = pref1.engagementScore;
      const score2 = pref2.engagementScore;

      sumProduct += score1 * score2;
      sumSq1 += score1 * score1;
      sumSq2 += score2 * score2;
    }

    const denominator = Math.sqrt(sumSq1) * Math.sqrt(sumSq2);
    return denominator > 0 ? sumProduct / denominator : 0;
  }

  /**
   * Get collaborative filtering recommendations
   */
  getCollaborativeRecommendations(playerId: string, limit: number = 5): GameRecommendation[] {
    const playerPrefs = this.playerPreferences.get(playerId) || [];
    const playedGames = new Set(playerPrefs.map((p) => p.gameId));

    // Find similar players
    const similarities: Array<{ playerId: string; similarity: number }> = [];

    for (const [otherPlayerId] of this.playerPreferences) {
      if (otherPlayerId === playerId) continue;

      const similarity = this.calculatePlayerSimilarity(playerId, otherPlayerId);
      if (similarity > 0) {
        similarities.push({ playerId: otherPlayerId, similarity });
      }
    }

    // Sort by similarity
    similarities.sort((a, b) => b.similarity - a.similarity);

    // Collect recommendations from similar players
    const gameScores: Map<string, number> = new Map();

    for (const { playerId: similarPlayerId, similarity } of similarities.slice(0, 10)) {
      const similarPlayerPrefs = this.playerPreferences.get(similarPlayerId) || [];

      for (const pref of similarPlayerPrefs) {
        if (!playedGames.has(pref.gameId)) {
          const currentScore = gameScores.get(pref.gameId) || 0;
          gameScores.set(pref.gameId, currentScore + pref.engagementScore * similarity);
        }
      }
    }

    // Sort by score and return top recommendations
    const recommendations = Array.from(gameScores.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, limit)
      .map(([gameId, score]) => ({
        gameId,
        title: `Game ${gameId}`,
        genre: 'unknown',
        score: Math.min(100, score * 10),
        reason: 'Players like you enjoyed this game',
        thumbnail: '',
        matchPercentage: Math.min(100, score * 10),
      }));

    return recommendations;
  }

  /**
   * Get genre-based recommendations
   */
  getGenreRecommendations(playerId: string, limit: number = 5): GameRecommendation[] {
    const playerPrefs = this.playerPreferences.get(playerId) || [];
    const playedGames = new Set(playerPrefs.map((p) => p.gameId));

    // Calculate genre preferences
    const genreScores: Map<string, number> = new Map();

    for (const pref of playerPrefs) {
      const currentScore = genreScores.get(pref.genre) || 0;
      genreScores.set(pref.genre, currentScore + pref.engagementScore);
    }

    // Get top genres
    const topGenres = Array.from(genreScores.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 3)
      .map(([genre]) => genre);

    // Find games in top genres that player hasn't played
    const recommendations: GameRecommendation[] = [];

    for (const [gameId, pref] of Array.from(this.playerPreferences.entries()).flatMap(([, prefs]) => prefs.map((p) => [p.gameId, p]))) {
      if (!playedGames.has(gameId) && topGenres.includes(pref.genre)) {
        recommendations.push({
          gameId,
          title: `Game ${gameId}`,
          genre: pref.genre,
          score: pref.engagementScore,
          reason: `You love ${pref.genre} games`,
          thumbnail: '',
          matchPercentage: pref.engagementScore,
        });
      }
    }

    return recommendations.sort((a, b) => b.matchPercentage - a.matchPercentage).slice(0, limit);
  }

  /**
   * Get engagement-based recommendations
   */
  getEngagementRecommendations(playerId: string, limit: number = 5): GameRecommendation[] {
    const playerPrefs = this.playerPreferences.get(playerId) || [];
    const playedGames = new Set(playerPrefs.map((p) => p.gameId));

    // Calculate average engagement
    const avgEngagement = playerPrefs.length > 0 ? playerPrefs.reduce((sum, p) => sum + p.engagementScore, 0) / playerPrefs.length : 0;

    // Find games with similar engagement levels
    const recommendations: GameRecommendation[] = [];

    for (const [gameId, pref] of Array.from(this.playerPreferences.entries()).flatMap(([, prefs]) => prefs.map((p) => [p.gameId, p]))) {
      if (!playedGames.has(gameId)) {
        const engagementDiff = Math.abs(pref.engagementScore - avgEngagement);
        const matchPercentage = Math.max(0, 100 - engagementDiff * 10);

        if (matchPercentage > 50) {
          recommendations.push({
            gameId,
            title: `Game ${gameId}`,
            genre: pref.genre,
            score: matchPercentage,
            reason: 'Similar engagement level to your preferences',
            thumbnail: '',
            matchPercentage,
          });
        }
      }
    }

    return recommendations.sort((a, b) => b.matchPercentage - a.matchPercentage).slice(0, limit);
  }

  /**
   * Get combined recommendations
   */
  getCombinedRecommendations(playerId: string, limit: number = 10): GameRecommendation[] {
    const collaborative = this.getCollaborativeRecommendations(playerId, Math.ceil(limit / 3));
    const genre = this.getGenreRecommendations(playerId, Math.ceil(limit / 3));
    const engagement = this.getEngagementRecommendations(playerId, Math.ceil(limit / 3));

    // Combine and deduplicate
    const combined = [...collaborative, ...genre, ...engagement];
    const seen = new Set<string>();
    const unique = combined.filter((rec) => {
      if (seen.has(rec.gameId)) return false;
      seen.add(rec.gameId);
      return true;
    });

    // Sort by score
    return unique.sort((a, b) => b.score - a.score).slice(0, limit);
  }

  /**
   * Cache recommendations
   */
  cacheRecommendations(playerId: string, recommendations: GameRecommendation[]): void {
    this.recommendations.set(playerId, {
      playerId,
      recommendations,
      timestamp: new Date(),
    });

    this.emit('recommendationsCached', { playerId, count: recommendations.length });
  }

  /**
   * Get cached recommendations
   */
  getCachedRecommendations(playerId: string): CollaborativeFilteringResult | undefined {
    return this.recommendations.get(playerId);
  }

  /**
   * Get recommendation statistics
   */
  getRecommendationStats() {
    return {
      totalPlayers: this.playerPreferences.size,
      totalRecommendationsCached: this.recommendations.size,
      avgPreferencesPerPlayer: this.playerPreferences.size > 0 ? Array.from(this.playerPreferences.values()).reduce((sum, prefs) => sum + prefs.length, 0) / this.playerPreferences.size : 0,
    };
  }

  /**
   * Clear all data
   */
  clear(): void {
    this.playerPreferences.clear();
    this.recommendations.clear();
    console.log('[GameRecommendation] Engine cleared');
  }
}

export const gameRecommendationEngine = new GameRecommendationEngine();
