import { EventEmitter } from 'events';

export interface GameMetrics {
  gameId: string;
  date: Date;
  totalPlays: number;
  totalWagers: number;
  totalWins: number;
  totalLosses: number;
  netRevenue: number;
  avgBetAmount: number;
  avgWinAmount: number;
  avgSessionLength: number;
  uniquePlayers: number;
  newPlayers: number;
  returningPlayers: number;
  playerRetention: number;
  rtp: number;
  volatility: number;
  maxWin: number;
  bigWins: number; // Wins > 10x bet
  jackpotHits: number;
}

export interface GamePerformance {
  gameId: string;
  period: 'daily' | 'weekly' | 'monthly';
  startDate: Date;
  endDate: Date;
  metrics: GameMetrics[];
  trends: {
    playTrend: 'up' | 'down' | 'stable';
    revenueTrend: 'up' | 'down' | 'stable';
    retentionTrend: 'up' | 'down' | 'stable';
  };
  comparison: {
    previousPeriod: GameMetrics;
    percentChange: Record<string, number>;
  };
}

export interface PlayerGameStats {
  playerId: string;
  gameId: string;
  totalPlays: number;
  totalWagered: number;
  totalWon: number;
  netProfit: number;
  avgBet: number;
  winRate: number;
  maxWin: number;
  lastPlayed: Date;
  favoriteGame: boolean;
}

class GameAnalyticsSystem extends EventEmitter {
  private gameMetrics = new Map<string, GameMetrics[]>();
  private playerGameStats = new Map<string, PlayerGameStats>();
  private performanceCache = new Map<string, GamePerformance>();

  /**
   * Record game metrics
   */
  recordGameMetrics(metrics: Omit<GameMetrics, 'date'>): GameMetrics {
    const fullMetrics: GameMetrics = {
      ...metrics,
      date: new Date(),
    };

    const key = metrics.gameId;
    if (!this.gameMetrics.has(key)) {
      this.gameMetrics.set(key, []);
    }

    this.gameMetrics.get(key)!.push(fullMetrics);
    this.emit('metrics:recorded', fullMetrics);
    return fullMetrics;
  }

  /**
   * Get game metrics for date range
   */
  getGameMetrics(gameId: string, startDate: Date, endDate: Date): GameMetrics[] {
    const metrics = this.gameMetrics.get(gameId) || [];
    return metrics.filter(m => m.date >= startDate && m.date <= endDate);
  }

  /**
   * Get latest game metrics
   */
  getLatestGameMetrics(gameId: string): GameMetrics | null {
    const metrics = this.gameMetrics.get(gameId);
    return metrics && metrics.length > 0 ? metrics[metrics.length - 1] : null;
  }

