package com.example.boidelov3.games.papelito;

import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;

import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;

/**
 * Tests unitaires pour la classe PapelitoGame.
 * Couvre l'initialisation, le vote, les conditions de victoire et la gestion des joueurs.
 */
public class PapelitoGameTest {

    private PapelitoGame game;
    private PapelitoPlayer player1;
    private PapelitoPlayer player2;
    private PapelitoPlayer player3;
    private PapelitoPlayer player4;
    private PapelitoPlayer player5;

    @Before
    public void setUp() {
        game = new PapelitoGame();
        player1 = new PapelitoPlayer("Alice");
        player2 = new PapelitoPlayer("Bob");
        player3 = new PapelitoPlayer("Charlie");
        player4 = new PapelitoPlayer("David");
        player5 = new PapelitoPlayer("Eve");
    }

    // ========== SETUP TESTS ==========

    @Test
    public void setupGame_withValidParameters_initializesCorrectly() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie", "David");
        int undercoverCount = 1;

        // Act
        game.setupGame(playerNames, undercoverCount);

        // Assert
        assertEquals("Game state should be DISCUSSION after setup",
                PapelitoGame.GameState.DISCUSSION, game.getGameState());
        assertEquals("Should have 4 players", 4, game.getPlayers().size());
        assertEquals("Should have 4 alive players", 4, game.getAlivePlayers().size());
        assertTrue("Should have selected a civil word", game.getCurrentCivilWord() != null && !game.getCurrentCivilWord().isEmpty());
        assertTrue("Should have selected an undercover word", game.getCurrentUndercoverWord() != null && !game.getCurrentUndercoverWord().isEmpty());
        assertNotEquals("Civil and undercover words should be different",
                game.getCurrentCivilWord(), game.getCurrentUndercoverWord());
    }

    @Test(expected = IllegalArgumentException.class)
    public void setupGame_withTooManyUndercovers_throwsException() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob");
        int undercoverCount = 3; // More than number of players

        // Act
        game.setupGame(playerNames, undercoverCount);
    }

    @Test(expected = IllegalArgumentException.class)
    public void setupGame_withEmptyPlayerList_throwsException() {
        // Arrange
        List<String> playerNames = new ArrayList<>();
        int undercoverCount = 1;

        // Act
        game.setupGame(playerNames, undercoverCount);
    }

    @Test
    public void setupGame_assignsRolesCorrectly() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
        int undercoverCount = 2;

        // Act
        game.setupGame(playerNames, undercoverCount);

        // Assert
        int civils = 0;
        int undercovers = 0;

        for (PapelitoPlayer player : game.getPlayers()) {
            assertNotNull("Player should have a role", player.getRole());
            if (player.isCivil()) {
                civils++;
                assertEquals("Civil should know civil word", game.getCurrentCivilWord(), player.getSecretWord());
            } else if (player.isUndercover()) {
                undercovers++;
                assertEquals("Undercover should know undercover word", game.getCurrentUndercoverWord(), player.getSecretWord());
            }
        }

        assertEquals("Should have correct number of undercovers", undercoverCount, undercovers);
        assertEquals("Should have correct number of civils", playerNames.size() - undercoverCount, civils);
    }

    @Test
    public void setupGame_selectsRandomWordPair() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob");

        // Act multiple times to test randomness
        game.setupGame(playerNames, 1);
        String civilWord1 = game.getCurrentCivilWord();
        String undercoverWord1 = game.getCurrentUndercoverWord();

        game.reset();
        game.setupGame(playerNames, 1);
        String civilWord2 = game.getCurrentCivilWord();
        String undercoverWord2 = game.getCurrentUndercoverWord();

        // Assert - may sometimes pick same pair, but should have different combinations
        assertTrue("Should have valid civil words", civilWord1 != null && !civilWord1.isEmpty());
        assertTrue("Should have valid undercover words", undercoverWord1 != null && !undercoverWord1.isEmpty());
    }

    // ========== VOTING TESTS ==========

    @Test
    public void vote_alivePlayerOnAlivePlayer_returnsTrue() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie");
        game.setupGame(playerNames, 1);
        PapelitoPlayer alivePlayer1 = game.getAlivePlayers().get(0);
        PapelitoPlayer alivePlayer2 = game.getAlivePlayers().get(1);

        // Set game state to VOTING
        game.setGameState(PapelitoGame.GameState.VOTING);

        // Act
        boolean result = game.vote(alivePlayer1, alivePlayer2);

        // Assert
        assertTrue("Vote should succeed when both players are alive", result);
        assertEquals("Voted player should have 1 vote", 1, alivePlayer2.getVotesReceived());
    }

    @Test
    public void vote_deadPlayer_returnsFalse() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie");
        game.setupGame(playerNames, 1);
        PapelitoPlayer deadPlayer = game.getPlayers().get(0);
        PapelitoPlayer alivePlayer = game.getAlivePlayers().get(0);

        deadPlayer.eliminate();
        game.setGameState(PapelitoGame.GameState.VOTING);

        // Act
        boolean result = game.vote(deadPlayer, alivePlayer);

        // Assert
        assertFalse("Vote should fail when voter is dead", result);
    }

    @Test
    public void vote_onDeadPlayer_returnsFalse() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie");
        game.setupGame(playerNames, 1);
        PapelitoPlayer aliveVoter = game.getAlivePlayers().get(0);
        PapelitoPlayer deadPlayer = game.getPlayers().get(0);

        deadPlayer.eliminate();
        game.setGameState(PapelitoGame.GameState.VOTING);

        // Act
        boolean result = game.vote(aliveVoter, deadPlayer);

        // Assert
        assertFalse("Vote should fail when voting for dead player", result);
    }

    @Test
    public void eliminateMostVoted_withClearWinner_returnsCorrectPlayer() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie");
        game.setupGame(playerNames, 1);
        game.setGameState(PapelitoGame.GameState.VOTING);

        PapelitoPlayer player1 = game.getAlivePlayers().get(0);
        PapelitoPlayer player2 = game.getAlivePlayers().get(1);
        PapelitoPlayer player3 = game.getAlivePlayers().get(2);

        // Give player2 more votes
        game.vote(player1, player2);
        game.vote(player3, player2);
        game.vote(player1, player3); // Player3 has 1 vote

        // Act
        PapelitoPlayer eliminated = game.eliminateMostVoted();

        // Assert
        assertNotNull("Should eliminate a player", eliminated);
        assertEquals("Player2 should have most votes", 2, eliminated.getVotesReceived());
        assertFalse("Player should not be alive", eliminated.isAlive());
        assertEquals("Should have 2 alive players", 2, game.getAlivePlayers().size());
        assertFalse("Player should not be in alive players", game.getAlivePlayers().contains(eliminated));
    }

    @Test
    public void eliminateMostVoted_withNoVotes_returnsNull() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob");
        game.setupGame(playerNames, 1);

        // Act
        PapelitoPlayer eliminated = game.eliminateMostVoted();

        // Assert
        assertNull("Should return null when no votes", eliminated);
        assertEquals("Both players should still be alive", 2, game.getAlivePlayers().size());
    }

    @Test
    public void eliminateMostVoted_removesFromAlivePlayers() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie");
        game.setupGame(playerNames, 1);
        game.setGameState(PapelitoGame.GameState.VOTING);

        PapelitoPlayer player = game.getAlivePlayers().get(0);
        game.vote(game.getAlivePlayers().get(1), player);
        game.vote(game.getAlivePlayers().get(2), player);

        // Act
        PapelitoPlayer eliminated = game.eliminateMostVoted();

        // Assert
        assertEquals("Should remove player from alive list", 2, game.getAlivePlayers().size());
        assertFalse("Player should not be in alive players", game.getAlivePlayers().contains(eliminated));
        assertFalse("Player should be dead", eliminated.isAlive());
    }

    @Test
    public void resetVotes_clearsAllPlayerVotes() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie");
        game.setupGame(playerNames, 1);

        // Add some votes
        PapelitoPlayer voter1 = game.getAlivePlayers().get(0);
        PapelitoPlayer voter2 = game.getAlivePlayers().get(1);
        PapelitoPlayer voter3 = game.getAlivePlayers().get(2);

        game.vote(voter1, voter2);
        game.vote(voter2, voter3);
        game.vote(voter3, voter1);

        // Act
        game.resetVotes();

        // Assert
        for (PapelitoPlayer player : game.getPlayers()) {
            assertEquals("All votes should be reset", 0, player.getVotesReceived());
        }
    }

    // ========== WIN CONDITION TESTS ==========

    @Test
    public void checkGameOver_noUndercovers_returnsTrue_civilsWin() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie");
        game.setupGame(playerNames, 1); // One undercover

        // Eliminate the undercover using game method
        game.setGameState(PapelitoGame.GameState.VOTING);
        PapelitoPlayer undercover = null;
        PapelitoPlayer civil1 = null;
        PapelitoPlayer civil2 = null;

        for (PapelitoPlayer player : game.getAlivePlayers()) {
            if (player.isUndercover()) {
                undercover = player;
            } else {
                if (civil1 == null) civil1 = player;
                else civil2 = player;
            }
        }

        game.vote(civil1, undercover);
        game.eliminateMostVoted();

        // Act
        boolean result = game.checkGameOver();

        // Assert
        assertTrue("Game should be over when no undercovers remain", result);
        assertEquals("Game state should be GAME_OVER", PapelitoGame.GameState.GAME_OVER, game.getGameState());
    }

    @Test
    public void checkGameOver_undercoversEqualCivils_returnsTrue_undercoversWin() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie");
        game.setupGame(playerNames, 2); // 2 undercovers, 1 civil initially

        // Kill the civil using game method
        game.setGameState(PapelitoGame.GameState.VOTING);

        // Count initial state
        int initialUndercovers = game.getAliveUndercoversCount();
        int initialCivils = game.getAliveCivilsCount();

        PapelitoPlayer civil = null;
        PapelitoPlayer undercover1 = null;
        PapelitoPlayer undercover2 = null;

        for (PapelitoPlayer player : game.getAlivePlayers()) {
            if (player.isCivil()) {
                civil = player;
            } else {
                if (undercover1 == null) undercover1 = player;
                else undercover2 = player;
            }
        }

        game.vote(undercover1, civil);
        game.eliminateMostVoted();

        // Act
        boolean result = game.checkGameOver();

        // Assert
        assertTrue("Game should be over when undercovers equal civils", result);
        assertEquals("Game state should be GAME_OVER", PapelitoGame.GameState.GAME_OVER, game.getGameState());
    }

    @Test
    public void checkGameOver_undercoversMoreThanCivils_returnsTrue_undercoversWin() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
        game.setupGame(playerNames, 3);

        // Set game state to voting
        game.setGameState(PapelitoGame.GameState.VOTING);

        // Find actual civils and undercovers (roles are assigned randomly)
        PapelitoPlayer civil1 = null;
        PapelitoPlayer civil2 = null;
        PapelitoPlayer undercover1 = null;
        PapelitoPlayer undercover2 = null;
        PapelitoPlayer undercover3 = null;

        for (PapelitoPlayer player : game.getAlivePlayers()) {
            if (player.isCivil()) {
                if (civil1 == null) civil1 = player;
                else if (civil2 == null) civil2 = player;
            } else if (player.isUndercover()) {
                if (undercover1 == null) undercover1 = player;
                else if (undercover2 == null) undercover2 = player;
                else if (undercover3 == null) undercover3 = player;
            }
        }

        // We should have 2 civils and 3 undercovers
        assertNotNull("Should have at least 1 civil", civil1);
        assertNotNull("Should have at least 2 civils", civil2);
        assertNotNull("Should have at least 1 undercover", undercover1);
        assertNotNull("Should have at least 2 undercovers", undercover2);
        assertNotNull("Should have at least 3 undercovers", undercover3);

        // Eliminate both civils
        game.vote(undercover1, civil1);
        game.eliminateMostVoted();

        game.vote(undercover2, civil2);
        game.eliminateMostVoted();

        // Act - Now we have 0 civils and 3 undercovers
        boolean result = game.checkGameOver();

        // Assert
        assertTrue("Game should be over when undercovers more than civils", result);
        assertEquals("Game state should be GAME_OVER", PapelitoGame.GameState.GAME_OVER, game.getGameState());
    }

    @Test
    public void checkGameOver_undercoversLessThanCivils_returnsFalse() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
        game.setupGame(playerNames, 2);

        // Act
        boolean result = game.checkGameOver();

        // Assert
        assertFalse("Game should continue when undercovers less than civils", result);
    }

    @Test
    public void getWinningTeam_civilsWin_returnsCivil() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie");
        game.setupGame(playerNames, 1); // One undercover

        // Kill the undercover using game method
        game.setGameState(PapelitoGame.GameState.VOTING);
        PapelitoPlayer undercover = null;
        PapelitoPlayer civil1 = null;
        PapelitoPlayer civil2 = null;

        for (PapelitoPlayer player : game.getAlivePlayers()) {
            if (player.isUndercover()) {
                undercover = player;
            } else {
                if (civil1 == null) civil1 = player;
                else civil2 = player;
            }
        }

        game.vote(civil1, undercover);
        game.eliminateMostVoted();

        // Act
        PapelitoPlayer.Role winner = game.getWinningTeam();

        // Assert
        assertEquals("Should return Civil as winning team", PapelitoPlayer.Role.CIVIL, winner);
    }

    @Test
    public void getWinningTeam_undercoversWin_returnsUndercover() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob");
        game.setupGame(playerNames, 1);

        // Find civil and undercover (roles are assigned randomly)
        PapelitoPlayer civil = null;
        PapelitoPlayer undercover = null;
        for (PapelitoPlayer p : game.getAlivePlayers()) {
            if (p.isCivil()) {
                civil = p;
            } else if (p.isUndercover()) {
                undercover = p;
            }
        }
        
        assertNotNull("Civil should exist", civil);
        assertNotNull("Undercover should exist", undercover);

        game.setGameState(PapelitoGame.GameState.VOTING);
        game.vote(undercover, civil);
        game.eliminateMostVoted();

        // Act
        PapelitoPlayer.Role winner = game.getWinningTeam();

        // Assert
        assertEquals("Should return Undercover as winning team", PapelitoPlayer.Role.UNDERCOVER, winner);
    }

    @Test
    public void getWinningTeam_gameContinues_returnsNull() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
        game.setupGame(playerNames, 2); // 2 undercovers, 3 civils

        // Ensure game continues by having both teams with players
        // At least one undercover and at least one civil alive
        int aliveUndercovers = game.getAliveUndercoversCount();
        int aliveCivils = game.getAliveCivilsCount();

        // Act
        PapelitoPlayer.Role winner = game.getWinningTeam();

        // Assert
        assertNull("Should return null when game continues", winner);
    }

    // ========== STATE TESTS ==========

    @Test
    public void getGameState_returnsInitialState() {
        // Act
        PapelitoGame.GameState state = game.getGameState();

        // Assert
        assertEquals("Initial state should be SETUP", PapelitoGame.GameState.SETUP, state);
    }

    @Test
    public void setGameState_changesState() {
        // Arrange - set to DISCUSSION first
        game.setGameState(PapelitoGame.GameState.DISCUSSION);

        // Act
        game.setGameState(PapelitoGame.GameState.VOTING);

        // Assert
        assertEquals("State should be VOTING", PapelitoGame.GameState.VOTING, game.getGameState());
    }

    @Test
    public void reset_clearsAllState() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob");
        game.setupGame(playerNames, 1);
        game.setGameState(PapelitoGame.GameState.VOTING);

        // Add some votes
        PapelitoPlayer voter = game.getAlivePlayers().get(0);
        PapelitoPlayer voted = game.getAlivePlayers().get(1);
        game.vote(voter, voted);

        // Act
        game.reset();

        // Assert
        assertEquals("Game state should be SETUP", PapelitoGame.GameState.SETUP, game.getGameState());
        assertEquals("Players list should be empty", 0, game.getPlayers().size());
        assertEquals("Alive players list should be empty", 0, game.getAlivePlayers().size());
    }

    // ========== PLAYER MANAGEMENT TESTS ==========

    @Test
    public void getPlayers_returnsDefensiveCopy() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob");
        game.setupGame(playerNames, 1);

        // Act
        List<PapelitoPlayer> players = game.getPlayers();

        // Assert
        assertEquals("Should have 2 players", 2, players.size());

        // Modify the returned list
        players.remove(0);

        // Original list should not be modified
        assertEquals("Original list should still have 2 players", 2, game.getPlayers().size());
    }

    @Test
    public void getAlivePlayers_returnsOnlyAlivePlayers() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie", "David");
        game.setupGame(playerNames, 2);
        game.setGameState(PapelitoGame.GameState.VOTING);

        // Kill one player using game method
        PapelitoPlayer playerToEliminate = game.getAlivePlayers().get(0);
        PapelitoPlayer voter = game.getAlivePlayers().get(1);
        PapelitoPlayer voter2 = game.getAlivePlayers().get(2);

        game.vote(voter, playerToEliminate);
        game.vote(voter2, playerToEliminate);
        game.eliminateMostVoted();

        // Act
        List<PapelitoPlayer> alivePlayers = game.getAlivePlayers();

        // Assert
        assertEquals("Should have 3 alive players", 3, alivePlayers.size());

        for (PapelitoPlayer player : alivePlayers) {
            assertTrue("All returned players should be alive", player.isAlive());
        }
    }

    @Test
    public void getAliveCivilsCount_returnsCorrectCount() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
        game.setupGame(playerNames, 2); // 2 undercovers, 3 civils

        // Kill one civil using game method
        game.setGameState(PapelitoGame.GameState.VOTING);
        PapelitoPlayer civilToKill = null;
        int civilVotes = 0;

        for (PapelitoPlayer player : game.getAlivePlayers()) {
            if (player.isCivil()) {
                if (civilToKill == null) civilToKill = player;
                civilVotes++;
            }
        }

        // Get 3 voters to vote for the civil
        List<PapelitoPlayer> voters = new ArrayList<>();
        for (PapelitoPlayer player : game.getAlivePlayers()) {
            if (player != civilToKill) {
                voters.add(player);
            }
        }

        game.vote(voters.get(0), civilToKill);
        game.vote(voters.get(1), civilToKill);
        game.eliminateMostVoted();

        // Act
        int civilsCount = game.getAliveCivilsCount();

        // Assert
        assertEquals("Should have 2 civils alive (3 total - 1 dead)", 2, civilsCount);
    }

    @Test
    public void getAliveUndercoversCount_returnsCorrectCount() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie", "David");
        game.setupGame(playerNames, 1);

        // Act
        int undercoversCount = game.getAliveUndercoversCount();

        // Assert
        assertEquals("Should have 1 undercover alive", 1, undercoversCount);
    }

    @Test
    public void getAliveCivilsCount_allCivilsDead() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie");
        game.setupGame(playerNames, 2); // 2 undercovers, 1 civil

        // Kill the civil using game method
        game.setGameState(PapelitoGame.GameState.VOTING);
        PapelitoPlayer civil = null;
        PapelitoPlayer undercover1 = null;
        PapelitoPlayer undercover2 = null;

        for (PapelitoPlayer player : game.getAlivePlayers()) {
            if (player.isCivil()) {
                civil = player;
            } else {
                if (undercover1 == null) undercover1 = player;
                else undercover2 = player;
            }
        }

        game.vote(undercover1, civil);
        game.vote(undercover2, civil);
        game.eliminateMostVoted();

        // Act
        int civilsCount = game.getAliveCivilsCount();

        // Assert
        assertEquals("Should have 0 civils alive", 0, civilsCount);
    }

    @Test
    public void getAliveUndercoversCount_allUndercoversDead() {
        // Arrange
        List<String> playerNames = Arrays.asList("Alice", "Bob", "Charlie");
        game.setupGame(playerNames, 1);
        game.setGameState(PapelitoGame.GameState.VOTING);

        // Kill the undercover using game method
        PapelitoPlayer undercover = null;
        PapelitoPlayer civil1 = null;
        PapelitoPlayer civil2 = null;

        for (PapelitoPlayer player : game.getAlivePlayers()) {
            if (player.isUndercover()) {
                undercover = player;
            } else {
                if (civil1 == null) civil1 = player;
                else civil2 = player;
            }
        }

        // Make sure we found the undercover
        assertNotNull("Should find an undercover player", undercover);

        game.vote(civil1, undercover);
        game.vote(civil2, undercover);
        game.eliminateMostVoted();

        // Act
        int undercoversCount = game.getAliveUndercoversCount();

        // Assert
        assertEquals("Should have 0 undercovers alive", 0, undercoversCount);
    }
}