/**
 * CoinBattle Game Engine
 * Manages match state, game logic, timers, teams, and multipliers
 */

const { v4: uuidv4 } = require('uuid');

class CoinBattleEngine {
  constructor(database, socketIO, logger = console) {
    this.db = database;
    this.io = socketIO;
    this.logger = logger;

    // Current match state
    this.currentMatch = null;
    this.matchTimer = null;
    this.matchStartTime = null;
    this.matchDuration = 0; // seconds
    this.isPaused = false;
    this.pauseStartTime = null;
    this.totalPausedTime = 0;

    // Player tracking (in-memory for current match)
    this.players = new Map(); // userId -> player data
    this.teams = { red: new Set(), blue: new Set() };

    // Multiplier state
    this.activeMultiplier = 1.0;
    this.multiplierTimer = null;
    this.multiplierEndTime = null;

    // Configuration
    this.config = {
      matchDuration: 300, // 5 minutes default
      autoStart: false,
      autoReset: true,
      autoExtension: true,
      extensionThreshold: 15, // coins difference
      extensionDuration: 60, // seconds
      mode: 'solo', // 'solo' or 'team'
      teamAssignment: 'random', // 'random', 'manual', 'alternate'
      enableMultipliers: true,
      enableOfflineSimulation: false
    };

    // Offline simulation
    this.simulationInterval = null;
    this.simulationPlayers = [];

    // Atomic operation locks for race condition prevention
    this.matchEndLock = false;
    this.matchStartLock = false;

    // Event cache cleanup interval (every 5 minutes)
    this.cacheCleanupInterval = setInterval(() => {
      try {
        this.db.cleanupEventCache();
      } catch (error) {
        this.logger.error(`Event cache cleanup failed: ${error.message}`);
      }
    }, 5 * 60 * 1000);
  }

  /**
   * Load configuration
   */
  loadConfig(config) {
    this.config = { ...this.config, ...config };
    this.logger.info('CoinBattle config loaded:', this.config);
  }

  /**
   * Start a new match with atomic locking
   */
  startMatch(mode = null, duration = null) {
    // Atomic lock to prevent concurrent starts
    if (this.matchStartLock) {
      this.logger.warn('Match start already in progress');
      throw new Error('Match start already in progress');
    }

    if (this.currentMatch) {
      throw new Error('Match already in progress');
    }

    this.matchStartLock = true;
    try {
      const matchMode = mode || this.config.mode;
      const matchDuration = duration || this.config.matchDuration;

      // Create match in database
      const matchUuid = uuidv4();
      const matchId = this.db.createMatch({ match_uuid: matchUuid, mode: matchMode });

      this.currentMatch = {
        id: matchId,
        uuid: matchUuid,
        mode: matchMode,
        duration: matchDuration,
        status: 'active'
      };

      this.matchStartTime = Date.now();
      this.matchDuration = matchDuration;
      this.totalPausedTime = 0;
      this.isPaused = false;
      this.players.clear();
      this.teams.red.clear();
      this.teams.blue.clear();

      // Start countdown timer
      this.startTimer();

      // Emit match start event
      this.emitMatchState();

      this.logger.info(`Match started: ${matchUuid} (${matchMode} mode, ${matchDuration}s)`);
      return this.currentMatch;
    } finally {
      this.matchStartLock = false;
    }
  }