  /**
   * Calculate game performance
   */
  calculateGamePerformance(
    gameId: string,
    period: 'daily' | 'weekly' | 'monthly',
    endDate: Date = new Date()
  ): GamePerformance | null {
    const periodMs = {
      daily: 24 * 60 * 60 * 1000,
      weekly: 7 * 24 * 60 * 60 * 1000,
      monthly: 30 * 24 * 60 * 60 * 1000,
    }[period];

    const startDate = new Date(endDate.getTime() - periodMs);
    const previousStartDate = new Date(startDate.getTime() - periodMs);

    const currentMetrics = this.getGameMetrics(gameId, startDate, endDate);
    const previousMetrics = this.getGameMetrics(gameId, previousStartDate, startDate);

    if (currentMetrics.length === 0) return null;

    // Aggregate metrics
    const aggregated: GameMetrics = {
      gameId,
      date: endDate,
      totalPlays: currentMetrics.reduce((sum, m) => sum + m.totalPlays, 0),
      totalWagers: currentMetrics.reduce((sum, m) => sum + m.totalWagers, 0),
      totalWins: currentMetrics.reduce((sum, m) => sum + m.totalWins, 0),
      totalLosses: currentMetrics.reduce((sum, m) => sum + m.totalLosses, 0),
      netRevenue: currentMetrics.reduce((sum, m) => sum + m.netRevenue, 0),
      avgBetAmount: currentMetrics.reduce((sum, m) => sum + m.avgBetAmount, 0) / currentMetrics.length,
      avgWinAmount: currentMetrics.reduce((sum, m) => sum + m.avgWinAmount, 0) / currentMetrics.length,
      avgSessionLength: currentMetrics.reduce((sum, m) => sum + m.avgSessionLength, 0) / currentMetrics.length,
      uniquePlayers: currentMetrics.reduce((sum, m) => sum + m.uniquePlayers, 0),
      newPlayers: currentMetrics.reduce((sum, m) => sum + m.newPlayers, 0),
      returningPlayers: currentMetrics.reduce((sum, m) => sum + m.returningPlayers, 0),
      playerRetention:
        currentMetrics.reduce((sum, m) => sum + m.playerRetention, 0) / currentMetrics.length,
      rtp: currentMetrics.reduce((sum, m) => sum + m.rtp, 0) / currentMetrics.length,
      volatility: currentMetrics.reduce((sum, m) => sum + m.volatility, 0) / currentMetrics.length,
      maxWin: Math.max(...currentMetrics.map(m => m.maxWin)),
      bigWins: currentMetrics.reduce((sum, m) => sum + m.bigWins, 0),
      jackpotHits: currentMetrics.reduce((sum, m) => sum + m.jackpotHits, 0),
    };

    // Calculate trends
    const previousAggregated = previousMetrics.length > 0 ? {
      totalPlays: previousMetrics.reduce((sum, m) => sum + m.totalPlays, 0),
      netRevenue: previousMetrics.reduce((sum, m) => sum + m.netRevenue, 0),
      playerRetention: previousMetrics.reduce((sum, m) => sum + m.playerRetention, 0) / previousMetrics.length,
    } : null;

    const trends = {
      playTrend: previousAggregated && aggregated.totalPlays > previousAggregated.totalPlays ? 'up' : 'down',
      revenueTrend: previousAggregated && aggregated.netRevenue > previousAggregated.netRevenue ? 'up' : 'down',
      retentionTrend: previousAggregated && aggregated.playerRetention > previousAggregated.playerRetention ? 'up' : 'down',
    };

    const percentChange: Record<string, number> = {};
    if (previousAggregated) {
      percentChange.plays = ((aggregated.totalPlays - previousAggregated.totalPlays) / previousAggregated.totalPlays) * 100;
      percentChange.revenue = ((aggregated.netRevenue - previousAggregated.netRevenue) / previousAggregated.netRevenue) * 100;
      percentChange.retention = ((aggregated.playerRetention - previousAggregated.playerRetention) / previousAggregated.playerRetention) * 100;
    }

    const performance: GamePerformance = {
      gameId,
      period,
      startDate,
      endDate,
      metrics: currentMetrics,
      trends,
      comparison: {
        previousPeriod: previousAggregated as GameMetrics,
        percentChange,
      },
    };

    this.performanceCache.set(`${gameId}_${period}`, performance);
    return performance;
  }

  /**
   * Record player game statistics
   */
  recordPlayerGameStats(stats: PlayerGameStats): void {
    const key = `${stats.playerId}_${stats.gameId}`;
    this.playerGameStats.set(key, stats);
    this.emit('player:stats:recorded', stats);
  }

  /**
   * Get player game statistics
   */
  getPlayerGameStats(playerId: string, gameId: string): PlayerGameStats | null {
    return this.playerGameStats.get(`${playerId}_${gameId}`) || null;
  }

  /**
   * Get player's favorite games
   */
  getPlayerFavoriteGames(playerId: string, limit: number = 5): PlayerGameStats[] {
    const stats = Array.from(this.playerGameStats.values())
      .filter(s => s.playerId === playerId)
      .sort((a, b) => b.totalPlays - a.totalPlays)
      .slice(0, limit);
    return stats;
  }

  /**
   * Get top performing games
   */
  getTopPerformingGames(limit: number = 10): Array<{ gameId: string; revenue: number; plays: number }> {
    const gameStats: Record<string, { revenue: number; plays: number }> = {};

    for (const metrics of this.gameMetrics.values()) {
      metrics.forEach(m => {
        if (!gameStats[m.gameId]) {
          gameStats[m.gameId] = { revenue: 0, plays: 0 };
        }
        gameStats[m.gameId].revenue += m.netRevenue;
        gameStats[m.gameId].plays += m.totalPlays;
      });
    }

    return Object.entries(gameStats)
      .map(([gameId, stats]) => ({ gameId, ...stats }))
      .sort((a, b) => b.revenue - a.revenue)
      .slice(0, limit);
  }

