import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

/**
 * Test suite for game reconnection scenarios
 * 
 * These tests verify the reconnection logic works correctly for:
 * 1. Player reconnection with score/answer preservation
 * 2. Host reconnection with game state preservation
 * 3. State synchronization between host and reconnecting players
 */
describe('Game Reconnection Logic', () => {
  describe('Player Matching on Reconnection', () => {
    const createMockPlayer = (overrides = {}) => ({
      id: 'player-123',
      name: 'TestPlayer',
      score: 500,
      previousScore: 0,
      streak: 2,
      lastAnswerCorrect: true,
      selectedShape: 'diamond' as const,
      pointsBreakdown: { basePoints: 450, streakBonus: 50, comebackBonus: 0, firstCorrectBonus: 0, penalty: 0, total: 500 },
      isBot: false,
      avatarSeed: 0.5,
      color: '#ff0000',
      ...overrides
    });

    it('should find existing player by previousId', () => {
      const players = [createMockPlayer({ id: 'old-peer-id' })];
      const payload = { name: 'TestPlayer', reconnect: true, previousId: 'old-peer-id' };
      
      const existingByPreviousId = payload.previousId 
        ? players.find(p => p.id === payload.previousId) 
        : null;
      
      expect(existingByPreviousId).toBeDefined();
      expect(existingByPreviousId?.score).toBe(500);
      expect(existingByPreviousId?.lastAnswerCorrect).toBe(true);
    });

    it('should find existing player by name when previousId not found', () => {
      const players = [createMockPlayer({ id: 'different-id' })];
      const payload = { name: 'TestPlayer', reconnect: true, previousId: 'non-existent-id' };
      
      const existingByPreviousId = payload.previousId 
        ? players.find(p => p.id === payload.previousId) 
        : null;
      const existingByName = players.find(p => p.name === payload.name && p.id !== 'host');
      const existingPlayer = existingByPreviousId || existingByName;
      
      expect(existingByPreviousId).toBeUndefined();
      expect(existingByName).toBeDefined();
      expect(existingPlayer?.score).toBe(500);
    });

    it('should preserve player score when reconnecting', () => {
      const players = [createMockPlayer({ score: 1500, streak: 3 })];
      const payload = { name: 'TestPlayer', reconnect: true, previousId: 'player-123' };
      
      const existingPlayer = players.find(p => p.id === payload.previousId);
      const reconnectedPlayer = existingPlayer 
        ? { ...existingPlayer, id: 'new-peer-id' }
        : null;
      
      expect(reconnectedPlayer?.score).toBe(1500);
      expect(reconnectedPlayer?.streak).toBe(3);
      expect(reconnectedPlayer?.id).toBe('new-peer-id');
    });

    it('should preserve lastAnswerCorrect when reconnecting', () => {
      const players = [createMockPlayer({ lastAnswerCorrect: true, pointsBreakdown: { total: 500 } as any })];
      const payload = { name: 'TestPlayer', reconnect: true, previousId: 'player-123' };
      
      const existingPlayer = players.find(p => p.id === payload.previousId);
      
      expect(existingPlayer?.lastAnswerCorrect).toBe(true);
      expect(existingPlayer?.pointsBreakdown?.total).toBe(500);
    });

    it('should NOT create duplicate player when reconnecting by name', () => {
      const players = [createMockPlayer({ id: 'old-id', name: 'TestPlayer' })];
      const payload = { name: 'TestPlayer', reconnect: true, previousId: 'wrong-id' };
      
      const existingByName = players.find(p => p.name === payload.name && p.id !== 'host');
      
      // Should find existing player, not create new one
      expect(existingByName).toBeDefined();
      expect(players.length).toBe(1);
    });

    it('should update player ID when reconnecting', () => {
      const oldId = 'old-peer-id';
      const newId = 'new-peer-id';
      const players = [createMockPlayer({ id: oldId })];
      
      const existingPlayer = players.find(p => p.id === oldId);
      const updatedPlayers = players.map(p => 
        p.id === oldId ? { ...p, id: newId } : p
      );
      
      expect(updatedPlayers[0].id).toBe(newId);
      expect(updatedPlayers[0].score).toBe(existingPlayer?.score);
    });
  });

  describe('WELCOME Message Construction', () => {
    it('should include hasAnswered when player already answered', () => {
      const reconnectedPlayer = {
        lastAnswerCorrect: true,
        pointsBreakdown: { total: 500 }
      };
      
      const welcomePayload: any = {};
      
      if (reconnectedPlayer) {
        welcomePayload.hasAnswered = reconnectedPlayer.lastAnswerCorrect !== null;
        welcomePayload.lastAnswerCorrect = reconnectedPlayer.lastAnswerCorrect;
        if (reconnectedPlayer.pointsBreakdown) {
          welcomePayload.lastPointsEarned = reconnectedPlayer.pointsBreakdown.total;
        }
      }
      
      expect(welcomePayload.hasAnswered).toBe(true);
      expect(welcomePayload.lastAnswerCorrect).toBe(true);
      expect(welcomePayload.lastPointsEarned).toBe(500);
    });

    it('should include hasAnswered=false when player has not answered', () => {
      const reconnectedPlayer = {
        lastAnswerCorrect: null,
        pointsBreakdown: null
      };
      
      const welcomePayload: any = {};
      
      if (reconnectedPlayer) {
        welcomePayload.hasAnswered = reconnectedPlayer.lastAnswerCorrect !== null;
      }
      
      expect(welcomePayload.hasAnswered).toBe(false);
    });

    it('should include question data for mid-game reconnection', () => {
      const currentQuestion = {
        text: 'What is 2+2?',
        options: [
          { text: '3', isCorrect: false, shape: 'triangle', color: 'red' },
          { text: '4', isCorrect: true, shape: 'diamond', color: 'blue' },
        ]
      };
      
      const welcomePayload: any = {
        gameState: 'QUESTION',
        currentQuestionIndex: 1,
        totalQuestions: 5,
        timeLeft: 15000,
      };
      
      if (currentQuestion) {
        welcomePayload.questionText = currentQuestion.text;
        const correctOpt = currentQuestion.options.find(o => o.isCorrect);
        welcomePayload.correctShape = correctOpt?.shape;
        // Mask correct answer from client
        welcomePayload.options = currentQuestion.options.map(o => ({
          ...o,
          isCorrect: false
        }));
      }
      
      expect(welcomePayload.questionText).toBe('What is 2+2?');
      expect(welcomePayload.correctShape).toBe('diamond');
      expect(welcomePayload.options.every((o: any) => o.isCorrect === false)).toBe(true);
    });

    it('should include player scores and streak', () => {
      const reconnectedPlayer = {
        score: 1500,
        streak: 3,
        lastAnswerCorrect: true,
        pointsBreakdown: { total: 500 }
      };
      
      const welcomePayload: any = {};
      
      if (reconnectedPlayer) {
        welcomePayload.score = reconnectedPlayer.score;
        welcomePayload.streak = reconnectedPlayer.streak;
      }
      
      expect(welcomePayload.score).toBe(1500);
      expect(welcomePayload.streak).toBe(3);
    });
  });

  describe('Client State Sync on WELCOME', () => {
    it('should set QUESTION state when host is in QUESTION and player answered', () => {
      const payload = {
        gameState: 'QUESTION' as const,
        hasAnswered: true,
      };
      
      let resultState: string;
      
      if (payload.gameState === 'QUESTION' || payload.gameState === 'COUNTDOWN') {
        resultState = payload.gameState;
      } else {
        resultState = 'LOBBY';
      }
      
      expect(resultState).toBe('QUESTION');
    });

    it('should set REVEAL state when host is in REVEAL', () => {
      const payload = {
        gameState: 'REVEAL' as const,
        hasAnswered: true,
      };
      
      let resultState: string;
      
      if (payload.gameState === 'REVEAL') {
        resultState = 'REVEAL';
      } else {
        resultState = 'LOBBY';
      }
      
      expect(resultState).toBe('REVEAL');
    });

    it('should set SCOREBOARD state when host is in SCOREBOARD', () => {
      const payload = {
        gameState: 'SCOREBOARD' as const,
      };
      
      let resultState: string;
      
      if (payload.gameState === 'SCOREBOARD') {
        resultState = 'SCOREBOARD';
      } else {
        resultState = 'LOBBY';
      }
      
      expect(resultState).toBe('SCOREBOARD');
    });

    it('should reconstruct quiz from WELCOME payload', () => {
      const payload = {
        quizTitle: 'Test Quiz',
        totalQuestions: 5,
        currentQuestionIndex: 2,
        questionText: 'What is 2+2?',
        options: [
          { text: '4', shape: 'diamond', color: 'blue', isCorrect: false }
        ]
      };
      
      const questions: any[] = [];
      for (let i = 0; i < payload.totalQuestions; i++) {
        if (i === payload.currentQuestionIndex) {
          questions.push({
            id: `q-${i}`,
            text: payload.questionText,
            options: payload.options,
            timeLimit: 30
          });
        } else {
          questions.push({ id: `loading-${i}`, text: '', options: [], timeLimit: 0 });
        }
      }
      
      const quiz = { title: payload.quizTitle, questions };
      
      expect(quiz.questions.length).toBe(5);
      expect(quiz.questions[2].text).toBe('What is 2+2?');
      expect(quiz.questions[0].text).toBe('');
    });
  });

  describe('Host Reconnection - Game Resume', () => {
    it('should set HOST_RECONNECTED state when game was mid-play', () => {
      const hostData = {
        gameState: 'QUESTION',
        currentQuestionIndex: 2,
      };
      
      let resultState: string;
      
      if (hostData.gameState === 'LOBBY') {
        resultState = 'LOBBY';
      } else if (hostData.gameState === 'PODIUM') {
        resultState = 'PODIUM';
      } else {
        resultState = 'HOST_RECONNECTED';
      }
      
      expect(resultState).toBe('HOST_RECONNECTED');
    });

    it('should preserve currentQuestionIndex on host reconnect', () => {
      const hostData = {
        currentQuestionIndex: 3,
        quiz: { questions: [{}, {}, {}, {}, {}] }
      };
      
      expect(hostData.currentQuestionIndex).toBe(3);
    });

    it('should restore host player score on reconnect', () => {
      const hostData = {
        players: [
          { id: 'host', name: 'Host', score: 1200, streak: 4 }
        ]
      };
      
      const hostPlayer = hostData.players.find(p => p.id === 'host');
      
      expect(hostPlayer?.score).toBe(1200);
      expect(hostPlayer?.streak).toBe(4);
    });

    it('should clear non-host players on host reconnect (they will rejoin)', () => {
      const hostData = {
        players: [
          { id: 'host', name: 'Host', score: 1200 },
          { id: 'player1', name: 'Player1', score: 800 },
          { id: 'player2', name: 'Player2', score: 600 },
        ]
      };
      
      const hostPlayer = hostData.players.find(p => p.id === 'host');
      const restoredPlayers = hostPlayer ? [hostPlayer] : [];
      
      // Only host should be in players array after reconnect
      // Other players will rejoin and be matched by name
      expect(restoredPlayers.length).toBe(1);
      expect(restoredPlayers[0].id).toBe('host');
    });
  });

  describe('Host Answer State on Resume', () => {
    it('should preserve hasAnswered=true if host already answered before disconnect', () => {
      const hostPlayer = {
        id: 'host',
        lastAnswerCorrect: true,
        pointsBreakdown: { total: 500 }
      };
      
      const hostAlreadyAnswered = hostPlayer.lastAnswerCorrect !== null;
      
      expect(hostAlreadyAnswered).toBe(true);
    });

    it('should allow host to answer if they had not answered before disconnect', () => {
      const hostPlayer = {
        id: 'host',
        lastAnswerCorrect: null,
        pointsBreakdown: null
      };
      
      const hostAlreadyAnswered = hostPlayer.lastAnswerCorrect !== null;
      
      expect(hostAlreadyAnswered).toBe(false);
    });

    it('startQuestion with isResume=true should preserve answer state', () => {
      const players = [
        { id: 'host', lastAnswerCorrect: true, score: 500 },
        { id: 'player1', lastAnswerCorrect: false, score: 0 },
        { id: 'player2', lastAnswerCorrect: null, score: 300 }, // Didn't answer yet
      ];
      
      const isResume = true;
      
      // When resuming, players who already answered should keep their state
      const updatedPlayers = isResume 
        ? players // Keep all states on resume
        : players.map(p => ({ ...p, lastAnswerCorrect: null, pointsBreakdown: null }));
      
      expect(updatedPlayers[0].lastAnswerCorrect).toBe(true);
      expect(updatedPlayers[1].lastAnswerCorrect).toBe(false);
    });

    it('startQuestion with isResume=false should reset all answer states', () => {
      const players = [
        { id: 'host', lastAnswerCorrect: true, score: 500 },
        { id: 'player1', lastAnswerCorrect: false, score: 0 },
      ];
      
      const isResume = false;
      
      const updatedPlayers = isResume 
        ? players
        : players.map(p => ({ ...p, lastAnswerCorrect: null, pointsBreakdown: null }));
      
      expect(updatedPlayers[0].lastAnswerCorrect).toBe(null);
      expect(updatedPlayers[1].lastAnswerCorrect).toBe(null);
    });
  });

  describe('Disconnection State Transitions', () => {
    it('should transition to DISCONNECTED state on connection close', () => {
      const currentGameState: string = 'QUESTION';
      let newState = currentGameState;
      
      // Simulating conn.on('close') handler
      if (currentGameState !== 'PODIUM') {
        newState = 'DISCONNECTED';
      }
      
      expect(newState).toBe('DISCONNECTED');
    });

    it('should NOT transition to DISCONNECTED if game is at PODIUM', () => {
      const currentGameState: string = 'PODIUM';
      let newState = currentGameState;
      
      if (currentGameState !== 'PODIUM') {
        newState = 'DISCONNECTED';
      }
      
      expect(newState).toBe('PODIUM');
    });

    it('should preserve player name and game PIN when disconnected', () => {
      const gameState = {
        currentPlayerName: 'TestPlayer',
        gamePin: '123456',
      };
      
      // On disconnect, these should NOT be cleared - only gameState changes to DISCONNECTED
      expect(gameState.currentPlayerName).toBe('TestPlayer');
      expect(gameState.gamePin).toBe('123456');
    });
  });
});

