import { describe, it, expect, vi, beforeEach } from 'vitest';
import { Player, GameState } from '../../types';

describe('Kick and Leave Feature Logic', () => {
  const createPlayer = (overrides: Partial<Player> = {}): Player => ({
    id: 'player-1',
    name: 'Test Player',
    score: 0,
    previousScore: 0,
    streak: 0,
    lastAnswerCorrect: null,
    selectedShape: null,
    pointsBreakdown: null,
    isBot: false,
    avatarSeed: 0.5,
    color: '#ff0000',
    ...overrides,
  });

  describe('kickPlayer logic', () => {
    const processKick = (
      playerId: string,
      players: Player[],
      presenterId: string | null,
      role: 'HOST' | 'CLIENT'
    ): { 
      updatedPlayers: Player[]; 
      newPresenterId: string | null;
      shouldKick: boolean;
    } => {
      if (role !== 'HOST') {
        return { updatedPlayers: players, newPresenterId: presenterId, shouldKick: false };
      }
      if (playerId === 'host') {
        return { updatedPlayers: players, newPresenterId: presenterId, shouldKick: false };
      }
      
      const updatedPlayers = players.filter(p => p.id !== playerId);
      const newPresenterId = presenterId === playerId ? null : presenterId;
      
      return { updatedPlayers, newPresenterId, shouldKick: true };
    };

    it('removes player from players list', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const result = processKick('player-1', players, null, 'HOST');
      
      expect(result.updatedPlayers).toHaveLength(1);
      expect(result.updatedPlayers[0].id).toBe('player-2');
      expect(result.shouldKick).toBe(true);
    });

    it('does not allow kick when role is CLIENT', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const result = processKick('player-1', players, null, 'CLIENT');
      
      expect(result.updatedPlayers).toHaveLength(2);
      expect(result.shouldKick).toBe(false);
    });

    it('does not allow kicking host player', () => {
      const players = [
        createPlayer({ id: 'host', name: 'Host' }),
        createPlayer({ id: 'player-1', name: 'Alice' }),
      ];
      
      const result = processKick('host', players, null, 'HOST');
      
      expect(result.updatedPlayers).toHaveLength(2);
      expect(result.shouldKick).toBe(false);
    });

    it('clears presenter when kicked player was presenter', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const result = processKick('player-1', players, 'player-1', 'HOST');
      
      expect(result.updatedPlayers).toHaveLength(1);
      expect(result.newPresenterId).toBeNull();
      expect(result.shouldKick).toBe(true);
    });

    it('preserves presenter when different player kicked', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const result = processKick('player-2', players, 'player-1', 'HOST');
      
      expect(result.updatedPlayers).toHaveLength(1);
      expect(result.newPresenterId).toBe('player-1');
      expect(result.shouldKick).toBe(true);
    });

    it('handles kicking non-existent player gracefully', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
      ];
      
      const result = processKick('player-999', players, null, 'HOST');
      
      expect(result.updatedPlayers).toHaveLength(1);
      expect(result.shouldKick).toBe(true);
    });

    it('handles kicking last player', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
      ];
      
      const result = processKick('player-1', players, 'player-1', 'HOST');
      
      expect(result.updatedPlayers).toHaveLength(0);
      expect(result.newPresenterId).toBeNull();
      expect(result.shouldKick).toBe(true);
    });

    it('handles empty player id string', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
      ];
      
      const result = processKick('', players, null, 'HOST');
      
      expect(result.updatedPlayers).toHaveLength(1);
      expect(result.shouldKick).toBe(true);
    });

    it('handles player id with special characters', () => {
      const players = [
        createPlayer({ id: 'peer-abc123-xyz', name: 'Alice' }),
        createPlayer({ id: 'peer-def456-uvw', name: 'Bob' }),
      ];
      
      const result = processKick('peer-abc123-xyz', players, null, 'HOST');
      
      expect(result.updatedPlayers).toHaveLength(1);
      expect(result.updatedPlayers[0].id).toBe('peer-def456-uvw');
      expect(result.shouldKick).toBe(true);
    });

    it('handles kicking from empty players array', () => {
      const players: Player[] = [];
      
      const result = processKick('player-1', players, null, 'HOST');
      
      expect(result.updatedPlayers).toHaveLength(0);
      expect(result.shouldKick).toBe(true);
    });

    it('preserves player scores and data when kicking another player', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice', score: 1000, streak: 5 }),
        createPlayer({ id: 'player-2', name: 'Bob', score: 500, streak: 2 }),
      ];
      
      const result = processKick('player-1', players, null, 'HOST');
      
      expect(result.updatedPlayers[0].score).toBe(500);
      expect(result.updatedPlayers[0].streak).toBe(2);
    });

    it('handles kicking player with same name as host', () => {
      const players = [
        createPlayer({ id: 'host', name: 'Host' }),
        createPlayer({ id: 'player-1', name: 'Host' }),
      ];
      
      const result = processKick('player-1', players, null, 'HOST');
      
      expect(result.updatedPlayers).toHaveLength(1);
      expect(result.updatedPlayers[0].id).toBe('host');
      expect(result.shouldKick).toBe(true);
    });

    it('handles multiple sequential kicks', () => {
      let players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
        createPlayer({ id: 'player-3', name: 'Charlie' }),
      ];
      
      const result1 = processKick('player-1', players, null, 'HOST');
      players = result1.updatedPlayers;
      
      const result2 = processKick('player-2', players, null, 'HOST');
      players = result2.updatedPlayers;
      
      expect(players).toHaveLength(1);
      expect(players[0].id).toBe('player-3');
    });

    it('handles kicking when multiple players have similar ids', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-10', name: 'Bob' }),
        createPlayer({ id: 'player-100', name: 'Charlie' }),
      ];
      
      const result = processKick('player-1', players, null, 'HOST');
      
      expect(result.updatedPlayers).toHaveLength(2);
      expect(result.updatedPlayers.find(p => p.id === 'player-10')).toBeDefined();
      expect(result.updatedPlayers.find(p => p.id === 'player-100')).toBeDefined();
    });

    it('does not modify original players array', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      const originalLength = players.length;
      
      processKick('player-1', players, null, 'HOST');
      
      expect(players).toHaveLength(originalLength);
    });

    it('handles bot players same as regular players', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice', isBot: false }),
        createPlayer({ id: 'bot-1', name: 'Bot Player', isBot: true }),
      ];
      
      const result = processKick('bot-1', players, null, 'HOST');
      
      expect(result.updatedPlayers).toHaveLength(1);
      expect(result.shouldKick).toBe(true);
    });

    it('handles player who answered correctly being kicked', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice', lastAnswerCorrect: true, selectedShape: 'triangle' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const result = processKick('player-1', players, null, 'HOST');
      
      expect(result.updatedPlayers).toHaveLength(1);
      expect(result.shouldKick).toBe(true);
    });
  });

  describe('leaveGame logic', () => {
    interface LeaveGameContext {
      role: 'HOST' | 'CLIENT';
      gameState: GameState;
      hasConnection: boolean;
      hasPeer: boolean;
    }

    const processLeave = (context: LeaveGameContext): { 
      shouldLeave: boolean;
      shouldCloseConnection: boolean;
      shouldDestroyPeer: boolean;
      shouldClearSession: boolean;
      shouldClearTimers: boolean;
      newGameState: GameState;
    } => {
      if (context.role !== 'CLIENT') {
        return { 
          shouldLeave: false, 
          shouldCloseConnection: false,
          shouldDestroyPeer: false,
          shouldClearSession: false,
          shouldClearTimers: false,
          newGameState: context.gameState,
        };
      }
      return { 
        shouldLeave: true,
        shouldCloseConnection: context.hasConnection,
        shouldDestroyPeer: context.hasPeer,
        shouldClearSession: true,
        shouldClearTimers: true,
        newGameState: 'LANDING',
      };
    };

    it('allows client to leave', () => {
      const result = processLeave({ 
        role: 'CLIENT', 
        gameState: 'LOBBY',
        hasConnection: true,
        hasPeer: true,
      });
      
      expect(result.shouldLeave).toBe(true);
    });

    it('does not allow host to leave via leaveGame', () => {
      const result = processLeave({ 
        role: 'HOST', 
        gameState: 'LOBBY',
        hasConnection: false,
        hasPeer: true,
      });
      
      expect(result.shouldLeave).toBe(false);
    });

    it('closes connection when leaving with active connection', () => {
      const result = processLeave({ 
        role: 'CLIENT', 
        gameState: 'LOBBY',
        hasConnection: true,
        hasPeer: true,
      });
      
      expect(result.shouldCloseConnection).toBe(true);
    });

    it('does not close connection when no connection exists', () => {
      const result = processLeave({ 
        role: 'CLIENT', 
        gameState: 'LOBBY',
        hasConnection: false,
        hasPeer: true,
      });
      
      expect(result.shouldCloseConnection).toBe(false);
    });

    it('destroys peer when leaving', () => {
      const result = processLeave({ 
        role: 'CLIENT', 
        gameState: 'LOBBY',
        hasConnection: true,
        hasPeer: true,
      });
      
      expect(result.shouldDestroyPeer).toBe(true);
    });

    it('does not destroy peer when none exists', () => {
      const result = processLeave({ 
        role: 'CLIENT', 
        gameState: 'LOBBY',
        hasConnection: true,
        hasPeer: false,
      });
      
      expect(result.shouldDestroyPeer).toBe(false);
    });

    it('clears session when leaving', () => {
      const result = processLeave({ 
        role: 'CLIENT', 
        gameState: 'LOBBY',
        hasConnection: true,
        hasPeer: true,
      });
      
      expect(result.shouldClearSession).toBe(true);
    });

    it('clears timers when leaving', () => {
      const result = processLeave({ 
        role: 'CLIENT', 
        gameState: 'QUESTION',
        hasConnection: true,
        hasPeer: true,
      });
      
      expect(result.shouldClearTimers).toBe(true);
    });

    it('sets game state to LANDING when leaving', () => {
      const result = processLeave({ 
        role: 'CLIENT', 
        gameState: 'LOBBY',
        hasConnection: true,
        hasPeer: true,
      });
      
      expect(result.newGameState).toBe('LANDING');
    });

    it('preserves game state when host tries to leave', () => {
      const result = processLeave({ 
        role: 'HOST', 
        gameState: 'QUESTION',
        hasConnection: false,
        hasPeer: true,
      });
      
      expect(result.newGameState).toBe('QUESTION');
    });

    it('handles leaving during different game states', () => {
      const states: GameState[] = ['LOBBY', 'COUNTDOWN', 'QUESTION', 'REVEAL', 'SCOREBOARD'];
      
      states.forEach(state => {
        const result = processLeave({ 
          role: 'CLIENT', 
          gameState: state,
          hasConnection: true,
          hasPeer: true,
        });
        expect(result.shouldLeave).toBe(true);
        expect(result.newGameState).toBe('LANDING');
      });
    });

    it('handles leaving when already disconnected', () => {
      const result = processLeave({ 
        role: 'CLIENT', 
        gameState: 'DISCONNECTED',
        hasConnection: false,
        hasPeer: false,
      });
      
      expect(result.shouldLeave).toBe(true);
      expect(result.newGameState).toBe('LANDING');
    });
  });

  describe('KICKED message handling', () => {
    interface KickedPayload {
      reason?: string;
    }

    interface KickedContext {
      hasHostConnection: boolean;
      hasPeer: boolean;
      hasTimer: boolean;
    }

    const processKickedMessage = (
      payload: KickedPayload,
      currentGameState: string,
      context: KickedContext = { hasHostConnection: true, hasPeer: true, hasTimer: true }
    ): { 
      newGameState: 'LANDING';
      error: string;
      shouldClearSession: boolean;
      shouldCloseConnection: boolean;
      shouldDestroyPeer: boolean;
      shouldClearTimer: boolean;
      shouldClearGameData: boolean;
    } => {
      return {
        newGameState: 'LANDING',
        error: payload.reason || 'You were kicked from the game',
        shouldClearSession: true,
        shouldCloseConnection: context.hasHostConnection,
        shouldDestroyPeer: context.hasPeer,
        shouldClearTimer: context.hasTimer,
        shouldClearGameData: true,
      };
    };

    it('sets game state to LANDING when kicked', () => {
      const result = processKickedMessage({ reason: 'Kicked by host' }, 'LOBBY');
      
      expect(result.newGameState).toBe('LANDING');
    });

    it('uses provided reason as error message', () => {
      const result = processKickedMessage({ reason: 'Custom kick reason' }, 'LOBBY');
      
      expect(result.error).toBe('Custom kick reason');
    });

    it('uses default error message when no reason provided', () => {
      const result = processKickedMessage({}, 'LOBBY');
      
      expect(result.error).toBe('You were kicked from the game');
    });

    it('clears session when kicked', () => {
      const result = processKickedMessage({ reason: 'Kicked' }, 'LOBBY');
      
      expect(result.shouldClearSession).toBe(true);
    });

    it('handles kick during different game states', () => {
      const states = ['LOBBY', 'QUESTION', 'REVEAL', 'SCOREBOARD'];
      
      states.forEach(state => {
        const result = processKickedMessage({ reason: 'Kicked' }, state);
        expect(result.newGameState).toBe('LANDING');
        expect(result.shouldClearSession).toBe(true);
      });
    });

    it('handles empty reason string', () => {
      const result = processKickedMessage({ reason: '' }, 'LOBBY');
      
      expect(result.error).toBe('You were kicked from the game');
    });

    it('handles reason with special characters', () => {
      const result = processKickedMessage({ reason: 'Kicked for <script>alert("xss")</script>' }, 'LOBBY');
      
      expect(result.error).toBe('Kicked for <script>alert("xss")</script>');
    });

    it('handles very long reason string', () => {
      const longReason = 'A'.repeat(1000);
      const result = processKickedMessage({ reason: longReason }, 'LOBBY');
      
      expect(result.error).toBe(longReason);
    });

    it('closes host connection when kicked', () => {
      const result = processKickedMessage(
        { reason: 'Kicked' }, 
        'LOBBY',
        { hasHostConnection: true, hasPeer: true, hasTimer: true }
      );
      
      expect(result.shouldCloseConnection).toBe(true);
    });

    it('does not try to close connection when none exists', () => {
      const result = processKickedMessage(
        { reason: 'Kicked' }, 
        'LOBBY',
        { hasHostConnection: false, hasPeer: true, hasTimer: true }
      );
      
      expect(result.shouldCloseConnection).toBe(false);
    });

    it('destroys peer when kicked', () => {
      const result = processKickedMessage(
        { reason: 'Kicked' }, 
        'LOBBY',
        { hasHostConnection: true, hasPeer: true, hasTimer: true }
      );
      
      expect(result.shouldDestroyPeer).toBe(true);
    });

    it('clears timer when kicked during question', () => {
      const result = processKickedMessage(
        { reason: 'Kicked' }, 
        'QUESTION',
        { hasHostConnection: true, hasPeer: true, hasTimer: true }
      );
      
      expect(result.shouldClearTimer).toBe(true);
    });

    it('handles kick when already in LANDING state', () => {
      const result = processKickedMessage({ reason: 'Kicked' }, 'LANDING');
      
      expect(result.newGameState).toBe('LANDING');
      expect(result.shouldClearSession).toBe(true);
    });

    it('handles kick during COUNTDOWN', () => {
      const result = processKickedMessage({ reason: 'Kicked' }, 'COUNTDOWN');
      
      expect(result.newGameState).toBe('LANDING');
    });

    it('clears all game data when kicked', () => {
      const result = processKickedMessage({ reason: 'Kicked' }, 'QUESTION');
      
      expect(result.shouldClearGameData).toBe(true);
    });

    it('handles undefined reason gracefully', () => {
      const result = processKickedMessage({ reason: undefined }, 'LOBBY');
      
      expect(result.error).toBe('You were kicked from the game');
    });
  });

  describe('PLAYER_LEFT message handling', () => {
    const processPlayerLeft = (
      playerId: string,
      players: Player[],
      presenterId: string | null = null
    ): { updatedPlayers: Player[]; shouldUpdatePresenter: boolean; newPresenterId: string | null } => {
      const updatedPlayers = players.filter(p => p.id !== playerId);
      const shouldUpdatePresenter = presenterId === playerId;
      const newPresenterId = shouldUpdatePresenter ? null : presenterId;
      
      return { updatedPlayers, shouldUpdatePresenter, newPresenterId };
    };

    it('removes player from list when they leave', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
        createPlayer({ id: 'player-3', name: 'Charlie' }),
      ];
      
      const result = processPlayerLeft('player-2', players);
      
      expect(result.updatedPlayers).toHaveLength(2);
      expect(result.updatedPlayers.find(p => p.id === 'player-2')).toBeUndefined();
      expect(result.updatedPlayers.find(p => p.id === 'player-1')).toBeDefined();
      expect(result.updatedPlayers.find(p => p.id === 'player-3')).toBeDefined();
    });

    it('handles player leaving who does not exist', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
      ];
      
      const result = processPlayerLeft('player-999', players);
      
      expect(result.updatedPlayers).toHaveLength(1);
    });

    it('handles last player leaving', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
      ];
      
      const result = processPlayerLeft('player-1', players);
      
      expect(result.updatedPlayers).toHaveLength(0);
    });

    it('updates presenter when presenter leaves', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const result = processPlayerLeft('player-1', players, 'player-1');
      
      expect(result.shouldUpdatePresenter).toBe(true);
      expect(result.newPresenterId).toBeNull();
    });

    it('does not update presenter when non-presenter leaves', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const result = processPlayerLeft('player-2', players, 'player-1');
      
      expect(result.shouldUpdatePresenter).toBe(false);
      expect(result.newPresenterId).toBe('player-1');
    });

    it('preserves player order when someone leaves', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
        createPlayer({ id: 'player-3', name: 'Charlie' }),
      ];
      
      const result = processPlayerLeft('player-2', players);
      
      expect(result.updatedPlayers[0].id).toBe('player-1');
      expect(result.updatedPlayers[1].id).toBe('player-3');
    });

    it('preserves player scores when someone leaves', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice', score: 1000 }),
        createPlayer({ id: 'player-2', name: 'Bob', score: 500 }),
      ];
      
      const result = processPlayerLeft('player-2', players);
      
      expect(result.updatedPlayers[0].score).toBe(1000);
    });

    it('handles empty players array', () => {
      const players: Player[] = [];
      
      const result = processPlayerLeft('player-1', players);
      
      expect(result.updatedPlayers).toHaveLength(0);
    });

    it('handles player with complex id leaving', () => {
      const players = [
        createPlayer({ id: 'peer-abc123-xyz789-def', name: 'Alice' }),
        createPlayer({ id: 'peer-111222-333444-555', name: 'Bob' }),
      ];
      
      const result = processPlayerLeft('peer-abc123-xyz789-def', players);
      
      expect(result.updatedPlayers).toHaveLength(1);
      expect(result.updatedPlayers[0].id).toBe('peer-111222-333444-555');
    });

    it('does not modify original players array', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      const originalLength = players.length;
      
      processPlayerLeft('player-1', players);
      
      expect(players).toHaveLength(originalLength);
    });

    it('handles multiple players leaving in sequence', () => {
      let players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
        createPlayer({ id: 'player-3', name: 'Charlie' }),
        createPlayer({ id: 'player-4', name: 'Diana' }),
      ];
      
      const result1 = processPlayerLeft('player-1', players);
      players = result1.updatedPlayers;
      
      const result2 = processPlayerLeft('player-3', players);
      players = result2.updatedPlayers;
      
      expect(players).toHaveLength(2);
      expect(players.map(p => p.id)).toEqual(['player-2', 'player-4']);
    });

    it('handles leaving player with active answer state', () => {
      const players = [
        createPlayer({ 
          id: 'player-1', 
          name: 'Alice', 
          lastAnswerCorrect: true, 
          selectedShape: 'triangle',
          pointsBreakdown: { basePoints: 100, streakBonus: 0, comebackBonus: 0, firstCorrectBonus: 0, penalty: 0, total: 100 }
        }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const result = processPlayerLeft('player-1', players);
      
      expect(result.updatedPlayers).toHaveLength(1);
      expect(result.updatedPlayers[0].id).toBe('player-2');
    });
  });

  describe('edge cases and race conditions', () => {
    it('handles kick request for already-kicked player', () => {
      const processKick = (playerId: string, players: Player[]) => {
        return players.filter(p => p.id !== playerId);
      };
      
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
      ];
      
      const afterFirstKick = processKick('player-1', players);
      const afterSecondKick = processKick('player-1', afterFirstKick);
      
      expect(afterSecondKick).toHaveLength(0);
    });

    it('handles concurrent kick and leave for same player', () => {
      const processRemoval = (playerId: string, players: Player[]) => {
        return players.filter(p => p.id !== playerId);
      };
      
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const afterKick = processRemoval('player-1', players);
      const afterLeave = processRemoval('player-1', afterKick);
      
      expect(afterLeave).toHaveLength(1);
      expect(afterLeave[0].id).toBe('player-2');
    });

    it('handles kick during answer submission', () => {
      const player = createPlayer({ 
        id: 'player-1', 
        name: 'Alice', 
        lastAnswerCorrect: null,
        selectedShape: null,
      });
      
      const players = [player];
      const updatedPlayers = players.filter(p => p.id !== 'player-1');
      
      expect(updatedPlayers).toHaveLength(0);
    });

    it('handles multiple rapid kicks', () => {
      const processKick = (playerId: string, players: Player[]) => {
        return players.filter(p => p.id !== playerId);
      };
      
      let players = Array.from({ length: 10 }, (_, i) => 
        createPlayer({ id: `player-${i}`, name: `Player ${i}` })
      );
      
      for (let i = 0; i < 10; i++) {
        players = processKick(`player-${i}`, players);
      }
      
      expect(players).toHaveLength(0);
    });

    it('validates player id before kick', () => {
      const isValidKickTarget = (playerId: string, role: 'HOST' | 'CLIENT') => {
        if (role !== 'HOST') return false;
        if (playerId === 'host') return false;
        if (!playerId || playerId.trim() === '') return false;
        return true;
      };
      
      expect(isValidKickTarget('player-1', 'HOST')).toBe(true);
      expect(isValidKickTarget('player-1', 'CLIENT')).toBe(false);
      expect(isValidKickTarget('host', 'HOST')).toBe(false);
      expect(isValidKickTarget('', 'HOST')).toBe(false);
      expect(isValidKickTarget('   ', 'HOST')).toBe(false);
    });

    it('handles kick when game is in PODIUM state', () => {
      const canKickInState = (gameState: GameState) => {
        return gameState !== 'PODIUM';
      };
      
      expect(canKickInState('LOBBY')).toBe(true);
      expect(canKickInState('QUESTION')).toBe(true);
      expect(canKickInState('PODIUM')).toBe(false);
    });

    it('handles leave when game transitions state', () => {
      const canLeaveInState = (gameState: GameState) => {
        const nonLeavableStates: GameState[] = ['PODIUM', 'LANDING'];
        return !nonLeavableStates.includes(gameState);
      };
      
      expect(canLeaveInState('LOBBY')).toBe(true);
      expect(canLeaveInState('QUESTION')).toBe(true);
      expect(canLeaveInState('SCOREBOARD')).toBe(true);
      expect(canLeaveInState('PODIUM')).toBe(false);
      expect(canLeaveInState('LANDING')).toBe(false);
    });

    it('preserves game integrity after player removal', () => {
      const validateGameIntegrity = (players: Player[]) => {
        const ids = players.map(p => p.id);
        const uniqueIds = new Set(ids);
        return ids.length === uniqueIds.size;
      };
      
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const afterRemoval = players.filter(p => p.id !== 'player-1');
      
      expect(validateGameIntegrity(afterRemoval)).toBe(true);
    });
  });

  describe('network message validation', () => {
    it('validates KICK message structure', () => {
      const isValidKickMessage = (message: unknown): boolean => {
        if (typeof message !== 'object' || message === null) return false;
        const msg = message as { type?: string; payload?: { playerId?: string } };
        return msg.type === 'KICK' && 
               typeof msg.payload?.playerId === 'string' &&
               msg.payload.playerId.length > 0;
      };
      
      expect(isValidKickMessage({ type: 'KICK', payload: { playerId: 'player-1' } })).toBe(true);
      expect(isValidKickMessage({ type: 'KICK', payload: { playerId: '' } })).toBe(false);
      expect(isValidKickMessage({ type: 'KICK', payload: {} })).toBe(false);
      expect(isValidKickMessage({ type: 'KICK' })).toBe(false);
      expect(isValidKickMessage({ type: 'OTHER', payload: { playerId: 'player-1' } })).toBe(false);
      expect(isValidKickMessage(null)).toBe(false);
      expect(isValidKickMessage(undefined)).toBe(false);
    });

    it('validates KICKED message structure', () => {
      const isValidKickedMessage = (message: unknown): boolean => {
        if (typeof message !== 'object' || message === null) return false;
        const msg = message as { type?: string; payload?: { reason?: string } };
        return msg.type === 'KICKED' && typeof msg.payload === 'object';
      };
      
      expect(isValidKickedMessage({ type: 'KICKED', payload: { reason: 'test' } })).toBe(true);
      expect(isValidKickedMessage({ type: 'KICKED', payload: {} })).toBe(true);
      expect(isValidKickedMessage({ type: 'KICKED' })).toBe(false);
      expect(isValidKickedMessage({ type: 'OTHER', payload: {} })).toBe(false);
    });

    it('validates PLAYER_LEFT message structure', () => {
      const isValidPlayerLeftMessage = (message: unknown): boolean => {
        if (typeof message !== 'object' || message === null) return false;
        const msg = message as { type?: string; payload?: { playerId?: string } };
        return msg.type === 'PLAYER_LEFT' && 
               typeof msg.payload?.playerId === 'string';
      };
      
      expect(isValidPlayerLeftMessage({ type: 'PLAYER_LEFT', payload: { playerId: 'player-1' } })).toBe(true);
      expect(isValidPlayerLeftMessage({ type: 'PLAYER_LEFT', payload: {} })).toBe(false);
      expect(isValidPlayerLeftMessage({ type: 'PLAYER_LEFT' })).toBe(false);
    });

    it('validates LEAVE message structure', () => {
      const isValidLeaveMessage = (message: unknown): boolean => {
        if (typeof message !== 'object' || message === null) return false;
        const msg = message as { type?: string; payload?: object };
        return msg.type === 'LEAVE' && typeof msg.payload === 'object';
      };
      
      expect(isValidLeaveMessage({ type: 'LEAVE', payload: {} })).toBe(true);
      expect(isValidLeaveMessage({ type: 'LEAVE' })).toBe(false);
      expect(isValidLeaveMessage({ type: 'OTHER', payload: {} })).toBe(false);
      expect(isValidLeaveMessage(null)).toBe(false);
    });
  });

  describe('host handling LEAVE message', () => {
    const processLeaveMessage = (
      leavingPlayerId: string,
      players: Player[],
      presenterId: string | null
    ): {
      updatedPlayers: Player[];
      newPresenterId: string | null;
      shouldBroadcastPlayerLeft: boolean;
      shouldBroadcastPresenterChanged: boolean;
    } => {
      const updatedPlayers = players.filter(p => p.id !== leavingPlayerId);
      const wasPresenter = presenterId === leavingPlayerId;
      
      let newPresenterId = presenterId;
      if (wasPresenter) {
        const realPlayers = updatedPlayers.filter(p => p.id !== 'host');
        newPresenterId = realPlayers.length > 0 ? realPlayers[0].id : null;
      }
      
      return {
        updatedPlayers,
        newPresenterId,
        shouldBroadcastPlayerLeft: true,
        shouldBroadcastPresenterChanged: wasPresenter,
      };
    };

    it('removes leaving player from players list', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const result = processLeaveMessage('player-1', players, null);
      
      expect(result.updatedPlayers).toHaveLength(1);
      expect(result.updatedPlayers[0].id).toBe('player-2');
    });

    it('broadcasts PLAYER_LEFT to remaining players', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const result = processLeaveMessage('player-1', players, null);
      
      expect(result.shouldBroadcastPlayerLeft).toBe(true);
    });

    it('reassigns presenter when leaving player was presenter', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const result = processLeaveMessage('player-1', players, 'player-1');
      
      expect(result.newPresenterId).toBe('player-2');
      expect(result.shouldBroadcastPresenterChanged).toBe(true);
    });

    it('preserves presenter when non-presenter leaves', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      
      const result = processLeaveMessage('player-2', players, 'player-1');
      
      expect(result.newPresenterId).toBe('player-1');
      expect(result.shouldBroadcastPresenterChanged).toBe(false);
    });

    it('sets presenter to null when last non-host player leaves', () => {
      const players = [
        createPlayer({ id: 'host', name: 'Host' }),
        createPlayer({ id: 'player-1', name: 'Alice' }),
      ];
      
      const result = processLeaveMessage('player-1', players, 'player-1');
      
      expect(result.newPresenterId).toBeNull();
      expect(result.shouldBroadcastPresenterChanged).toBe(true);
    });

    it('handles leave when player does not exist', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
      ];
      
      const result = processLeaveMessage('player-999', players, null);
      
      expect(result.updatedPlayers).toHaveLength(1);
      expect(result.shouldBroadcastPlayerLeft).toBe(true);
    });

    it('does not modify original players array', () => {
      const players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
      ];
      const originalLength = players.length;
      
      processLeaveMessage('player-1', players, null);
      
      expect(players).toHaveLength(originalLength);
    });

    it('handles multiple sequential leaves', () => {
      let players = [
        createPlayer({ id: 'player-1', name: 'Alice' }),
        createPlayer({ id: 'player-2', name: 'Bob' }),
        createPlayer({ id: 'player-3', name: 'Charlie' }),
      ];
      let presenterId: string | null = 'player-1';
      
      const result1 = processLeaveMessage('player-1', players, presenterId);
      players = result1.updatedPlayers;
      presenterId = result1.newPresenterId;
      
      const result2 = processLeaveMessage('player-2', players, presenterId);
      players = result2.updatedPlayers;
      presenterId = result2.newPresenterId;
      
      expect(players).toHaveLength(1);
      expect(players[0].id).toBe('player-3');
      expect(presenterId).toBe('player-3');
    });
  });
});
