// Socket.IO client instance for real-time communication
import { io } from 'socket.io-client';
import { createContext, useContext, useEffect, useState } from 'react';

// Create a Socket.IO context for use throughout the app
const SocketContext = createContext(null);

// Game context for sharing game state
const GameContext = createContext(null);

export function SocketProvider({ children }) {
  const [socket, setSocket] = useState(null);
  const [isConnected, setIsConnected] = useState(false);
  const [isReconnecting, setIsReconnecting] = useState(false);
  const [reconnectionAttempts, setReconnectionAttempts] = useState(0);
  const [offlineActions, setOfflineActions] = useState([]);
  const [lastActivityTime, setLastActivityTime] = useState(Date.now());

  useEffect(() => {
    // Create socket connection on component mount with improved reconnection settings
    const socketInstance = io(import.meta.env.DEV ? 'http://localhost:5237' : window.location.origin, {
      autoConnect: true,
      reconnection: true,
      reconnectionDelay: 1000,
      reconnectionDelayMax: 10000,
      reconnectionAttempts: Infinity, // Keep trying to reconnect indefinitely
      timeout: 20000, // Longer timeout for initial connection
      randomizationFactor: 0.5 // Add some randomization to reconnection attempts to prevent thundering herd
    });

    // Socket event listeners
    socketInstance.on('connect', () => {
      setIsConnected(true);
      setIsReconnecting(false);
      setReconnectionAttempts(0);
      console.log('Connected to server');
      
      // Process any actions that were queued while offline
      if (offlineActions.length > 0) {
        console.log(`Processing ${offlineActions.length} queued actions from offline mode`);
        // Wait a moment to ensure connection is stable
        setTimeout(() => {
          processOfflineActions(socketInstance);
        }, 1000);
      }
      
      // Try to reconnect to game if we have saved data
      const savedGameData = localStorage.getItem('songBattleGame');
      if (savedGameData) {
        try {
          const { lobbyId, playerName, lastKnownState } = JSON.parse(savedGameData);
          if (lobbyId && playerName) {
            console.log(`Attempting to reconnect to lobby: ${lobbyId}`);
            socketInstance.emit('reconnect_to_lobby', { 
              lobbyId, 
              playerName,
              lastKnownState // Send last known game state for server reconciliation
            }, (response) => {
              if (response.error) {
                console.error('Reconnection failed:', response.error);
                // Don't remove data immediately, we might try again
                if (response.error === 'Lobby not found') {
                  localStorage.removeItem('songBattleGame');
                }
              } else {
                console.log('Successfully reconnected to lobby');
                // Update the saved game data with latest state
                updateSavedGameData(response.lobby);
              }
            });
          }
        } catch (e) {
          console.error('Error parsing saved game data:', e);
          localStorage.removeItem('songBattleGame');
        }
      }
    });

    socketInstance.on('disconnect', (reason) => {
      setIsConnected(false);
      console.log(`Disconnected from server: ${reason}`);
      
      // If the disconnection is expected, don't try to reconnect
      if (reason === 'io client disconnect') {
        console.log('Disconnection was initiated by the client');
      } else {
        // Start reconnection process for unexpected disconnections
        setIsReconnecting(true);
      }
    });

    socketInstance.on('connect_error', (error) => {
      console.error('Connection error:', error);
      setIsReconnecting(true);
    });
    
    socketInstance.on('reconnect_attempt', (attemptNumber) => {
      console.log(`Reconnection attempt #${attemptNumber}`);
      setReconnectionAttempts(attemptNumber);
    });
    
    socketInstance.on('reconnect', () => {
      console.log('Reconnected to server after interruption');
      setIsReconnecting(false);
      setReconnectionAttempts(0);
    });
    
    socketInstance.on('reconnect_error', (error) => {
      console.error('Reconnection error:', error);
    });
    
    socketInstance.on('reconnect_failed', () => {
      console.error('Failed to reconnect to server after maximum attempts');
      setIsReconnecting(false);
    });

    // Setup a heartbeat to detect silent disconnections
    const heartbeatInterval = setInterval(() => {
      if (socketInstance && isConnected) {
        // Check if we've seen activity recently (within 15 seconds)
        const timeSinceLastActivity = Date.now() - lastActivityTime;
        if (timeSinceLastActivity > 15000) {
          console.log('No recent activity, sending ping to verify connection');
          socketInstance.emit('ping', () => {
            // Update activity time when we get a response
            setLastActivityTime(Date.now());
          });
          
          // Set a timeout to check if the ping was successful
          setTimeout(() => {
            const newTimeSinceLastActivity = Date.now() - lastActivityTime;
            if (newTimeSinceLastActivity > 15000) {
              console.warn('No response to ping, connection may be dead');
              // Force a reconnection attempt
              socketInstance.disconnect().connect();
            }
          }, 5000);
        }
      }
    }, 30000); // Check every 30 seconds

    setSocket(socketInstance);

    // Function to process queued offline actions
    const processOfflineActions = (socket) => {
      if (!socket || !offlineActions.length) return;
      
      // Process each action in sequence
      const processAction = (index) => {
        if (index >= offlineActions.length) {
          // All actions processed, clear the queue
          setOfflineActions([]);
          return;
        }
        
        const { action, payload, callback } = offlineActions[index];
        console.log(`Processing offline action: ${action}`, payload);
        
        // Emit the action to the server
        socket.emit(action, payload, (response) => {
          if (response.error) {
            console.error(`Error processing offline action ${action}:`, response.error);
          } else {
            console.log(`Successfully processed offline action: ${action}`);
            if (callback) callback(response);
          }
          
          // Process the next action
          processAction(index + 1);
        });
      };
      
      // Start processing from the first action
      processAction(0);
    };
    
    // Update activity timestamp on any received message
    const originalOnEvent = socketInstance.onEvent;
    socketInstance.onEvent = (packet) => {
      setLastActivityTime(Date.now());
      originalOnEvent.call(socketInstance, packet);
    };
    
    // Function to update saved game data with latest state
    const updateSavedGameData = (lobby) => {
      if (!lobby) return;
      
      const currentData = localStorage.getItem('songBattleGame');
      if (currentData) {
        try {
          const data = JSON.parse(currentData);
          data.lastKnownState = {
            gameState: lobby.state,
            currentBattle: lobby.currentBattle,
            timestamp: Date.now()
          };
          localStorage.setItem('songBattleGame', JSON.stringify(data));
        } catch (e) {
          console.error('Error updating saved game data:', e);
        }
      }
    };

    // Clean up on unmount
    return () => {
      clearInterval(heartbeatInterval);
      socketInstance.disconnect();
    };
  }, []);

  // Offline mode detection
  const [isOffline, setIsOffline] = useState(!navigator.onLine);
  
  // Monitor browser's online/offline status
  useEffect(() => {
    const handleOnline = () => setIsOffline(false);
    const handleOffline = () => setIsOffline(true);
    
    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);
  
  // Add an action to the offline queue
  const queueOfflineAction = (action, payload, callback) => {
    console.log(`Queuing offline action: ${action}`, payload);
    setOfflineActions(prev => [...prev, { action, payload, callback }]);
    
    // Store in localStorage as fallback
    try {
      const offlineQueue = JSON.parse(localStorage.getItem('offlineActionQueue') || '[]');
      offlineQueue.push({ action, payload, timestamp: Date.now() });
      localStorage.setItem('offlineActionQueue', JSON.stringify(offlineQueue));
    } catch (e) {
      console.error('Error storing offline action in localStorage:', e);
    }
  };
  
  // Emit an event, queue it if offline
  const safeEmit = (eventName, data, callback) => {
    // Update activity timestamp
    setLastActivityTime(Date.now());
    
    if (socket && isConnected && !isOffline) {
      // Online - send immediately
      socket.emit(eventName, data, callback);
    } else {
      // Offline or disconnected - queue for later
      queueOfflineAction(eventName, data, callback);
      
      // For specific actions, provide optimistic UI updates
      if (eventName === 'submit_vote' && data.songId) {
        // Optimistically show the vote locally
        // Implementation depends on your UI update mechanism
      }
    }
  };

  return (
    <SocketContext.Provider value={{ 
      socket, 
      isConnected, 
      isReconnecting,
      reconnectionAttempts,
      isOffline,
      safeEmit,
      queueOfflineAction
    }}>
      {children}
    </SocketContext.Provider>
  );
}

/**
 * Game state provider - manages game state and provides methods for game actions
 */
export function GameProvider({ children }) {
  const { socket, isConnected, isOffline, isReconnecting, safeEmit } = useContext(SocketContext);
  const [lobby, setLobby] = useState(null);
  const [error, setError] = useState(null);
  const [currentPlayer, setCurrentPlayer] = useState(null);

  // Save game info when lobby is joined
  useEffect(() => {
    if (lobby && currentPlayer) {
      const savedData = {
        lobbyId: lobby.id,
        playerName: currentPlayer.name,
        lastKnownState: {
          gameState: lobby.state,
          currentBattle: lobby.currentBattle,
          timestamp: Date.now()
        }
      };
      localStorage.setItem('songBattleGame', JSON.stringify(savedData));
    }
  }, [lobby, currentPlayer]);
  
  // Helper function to update saved game data
  const updateSavedGameData = (updatedLobby) => {
    if (!updatedLobby || !currentPlayer) return;
    
    const savedData = {
      lobbyId: updatedLobby.id,
      playerName: currentPlayer.name,
      lastKnownState: {
        gameState: updatedLobby.state,
        currentBattle: updatedLobby.currentBattle,
        playerState: {
          isReady: updatedLobby.players?.find(p => p.id === currentPlayer.id)?.isReady,
          hasVoted: updatedLobby.currentBattle?.votes?.[currentPlayer.id] !== undefined
        },
        timestamp: Date.now()
      }
    };
    localStorage.setItem('songBattleGame', JSON.stringify(savedData));
  };

  // Clear error after 5 seconds
  useEffect(() => {
    if (error) {
      const timer = setTimeout(() => {
        setError(null);
      }, 5000);
      return () => clearTimeout(timer);
    }
  }, [error]);

  // Socket event handlers for game updates
  useEffect(() => {
    if (!socket) return;

    // Player joined the lobby
    const handlePlayerJoined = (data) => {
      setLobby(prevLobby => {
        if (!prevLobby) return prevLobby;
        
        // Update the lobby with the new player
        const updatedPlayers = [...prevLobby.players, {
          id: data.playerId,
          name: data.playerName,
          isConnected: true,
          isReady: false,
          songCount: 0
        }];
        
        return {
          ...prevLobby,
          players: updatedPlayers
        };
      });
    };

    // Player reconnected to the lobby
    const handlePlayerReconnected = (data) => {
      setLobby(prevLobby => {
        if (!prevLobby) return prevLobby;
        
        // Update the lobby with the reconnected player
        const updatedPlayers = prevLobby.players.map(player => {
          if (player.name === data.playerName && !player.isConnected) {
            return { ...player, id: data.playerId, isConnected: true };
          }
          return player;
        });
        
        return {
          ...prevLobby,
          players: updatedPlayers
        };
      });
    };

    // Player disconnected from the lobby
    const handlePlayerDisconnected = (data) => {
      setLobby(data.lobby);
    };

    // Game settings were updated
    const handleSettingsUpdated = (data) => {
      setLobby(prevLobby => {
        if (!prevLobby) return prevLobby;
        return {
          ...prevLobby,
          settings: data.settings
        };
      });
    };

    // Game started
    const handleGameStarted = (data) => {
      setLobby(prevLobby => {
        if (!prevLobby) return prevLobby;
        return {
          ...prevLobby,
          state: data.state
        };
      });
    };

    // Songs were updated
    const handleSongsUpdated = (data) => {
      setLobby(data.lobby);
    };

    // Player status changed (ready/not ready)
    const handlePlayerStatusChanged = (data) => {
      console.log('Player status changed, new lobby state:', data.lobby.state);
      setLobby(data.lobby);

      // If the state is VOTING and we have a current battle, explicitly log it
      if (data.lobby.state === 'VOTING' && data.lobby.currentBattle) {
        console.log('Battle ready in player_status_changed:', data.lobby.currentBattle);
      }
    };

    // Vote was submitted
    const handleVoteSubmitted = (data) => {
      console.log('Vote submitted, updating lobby');
      setLobby(data.lobby);
    };

    // New battle started
    const handleNewBattle = (data) => {
      console.log('New battle received:', data.battle);
      setLobby(prevLobby => {
        if (!prevLobby) return prevLobby;
        
        // Ensure we update both the currentBattle and the state
        return {
          ...prevLobby,
          currentBattle: data.battle,
          state: 'VOTING'
        };
      });
    };

    // Handle battle ended and show result screen
    const handleBattleEnded = (data) => {
      console.log('Battle ended, showing result screen:', data);
      setLobby(prevLobby => {
        if (!prevLobby) return prevLobby;
        return {
          ...prevLobby,
          state: 'BATTLE',
          previousBattle: data.previousBattle
        };
      });
    };

    // Game finished
    const handleGameFinished = (data) => {
      setLobby(prevLobby => {
        if (!prevLobby) return prevLobby;
        return {
          ...prevLobby,
          state: 'FINISHED',
          finalWinner: data.winner,
          battles: data.battles
        };
      });
    };

    // Register socket event listeners
    socket.on('player_joined', handlePlayerJoined);
    socket.on('player_reconnected', handlePlayerReconnected);
    socket.on('player_disconnected', handlePlayerDisconnected);
    socket.on('settings_updated', handleSettingsUpdated);
    socket.on('game_started', handleGameStarted);
    socket.on('songs_updated', handleSongsUpdated);
    socket.on('player_status_changed', handlePlayerStatusChanged);
    socket.on('vote_submitted', handleVoteSubmitted);
    socket.on('battle_ended', handleBattleEnded);
    socket.on('tournament_started', data => {
      console.log('Tournament started event received:', data);
      setLobby(prevLobby => prevLobby ? {...prevLobby, state: data.state} : prevLobby);
    });
    socket.on('new_battle', handleNewBattle);
    socket.on('battle_ended', handleBattleEnded);
    socket.on('game_finished', handleGameFinished);

    // Clean up listeners on unmount
    return () => {
      socket.off('player_joined', handlePlayerJoined);
      socket.off('player_reconnected', handlePlayerReconnected);
      socket.off('player_disconnected', handlePlayerDisconnected);
      socket.off('settings_updated', handleSettingsUpdated);
      socket.off('game_started', handleGameStarted);
      socket.off('songs_updated', handleSongsUpdated);
      socket.off('player_status_changed', handlePlayerStatusChanged);
      socket.off('vote_submitted', handleVoteSubmitted);
      socket.off('new_battle', handleNewBattle);
      socket.off('battle_ended', handleBattleEnded);
      socket.off('game_finished', handleGameFinished);
    };
  }, [socket]);

  // Create a lobby
  const createLobby = (playerName) => {
    if (!socket || !isConnected) {
      setError('Not connected to server');
      return;
    }

    safeEmit('create_lobby', { playerName }, (response) => {
      if (response.error) {
        setError(response.error);
      } else {
        setLobby(response.lobby);
        setCurrentPlayer({
          id: socket.id,
          name: playerName,
          isHost: true
        });
      }
    });
  };

  // Join a lobby
  const joinLobby = (lobbyId, playerName) => {
    if (!socket || !isConnected) {
      setError('Not connected to server');
      return;
    }

    safeEmit('join_lobby', { lobbyId, playerName }, (response) => {
      if (response.error) {
        setError(response.error);
      } else {
        setLobby(response.lobby);
        setCurrentPlayer({
          id: socket.id,
          name: playerName,
          isHost: response.lobby.hostId === socket.id
        });
      }
    });
  };

  // Update lobby settings
  const updateSettings = (settings) => {
    if (!socket || !isConnected || !lobby) {
      setError('Not connected to server or no active lobby');
      return;
    }

    safeEmit('update_settings', { settings }, (response) => {
      if (response.error) {
        setError(response.error);
      }
    });
  };

  // Start the game
  const startGame = () => {
    if (!socket || !isConnected || !lobby) {
      setError('Not connected to server or no active lobby');
      return;
    }

    safeEmit('start_game', {}, (response) => {
      if (response.error) {
        setError(response.error);
      }
    });
  };

  // Add a song
  const addSong = (song) => {
    if (!socket || !isConnected || !lobby) {
      setError('Not connected to server or no active lobby');
      return Promise.reject('Not connected to server or no active lobby');
    }

    console.log('Attempting to add song:', song);
    console.log('Current player state:', currentPlayer);
    console.log('Current lobby state before adding song:', lobby);

    return new Promise((resolve, reject) => {
      safeEmit('add_song', { song }, (response) => {
        console.log('Song addition response:', response);
        if (response.error) {
          console.error('Error adding song:', response.error);
          setError(response.error);
          reject(response.error);
        } else if (response.lobby) {
          // Log detailed lobby state for debugging
          console.log('Song added successfully, full lobby response:', response.lobby);
          console.log('Current player songs:', response.lobby.players.find(p => p.id === currentPlayer.id)?.songs);
          console.log('All players song data:', 
            response.lobby.players.map(p => ({
              name: p.name, 
              id: p.id,
              songCount: p.songCount,
              songs: p.songs ? p.songs.map(s => ({id: s.id, title: s.title})) : 'No songs'
            }))
          );
          
          // Force a deep clone of the lobby to ensure React detects the change
          const updatedLobby = JSON.parse(JSON.stringify(response.lobby));
          setLobby(updatedLobby);
          
          // Verify the state was updated correctly
          setTimeout(() => {
            // This won't show the updated state immediately due to React's state update mechanism
            console.log('Lobby state after update (may not reflect immediate changes):', lobby);
            console.log('Updated lobby that was set:', updatedLobby);
          }, 0);
          
          // Store the latest state for offline reconciliation
          updateSavedGameData(updatedLobby);
          
          // Resolve with the updated lobby
          resolve(updatedLobby);
        } else {
          console.error('Song addition succeeded but no lobby data was returned');
          setError('Failed to update song list');
          reject('Failed to update song list');
        }
      });
    });
  };
   // Search for songs on YouTube
  const searchYouTube = (query) => {
    return new Promise((resolve, reject) => {
      if (!socket || !isConnected) {
        setError('Not connected to server');
        reject('Not connected to server');
        return;
      }

      safeEmit('search_youtube', { query }, (response) => {
        if (response.error) {
          setError(response.error);
          reject(response.error);
        } else {
          resolve(response.results);
        }
      });
    });
  };

  // Get metadata for a YouTube video by ID
  const getYouTubeMetadata = (videoId) => {
    return new Promise((resolve, reject) => {
      if (!socket || !isConnected) {
        setError('Not connected to server');
        reject('Not connected to server');
        return;
      }
      
      safeEmit('get_video_metadata', { videoId }, (response) => {
        if (response.error) {
          setError(response.error);
          reject(response.error);
        } else {
          resolve(response.metadata);
        }
      });
    });
  };

  // Remove a song
  const removeSong = (songId) => {
    if (!socket || !isConnected || !lobby) {
      setError('Not connected to server or no active lobby');
      return;
    }

    safeEmit('remove_song', { songId }, (response) => {
      if (response.error) {
        setError(response.error);
      } else if (response.lobby) {
        setLobby(response.lobby);
        updateSavedGameData(response.lobby);
      }
    });
  };

  // Set player ready
  const setPlayerReady = () => {
    if (!socket || !isConnected || !lobby) {
      setError('Not connected to server or no active lobby');
      return;
    }

    safeEmit('player_ready', {}, (response) => {
      if (response.error) {
        setError(response.error);
      } else if (response.lobby) {
        setLobby(response.lobby);
        updateSavedGameData(response.lobby);
      }
    });
  };

  // Submit a vote
  const submitVote = (songId) => {
    if (!socket || !isConnected || !lobby) {
      setError('Not connected to server or no active lobby');
      return;
    }

    safeEmit('submit_vote', { songId }, (response) => {
      if (response.error) {
        setError(response.error);
      } else if (response.lobby) {
        setLobby(response.lobby);
        updateSavedGameData(response.lobby);
      }
    });
  };

  // Leave lobby and clear saved game state
  const leaveLobby = () => {
    localStorage.removeItem('songBattleGame');
    setLobby(null);
    setCurrentPlayer(null);
  };

  // Proceed to next battle after the battle result screen
  const proceedToNextBattle = () => {
    if (!socket || !isConnected) {
      setError('Not connected to server');
      return;
    }

    safeEmit('proceed_to_next_battle', {}, (response) => {
      if (response.error) {
        setError(response.error);
      } else if (response.lobby) {
        setLobby(response.lobby);
        updateSavedGameData(response.lobby);
      }
    });
  };

  return (
    <GameContext.Provider value={{
      lobby,
      error,
      currentPlayer,
      isHost: currentPlayer && lobby && currentPlayer.id === lobby.hostId,
      isOffline: isOffline,
      isReconnecting,
      createLobby,
      joinLobby,
      updateSettings,
      startGame,
      addSong,
      removeSong,
      setPlayerReady,
      submitVote,
      leaveLobby,
      searchYouTube,
      getYouTubeMetadata,
      proceedToNextBattle
    }}>
      {children}
    </GameContext.Provider>
  );
}

// Custom hooks for using contexts
export const useSocket = () => useContext(SocketContext);
export const useGame = () => useContext(GameContext);

// Re-export for better module compatibility
export { SocketContext, GameContext };