  /**
   * Get games by RTP performance
   */
  getGamesByRTPPerformance(): Array<{ gameId: string; avgRTP: number; actualRTP: number; variance: number }> {
    const gameRTP: Record<string, { expected: number[]; actual: number[] }> = {};

    for (const metrics of this.gameMetrics.values()) {
      metrics.forEach(m => {
        if (!gameRTP[m.gameId]) {
          gameRTP[m.gameId] = { expected: [], actual: [] };
        }
        gameRTP[m.gameId].expected.push(m.rtp);
        gameRTP[m.gameId].actual.push((m.totalWins / m.totalWagers) * 100);
      });
    }

    return Object.entries(gameRTP).map(([gameId, data]) => {
      const avgRTP = data.expected.reduce((a, b) => a + b, 0) / data.expected.length;
      const avgActual = data.actual.reduce((a, b) => a + b, 0) / data.actual.length;
      const variance = Math.abs(avgRTP - avgActual);

      return { gameId, avgRTP, actualRTP: avgActual, variance };
    });
  }

  /**
   * Get player retention metrics
   */
  getPlayerRetentionMetrics(gameId: string, days: number = 30): {
    day1Retention: number;
    day7Retention: number;
    day30Retention: number;
    churnRate: number;
  } {
    const metrics = this.gameMetrics.get(gameId) || [];
    const recentMetrics = metrics.slice(-days);

    if (recentMetrics.length === 0) {
      return { day1Retention: 0, day7Retention: 0, day30Retention: 0, churnRate: 0 };
    }

    const day1 = recentMetrics[0];
    const day7 = recentMetrics[Math.min(6, recentMetrics.length - 1)];
    const day30 = recentMetrics[recentMetrics.length - 1];

    return {
      day1Retention: (day1.returningPlayers / day1.uniquePlayers) * 100,
      day7Retention: (day7.returningPlayers / day7.uniquePlayers) * 100,
      day30Retention: (day30.returningPlayers / day30.uniquePlayers) * 100,
      churnRate: ((day1.uniquePlayers - day30.returningPlayers) / day1.uniquePlayers) * 100,
    };
  }

  /**
   * Get volatility analysis
   */
  getVolatilityAnalysis(gameId: string): {
    avgVolatility: number;
    volatilityTrend: 'increasing' | 'decreasing' | 'stable';
    recommendedAdjustment: string;
  } {
    const metrics = this.gameMetrics.get(gameId) || [];
    if (metrics.length === 0) {
      return { avgVolatility: 0, volatilityTrend: 'stable', recommendedAdjustment: 'Insufficient data' };
    }

    const recentMetrics = metrics.slice(-10);
    const avgVolatility = recentMetrics.reduce((sum, m) => sum + m.volatility, 0) / recentMetrics.length;

    let volatilityTrend: 'increasing' | 'decreasing' | 'stable' = 'stable';
    if (recentMetrics.length >= 2) {
      const firstHalf = recentMetrics.slice(0, Math.floor(recentMetrics.length / 2));
      const secondHalf = recentMetrics.slice(Math.floor(recentMetrics.length / 2));
      const firstAvg = firstHalf.reduce((sum, m) => sum + m.volatility, 0) / firstHalf.length;
      const secondAvg = secondHalf.reduce((sum, m) => sum + m.volatility, 0) / secondHalf.length;

      if (secondAvg > firstAvg * 1.1) volatilityTrend = 'increasing';
      else if (secondAvg < firstAvg * 0.9) volatilityTrend = 'decreasing';
    }

    let recommendedAdjustment = 'No adjustment needed';
    if (avgVolatility > 0.7) {
      recommendedAdjustment = 'Consider reducing volatility to improve player retention';
    } else if (avgVolatility < 0.3) {
      recommendedAdjustment = 'Consider increasing volatility for more exciting gameplay';
    }

    return { avgVolatility, volatilityTrend, recommendedAdjustment };
  }

  /**
   * Export analytics report
   */
  exportAnalyticsReport(gameId: string, startDate: Date, endDate: Date): string {
    const metrics = this.getGameMetrics(gameId, startDate, endDate);
    return JSON.stringify({ gameId, period: { startDate, endDate }, metrics }, null, 2);
  }
}

export const gameAnalyticsSystem = new GameAnalyticsSystem();
