const express = require("express");
const { Server } = require("socket.io");
const http = require("http");
const app = express();
const path = require("path");
const GameManager = require("./game");

app.use(express.static(path.join(__dirname, '../client/dist')));
app.disable("x-powered-by");

app.get('*', (req, res) => res.sendFile(path.join(__dirname, '../client/dist', 'index.html')));

const server = http.createServer(app);

const io = new Server(server, {
  cors: {origin: "*"},
  pingTimeout: 30000,
  pingInterval: 10000
});

// Initialize game manager
const gameManager = new GameManager();

// Socket.IO event handlers
io.on('connection', (socket) => {
  console.log(`User connected: ${socket.id}`);
  
  // Create a new game lobby
  socket.on('create_lobby', ({ playerName }, callback) => {
    try {
      const result = gameManager.createLobby(socket.id, playerName);
      
      // Join the socket room for this lobby
      socket.join(result.lobbyId);
      
      // Send response to client
      if (callback) callback(result);
      
      console.log(`Lobby created: ${result.lobbyId} by ${playerName}`);
    } catch (error) {
      console.error('Error creating lobby:', error);
      if (callback) callback({ error: 'Failed to create lobby' });
    }
  });
  
  // Join an existing lobby
  socket.on('join_lobby', ({ lobbyId, playerName }, callback) => {
    try {
      const result = gameManager.joinLobby(socket.id, playerName, lobbyId);
      
      if (result.error) {
        if (callback) callback(result);
        return;
      }
      
      // Join the socket room for this lobby
      socket.join(lobbyId);
      
      // Notify all players in the lobby
      socket.to(lobbyId).emit('player_joined', {
        playerId: socket.id,
        playerName
      });
      
      // Send response to client
      if (callback) callback(result);
      
      console.log(`Player ${playerName} joined lobby ${lobbyId}`);
    } catch (error) {
      console.error('Error joining lobby:', error);
      if (callback) callback({ error: 'Failed to join lobby' });
    }
  });
  
  // Attempt to reconnect to a lobby
  socket.on('reconnect_to_lobby', ({ lobbyId, playerName }, callback) => {
    try {
      const result = gameManager.handleReconnect(socket.id, lobbyId, playerName);
      
      if (result.error) {
        if (callback) callback(result);
        return;
      }
      
      // Join the socket room for this lobby
      socket.join(lobbyId);
      
      // Notify all players in the lobby
      socket.to(lobbyId).emit('player_reconnected', {
        playerId: socket.id,
        playerName
      });
      
      // Send response to client
      if (callback) callback(result);
      
      console.log(`Player ${playerName} reconnected to lobby ${lobbyId}`);
    } catch (error) {
      console.error('Error reconnecting to lobby:', error);
      if (callback) callback({ error: 'Failed to reconnect to lobby' });
    }
  });
  
  // Update lobby settings
  socket.on('update_settings', ({ settings }, callback) => {
    try {
      const result = gameManager.updateSettings(socket.id, settings);
      
      if (result.error) {
        if (callback) callback(result);
        return;
      }
      
      // Notify all players in the lobby
      io.to(result.lobbyId).emit('settings_updated', {
        settings: result.lobby.settings
      });
      
      // Send response to client
      if (callback) callback(result);
    } catch (error) {
      console.error('Error updating settings:', error);
      if (callback) callback({ error: 'Failed to update settings' });
    }
  });
  
  // Start the game from lobby
  socket.on('start_game', (_, callback) => {
    try {
      const result = gameManager.startGame(socket.id);
      
      if (result.error) {
        if (callback) callback(result);
        return;
      }
      
      // Notify all players in the lobby
      io.to(result.lobbyId).emit('game_started', {
        state: result.lobby.state
      });
      
      // Send response to client
      if (callback) callback(result);
      
      console.log(`Game started in lobby ${result.lobbyId}`);
    } catch (error) {
      console.error('Error starting game:', error);
      if (callback) callback({ error: 'Failed to start game' });
    }
  });
  
  // Add a song
  socket.on('add_song', async ({ song }, callback) => {
    try {
      console.log(`[DEBUG] Attempting to add song: ${JSON.stringify(song)}`);
      // Fix: Properly await the result of the async addSong function
      const result = await gameManager.addSong(socket.id, song);
      
      if (result.error) {
        console.log(`[DEBUG] Error adding song: ${result.error}`);
        if (callback) callback(result);
        return;
      }
      
      // Add better error handling to prevent crash if lobby is not defined
      if (!result.lobby || !result.lobbyId) {
        console.log(`[DEBUG] Warning: Song added but lobby information is missing`);
        if (callback) callback({ error: 'Failed to associate song with lobby' });
        return;
      }
      
      console.log(`[DEBUG] Song added successfully, notifying lobby ${result.lobbyId}`);
      console.log(`[DEBUG] Lobby songs: ${JSON.stringify(result.lobby.songs.map(s => s.title))}`);
      
      // Notify all players in the lobby about updated song count
      io.to(result.lobbyId).emit('songs_updated', {
        lobby: result.lobby
      });
      
      // Send response to client
      if (callback) callback(result);
    } catch (error) {
      console.error('Error adding song:', error);
      if (callback) callback({ error: 'Failed to add song' });
    }
  });
  
  // Remove a song
  socket.on('remove_song', ({ songId }, callback) => {
    try {
      const result = gameManager.removeSong(socket.id, songId);
      
      if (result.error) {
        if (callback) callback(result);
        return;
      }
      
      // Notify all players in the lobby
      io.to(result.lobbyId).emit('songs_updated', {
        lobby: result.lobby
      });
      
      // Send response to client
      if (callback) callback(result);
    } catch (error) {
      console.error('Error removing song:', error);
      if (callback) callback({ error: 'Failed to remove song' });
    }
  });
  
  // Search YouTube for songs
  socket.on('search_youtube', async ({ query }, callback) => {
    try {
      if (!query || typeof query !== 'string') {
        if (callback) callback({ error: 'Invalid search query' });
        return;
      }
      
      const results = await gameManager.searchYouTube(query);
      
      // Send response to client
      if (callback) callback({ results });
    } catch (error) {
      console.error('Error searching YouTube:', error);
      if (callback) callback({ error: 'Failed to search YouTube' });
    }
  });
  
  // Get metadata for a single YouTube video
  socket.on('get_video_metadata', async ({ videoId }, callback) => {
    try {
      if (!videoId || typeof videoId !== 'string') {
        if (callback) callback({ error: 'Invalid video ID' });
        return;
      }
      
      const metadata = await gameManager.getYouTubeVideoMetadata(videoId);
      
      // Send response to client
      if (callback) callback({ metadata });
    } catch (error) {
      console.error('Error getting YouTube metadata:', error);
      if (callback) callback({ error: 'Failed to get YouTube metadata' });
    }
  });
  
  // Mark player as ready
  socket.on('player_ready', (_, callback) => {
    try {
      const result = gameManager.setPlayerReady(socket.id);
      
      if (result.error) {
        if (callback) callback(result);
        return;
      }
      
      // Notify all players in the lobby
      io.to(result.lobbyId).emit('player_status_changed', {
        lobby: result.lobby
      });
      
      // If the game state just changed to VOTING, notify about the first battle
      if (result.lobby.state === 'VOTING' && result.lobby.currentBattle) {
        console.log('Sending new_battle event to clients');
        io.to(result.lobbyId).emit('new_battle', {
          battle: result.lobby.currentBattle
        });
      }
      
      // If tournament just started, explicitly notify about the state change and first battle
      if (result.tournamentStarted && result.lobby.currentBattle) {
        console.log('Tournament started, sending battle data');
        // First send state change
        io.to(result.lobbyId).emit('tournament_started', {
          state: 'VOTING'
        });
        
        // Then send battle data with a slight delay to ensure clients process in the right order
        setTimeout(() => {
          io.to(result.lobbyId).emit('new_battle', {
            battle: result.lobby.currentBattle
          });
        }, 300);
      }
      
      // If game finished (edge case where only one song was submitted)
      if (result.lobby.state === 'FINISHED') {
        io.to(result.lobbyId).emit('game_finished', {
          winner: result.lobby.finalWinner,
          battles: result.lobby.battles
        });
      }
      
      // Send response to client
      if (callback) callback(result);
    } catch (error) {
      console.error('Error setting player ready:', error);
      if (callback) callback({ error: 'Failed to set player status' });
    }
  });
  
  // Submit a vote in a battle
  socket.on('submit_vote', ({ songId }, callback) => {
    try {
      const result = gameManager.submitVote(socket.id, songId);
      
      if (result.error) {
        if (callback) callback(result);
        return;
      }
      
      // Notify all players about vote count
      io.to(result.lobbyId).emit('vote_submitted', {
        lobby: result.lobby
      });
      
      // If battle is finished, notify about new battle
      if (result.lobby.currentBattle) {
        io.to(result.lobbyId).emit('new_battle', {
          battle: result.lobby.currentBattle
        });
      }
      
      // If game is finished, notify about the winner
      if (result.lobby.state === 'FINISHED') {
        io.to(result.lobbyId).emit('game_finished', {
          winner: result.lobby.finalWinner,
          battles: result.lobby.battles
        });
      }
      
      // Send response to client
      if (callback) callback(result);
    } catch (error) {
      console.error('Error submitting vote:', error);
      if (callback) callback({ error: 'Failed to submit vote' });
    }
  });
  
  // Handle disconnection
  socket.on('disconnect', () => {
    try {
      const result = gameManager.handleDisconnect(socket.id);
      
      if (result) {
        // Notify remaining players about the disconnection
        io.to(result.lobbyId).emit('player_disconnected', {
          playerId: socket.id,
          lobby: result.lobby
        });
        
        console.log(`Player ${socket.id} disconnected from lobby ${result.lobbyId}`);
      }
    } catch (error) {
      console.error('Error handling disconnect:', error);
    }
  });
});

server.on('error', (error) => {
  console.error('Server error:', error);
});

const PORT = process.env.PORT || 5237;
server.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

process.on('SIGINT', () => {
  console.log('Server shutting down...');
  server.close(() => {
    console.log('Server closed');
    process.exit(0);
  });
});