  /**
   * End current match with atomic locking
   */
  endMatch() {
    // Atomic lock to prevent concurrent calls
    if (this.matchEndLock) {
      this.logger.warn('Match end already in progress');
      return null;
    }

    if (!this.currentMatch) {
      throw new Error('No active match');
    }

    this.matchEndLock = true;
    try {
      this.stopTimer();

      // Calculate winner
      const leaderboard = this.db.getMatchLeaderboard(this.currentMatch.id, 100);
      const teamScores = this.currentMatch.mode === 'team' ? this.db.getTeamScores(this.currentMatch.id) : null;

      let winnerData = {
        total_coins: leaderboard.reduce((sum, p) => sum + p.coins, 0)
      };

      if (this.currentMatch.mode === 'team') {
        winnerData.team_red_score = teamScores.red;
        winnerData.team_blue_score = teamScores.blue;
        winnerData.winner_team = teamScores.red > teamScores.blue ? 'red' : 'blue';
      } else {
        winnerData.winner_player_id = leaderboard[0]?.user_id || null;
      }

      // End match in database
      this.db.endMatch(this.currentMatch.id, winnerData);

      // Update player lifetime stats
      for (const participant of leaderboard) {
        const isWinner = this.currentMatch.mode === 'team' 
          ? participant.team === winnerData.winner_team
          : participant.user_id === winnerData.winner_player_id;
        
        const isTeamMatch = this.currentMatch.mode === 'team';
        
        this.db.updatePlayerLifetimeStats(
          participant.player_id,
          participant.coins,
          participant.gifts,
          isWinner,
          isWinner && isTeamMatch
        );

        // Check and award badges
        const newBadges = this.db.checkAndAwardBadges(participant.player_id);
        if (newBadges.length > 0) {
          this.io.emit('coinbattle:badges-awarded', {
            userId: participant.user_id,
            badges: newBadges
          });
        }
      }

      // Update match stats
      this.db.updateMatchStats(this.currentMatch.id);

      // Emit match end event
      this.io.emit('coinbattle:match-ended', {
        matchId: this.currentMatch.id,
        winner: winnerData,
        leaderboard: leaderboard.slice(0, 10),
        teamScores
      });

      const matchId = this.currentMatch.id;
      this.currentMatch = null;

      this.logger.info(`Match ended: ${matchId}`);

      // Auto-reset if enabled
      if (this.config.autoReset) {
        setTimeout(() => {
          this.logger.info('Auto-reset triggered');
          // Optionally auto-start a new match
        }, 5000);
      }

      return { matchId, winnerData, leaderboard };
    } finally {
      this.matchEndLock = false;
    }
  }

  /**
   * Pause match
   */
  pauseMatch() {
    if (!this.currentMatch || this.isPaused) {
      return false;
    }

    this.isPaused = true;
    this.pauseStartTime = Date.now();
    this.stopTimer();

    this.io.emit('coinbattle:match-paused', {
      matchId: this.currentMatch.id,
      pausedAt: this.pauseStartTime
    });

    this.logger.info('Match paused');
    return true;
  }

  /**
   * Resume match
   */
  resumeMatch() {
    if (!this.currentMatch || !this.isPaused) {
      return false;
    }

    const pauseDuration = Date.now() - this.pauseStartTime;
    this.totalPausedTime += pauseDuration;
    this.isPaused = false;
    this.pauseStartTime = null;

    this.startTimer();

    this.io.emit('coinbattle:match-resumed', {
      matchId: this.currentMatch.id,
      resumedAt: Date.now()
    });

    this.logger.info('Match resumed');
    return true;
  }

  /**
   * Extend match duration
   */
  extendMatch(additionalSeconds) {
    if (!this.currentMatch) {
      return false;
    }

    this.matchDuration += additionalSeconds;
    this.db.incrementAutoExtension(this.currentMatch.id);

    this.io.emit('coinbattle:match-extended', {
      matchId: this.currentMatch.id,
      additionalSeconds,
      newDuration: this.matchDuration
    });

    this.logger.info(`Match extended by ${additionalSeconds}s`);
    return true;
  }

  /**
   * Start countdown timer
   */
  startTimer() {
    if (this.matchTimer) {
      clearInterval(this.matchTimer);
    }

    this.matchTimer = setInterval(() => {
      const elapsed = Math.floor((Date.now() - this.matchStartTime - this.totalPausedTime) / 1000);
      const remaining = Math.max(0, this.matchDuration - elapsed);

      this.io.emit('coinbattle:timer-update', {
        elapsed,
        remaining,
        total: this.matchDuration
      });

      // Check for auto-extension
      if (remaining === 0 && this.config.autoExtension) {
        const teamScores = this.currentMatch.mode === 'team' 
          ? this.db.getTeamScores(this.currentMatch.id)
          : null;

        if (teamScores) {
          const difference = Math.abs(teamScores.red - teamScores.blue);
          if (difference < this.config.extensionThreshold) {
            this.extendMatch(this.config.extensionDuration);
            return;
          }
        }
      }

      // Match end
      if (remaining === 0) {
        this.stopTimer();
        setTimeout(() => {
          this.endMatch();
        }, 1000);
      }
    }, 1000);
  }

  /**
   * Stop timer
   */
  stopTimer() {
    if (this.matchTimer) {
      clearInterval(this.matchTimer);
      this.matchTimer = null;
    }
  }