describe('Session Storage Logic', () => {
  const SESSION_STORAGE_KEY = 'kaboot_session';
  
  const getStoredSession = (storage: Map<string, string>) => {
    const stored = storage.get(SESSION_STORAGE_KEY);
    return stored ? JSON.parse(stored) : null;
  };
  
  const storeSession = (storage: Map<string, string>, session: any) => {
    storage.set(SESSION_STORAGE_KEY, JSON.stringify(session));
  };
  
  const clearStoredSession = (storage: Map<string, string>) => {
    storage.delete(SESSION_STORAGE_KEY);
  };

  it('should store session with all required fields for client', () => {
    const storage = new Map<string, string>();
    const session = {
      pin: '123456',
      role: 'CLIENT' as const,
      playerName: 'TestPlayer',
      playerId: 'peer-123'
    };
    
    storeSession(storage, session);
    
    const stored = getStoredSession(storage);
    expect(stored.pin).toBe('123456');
    expect(stored.role).toBe('CLIENT');
    expect(stored.playerName).toBe('TestPlayer');
    expect(stored.playerId).toBe('peer-123');
  });

  it('should store session with hostSecret for host', () => {
    const storage = new Map<string, string>();
    const session = {
      pin: '123456',
      role: 'HOST' as const,
      hostSecret: 'secret-abc123'
    };
    
    storeSession(storage, session);
    
    const stored = getStoredSession(storage);
    expect(stored.hostSecret).toBe('secret-abc123');
  });

  it('should update playerId on reconnection', () => {
    const storage = new Map<string, string>();
    const oldSession = {
      pin: '123456',
      role: 'CLIENT' as const,
      playerName: 'TestPlayer',
      playerId: 'old-peer-id'
    };
    
    storeSession(storage, oldSession);
    
    // Simulate reconnection - update playerId while preserving other fields
    const newSession = { ...oldSession, playerId: 'new-peer-id' };
    storeSession(storage, newSession);
    
    const stored = getStoredSession(storage);
    expect(stored.playerId).toBe('new-peer-id');
    expect(stored.playerName).toBe('TestPlayer'); // Name should be preserved
  });

  it('should clear session on goHomeFromDisconnected', () => {
    const storage = new Map<string, string>();
    const session = {
      pin: '123456',
      role: 'CLIENT' as const,
      playerName: 'TestPlayer',
    };
    
    storeSession(storage, session);
    expect(getStoredSession(storage)).not.toBeNull();
    
    // Simulate goHomeFromDisconnected
    clearStoredSession(storage);
    
    expect(getStoredSession(storage)).toBeNull();
  });
});