  /**
   * Process gift event with idempotency
   */
  processGift(giftData, userData, eventId = null) {
    if (!this.currentMatch) {
      // Auto-start if enabled
      if (this.config.autoStart) {
        this.startMatch();
      } else {
        return null;
      }
    }

    if (this.isPaused) {
      this.logger.warn('Gift received while match is paused');
      return null;
    }

    // Generate event identifiers for idempotency
    const generatedEventId = eventId || `evt_${this.currentMatch.id}_${userData.userId}_${giftData.giftId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    const idempotencyKey = `gift_${this.currentMatch.id}_${userData.userId}_${giftData.giftId}_${giftData.diamondCount || giftData.coins}_${Date.now()}`;

    // Check if event already processed (deduplication)
    if (this.db.isEventProcessed(generatedEventId, idempotencyKey)) {
      this.logger.warn(`Duplicate event detected and rejected: ${generatedEventId}`);
      return { duplicate: true, eventId: generatedEventId };
    }

    // Mark event as being processed (atomic operation)
    this.db.markEventProcessed(generatedEventId, idempotencyKey, this.currentMatch.id, userData.userId, 3600);

    try {
      // Get or create player
      const player = this.db.getOrCreatePlayer({
        userId: userData.userId,
        uniqueId: userData.uniqueId,
        nickname: userData.nickname,
        profilePictureUrl: userData.profilePictureUrl
      });

      // Assign team if in team mode
      let team = null;
      if (this.currentMatch.mode === 'team') {
        team = this.assignTeam(userData.userId, player.id);
      }

      // Add to match participants
      this.db.addMatchParticipant(this.currentMatch.id, player.id, userData.userId, team);

      // Calculate coins with multiplier
      // Use coins field which includes repeatCount (diamondCount * repeatCount)
      const coins = giftData.coins || giftData.diamondCount || 1;
      const multipliedCoins = Math.floor(coins * this.activeMultiplier);

      // Record gift event with idempotency keys
      const recorded = this.db.recordGiftEvent(
        this.currentMatch.id,
        player.id,
        userData.userId,
        {
          giftId: giftData.giftId,
          giftName: giftData.giftName,
          coins: coins
        },
        team,
        this.activeMultiplier,
        generatedEventId,
        idempotencyKey
      );

      if (!recorded) {
        this.logger.warn(`Event was duplicate in recordGiftEvent: ${generatedEventId}`);
        return { duplicate: true, eventId: generatedEventId };
      }

      // Update in-memory player data
      if (!this.players.has(userData.userId)) {
        this.players.set(userData.userId, {
          ...userData,
          coins: 0,
          gifts: 0,
          team
        });
      }

      const playerData = this.players.get(userData.userId);
      playerData.coins += multipliedCoins;
      playerData.gifts += 1;

      // Emit gift event
      this.io.emit('coinbattle:gift-received', {
        player: playerData,
        gift: giftData,
        coins: multipliedCoins,
        multiplier: this.activeMultiplier,
        team,
        eventId: generatedEventId
      });

      // Update leaderboard
      this.emitLeaderboard();

      return { player: playerData, coins: multipliedCoins, eventId: generatedEventId, duplicate: false };
    } catch (error) {
      this.logger.error(`Error processing gift: ${error.message}`);
      // Event failed to process, but cache entry prevents retry
      throw error;
    }
  }

  /**
   * Assign team to player
   */
  assignTeam(userId, playerId) {
    // Check if player already has a team in this match
    const participant = this.db.getRawDb().prepare(`
      SELECT team FROM coinbattle_match_participants
      WHERE match_id = ? AND player_id = ?
    `).get(this.currentMatch.id, playerId);

    if (participant && participant.team) {
      return participant.team;
    }

    let team;
    switch (this.config.teamAssignment) {
      case 'random':
        team = Math.random() < 0.5 ? 'red' : 'blue';
        break;
      case 'alternate':
        team = this.teams.red.size <= this.teams.blue.size ? 'red' : 'blue';
        break;
      default:
        team = 'red'; // Default
    }

    this.teams[team].add(userId);
    return team;
  }

  /**
   * Activate multiplier event
   */
  activateMultiplier(multiplier, duration, activatedBy = null) {
    if (!this.currentMatch) {
      throw new Error('No active match');
    }

    if (!this.config.enableMultipliers) {
      throw new Error('Multipliers are disabled');
    }

    this.activeMultiplier = multiplier;
    this.multiplierEndTime = Date.now() + (duration * 1000);

    // Record in database
    this.db.recordMultiplierEvent(this.currentMatch.id, multiplier, duration, activatedBy);

    // Clear existing timer
    if (this.multiplierTimer) {
      clearTimeout(this.multiplierTimer);
    }

    // Set timer to reset multiplier
    this.multiplierTimer = setTimeout(() => {
      this.activeMultiplier = 1.0;
      this.multiplierEndTime = null;
      this.io.emit('coinbattle:multiplier-ended', {
        matchId: this.currentMatch.id
      });
    }, duration * 1000);

    // Emit multiplier event
    this.io.emit('coinbattle:multiplier-activated', {
      matchId: this.currentMatch.id,
      multiplier,
      duration,
      endTime: this.multiplierEndTime,
      activatedBy
    });

    this.logger.info(`Multiplier activated: ${multiplier}x for ${duration}s`);
    return true;
  }

  /**
   * Emit current leaderboard
   */
  emitLeaderboard() {
    if (!this.currentMatch) return;

    const leaderboard = this.db.getMatchLeaderboard(this.currentMatch.id, 10);
    const teamScores = this.currentMatch.mode === 'team' 
      ? this.db.getTeamScores(this.currentMatch.id)
      : null;

    this.io.emit('coinbattle:leaderboard-update', {
      matchId: this.currentMatch.id,
      leaderboard,
      teamScores,
      mode: this.currentMatch.mode
    });
  }

  /**
   * Emit current match state
   */
  emitMatchState() {
    const state = this.getMatchState();
    this.io.emit('coinbattle:match-state', state);
  }

  /**
   * Get current match state
   */
  getMatchState() {
    if (!this.currentMatch) {
      return {
        active: false,
        match: null,
        leaderboard: [],
        teamScores: null,
        config: this.config
      };
    }

    const elapsed = Math.floor((Date.now() - this.matchStartTime - this.totalPausedTime) / 1000);
    const remaining = Math.max(0, this.matchDuration - elapsed);

    return {
      active: true,
      match: {
        ...this.currentMatch,
        startTime: this.matchStartTime,
        elapsed,
        remaining,
        isPaused: this.isPaused
      },
      leaderboard: this.db.getMatchLeaderboard(this.currentMatch.id, 10),
      teamScores: this.currentMatch.mode === 'team' 
        ? this.db.getTeamScores(this.currentMatch.id)
        : null,
      multiplier: {
        active: this.activeMultiplier > 1.0,
        value: this.activeMultiplier,
        endTime: this.multiplierEndTime
      },
      config: this.config
    };
  }

  /**
   * Start offline simulation mode
   */
  startSimulation() {
    if (this.simulationInterval) {
      this.stopSimulation();
    }

    // Create fake players
    this.simulationPlayers = [
      { userId: 'sim_1', uniqueId: 'simuser1', nickname: 'SimPlayer1', profilePictureUrl: null },
      { userId: 'sim_2', uniqueId: 'simuser2', nickname: 'SimPlayer2', profilePictureUrl: null },
      { userId: 'sim_3', uniqueId: 'simuser3', nickname: 'SimPlayer3', profilePictureUrl: null },
      { userId: 'sim_4', uniqueId: 'simuser4', nickname: 'SimPlayer4', profilePictureUrl: null },
      { userId: 'sim_5', uniqueId: 'simuser5', nickname: 'SimPlayer5', profilePictureUrl: null }
    ];

    // Start match if not active
    if (!this.currentMatch) {
      this.startMatch();
    }

    // Generate random gifts
    this.simulationInterval = setInterval(() => {
      const randomPlayer = this.simulationPlayers[Math.floor(Math.random() * this.simulationPlayers.length)];
      const randomCoins = Math.floor(Math.random() * 100) + 1;
      const randomGiftId = Math.floor(Math.random() * 10) + 1;

      this.processGift({
        giftId: randomGiftId,
        giftName: `Gift ${randomGiftId}`,
        diamondCount: randomCoins,
        coins: randomCoins
      }, randomPlayer);
    }, 2000);

    this.config.enableOfflineSimulation = true;
    this.logger.info('Offline simulation started');
  }

  /**
   * Stop offline simulation
   */
  stopSimulation() {
    if (this.simulationInterval) {
      clearInterval(this.simulationInterval);
      this.simulationInterval = null;
    }
    this.config.enableOfflineSimulation = false;
    this.logger.info('Offline simulation stopped');
  }

  /**
   * Clean up resources
   */
  destroy() {
    this.stopTimer();
    this.stopSimulation();
    if (this.multiplierTimer) {
      clearTimeout(this.multiplierTimer);
    }
    if (this.cacheCleanupInterval) {
      clearInterval(this.cacheCleanupInterval);
    }
  }
}

module.exports = CoinBattleEngine;
