"""Tests for the win conditions checker module.

This module tests all win condition checking functionality:
- check_win_conditions (main entry point)
- check_prizes_taken (point/prize victory)
- check_no_pokemon_in_play (knockout victory)
- check_cannot_draw (deck out victory)
- check_turn_limit (turn limit victory/draw)
- check_resignation (resignation handling)
- check_timeout (timeout handling)
- apply_win_result (applying results to game state)

Each test includes a docstring explaining the "what" and "why" of the test.
"""

import pytest

from app.core.config import PrizeConfig, RulesConfig, WinConditionsConfig
from app.core.enums import GameEndReason, TurnPhase
from app.core.models.game_state import GameState, PlayerState
from app.core.win_conditions import (
    WinResult,
    apply_win_result,
    check_cannot_draw,
    check_no_pokemon_in_play,
    check_prizes_taken,
    check_resignation,
    check_timeout,
    check_turn_limit,
    check_win_conditions,
)

# ============================================================================
# WinResult Model Tests
# ============================================================================


class TestWinResult:
    """Tests for the WinResult model."""

    def test_win_result_creation(self):
        """Test that WinResult can be created with all required fields.

        The WinResult model is the return type for all win condition checks.
        It must contain winner_id, loser_id, end_reason, and reason.
        """
        result = WinResult(
            winner_id="player1",
            loser_id="player2",
            end_reason=GameEndReason.PRIZES_TAKEN,
            reason="Player 1 scored 4 points",
        )

        assert result.winner_id == "player1"
        assert result.loser_id == "player2"
        assert result.end_reason == GameEndReason.PRIZES_TAKEN
        assert result.reason == "Player 1 scored 4 points"

    def test_win_result_serialization(self):
        """Test that WinResult serializes to JSON correctly.

        WinResult may be sent to clients or logged, so JSON serialization
        must work correctly.
        """
        result = WinResult(
            winner_id="player1",
            loser_id="player2",
            end_reason=GameEndReason.NO_POKEMON,
            reason="Player 2 has no Pokemon in play",
        )

        json_data = result.model_dump()
        assert json_data["winner_id"] == "player1"
        assert json_data["loser_id"] == "player2"
        assert json_data["end_reason"] == "no_pokemon"
        assert "no Pokemon" in json_data["reason"]


# ============================================================================
# check_prizes_taken Tests
# ============================================================================


class TestCheckPrizesTaken:
    """Tests for the check_prizes_taken function."""

    def test_player_wins_with_exact_points(self, extended_card_registry, card_instance_factory):
        """Test that a player wins when reaching exactly the required point count.

        The default rules require 4 points to win. When a player's score
        reaches exactly 4, they should win.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 4  # Exactly the required amount
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),  # 4 points to win
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_prizes_taken(game)

        assert result is not None
        assert result.winner_id == "player1"
        assert result.loser_id == "player2"
        assert result.end_reason == GameEndReason.PRIZES_TAKEN
        assert "4" in result.reason

    def test_player_wins_with_more_than_required_points(
        self, extended_card_registry, card_instance_factory
    ):
        """Test that a player wins when exceeding the required point count.

        This can happen if a VMAX (3 points) knockout pushes score over the limit.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 5  # More than 4 required
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_prizes_taken(game)

        assert result is not None
        assert result.winner_id == "player1"

    def test_no_winner_with_insufficient_points(
        self, extended_card_registry, card_instance_factory
    ):
        """Test that no winner is returned when neither player has enough points.

        This is the normal game state when both players have scored but
        neither has reached the winning threshold.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 2
        player2.score = 3
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),  # 4 points needed
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_prizes_taken(game)

        assert result is None

    def test_custom_point_threshold(self, extended_card_registry, card_instance_factory):
        """Test winning with a custom point threshold.

        Free play mode allows configuring different win thresholds.
        A player should win when reaching that custom threshold.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 6  # Custom threshold
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        rules = RulesConfig(prizes=PrizeConfig(count=6))

        game = GameState(
            game_id="test",
            rules=rules,
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_prizes_taken(game)

        assert result is not None
        assert result.winner_id == "player1"

    def test_prize_card_mode_all_prizes_taken(self, extended_card_registry, card_instance_factory):
        """Test winning in prize card mode when all prizes are taken.

        In classic Pokemon TCG mode (use_prize_cards=True), a player wins
        when their prizes zone is empty (all prizes taken).
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        # Player1 has taken all prizes (empty prize zone)
        player1.active.add(card_instance_factory("pikachu_base_001"))
        # prizes zone is empty by default

        # Player2 still has prizes
        player2.active.add(card_instance_factory("pikachu_base_001"))
        player2.prizes.add(card_instance_factory("pikachu_base_001"))

        rules = RulesConfig(prizes=PrizeConfig(use_prize_cards=True, count=6))

        game = GameState(
            game_id="test",
            rules=rules,
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_prizes_taken(game)

        assert result is not None
        assert result.winner_id == "player1"
        assert result.end_reason == GameEndReason.PRIZES_TAKEN
        assert "prize" in result.reason.lower()

    def test_prize_card_mode_no_winner_with_remaining_prizes(
        self, extended_card_registry, card_instance_factory
    ):
        """Test that no winner in prize card mode when prizes remain.

        Neither player should win if both still have prize cards to take.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        # Both players have prizes remaining
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player1.prizes.add(card_instance_factory("pikachu_base_001"))

        player2.active.add(card_instance_factory("pikachu_base_001"))
        player2.prizes.add(card_instance_factory("pikachu_base_001"))

        rules = RulesConfig(prizes=PrizeConfig(use_prize_cards=True, count=6))

        game = GameState(
            game_id="test",
            rules=rules,
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_prizes_taken(game)

        assert result is None

    def test_prize_card_mode_ignores_setup_phase(
        self, extended_card_registry, card_instance_factory
    ):
        """Test that empty prizes during setup don't trigger win.

        During setup, prizes haven't been dealt yet so the zone is empty.
        This should not count as a win condition.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        # Empty prizes during setup
        player1.hand.add(card_instance_factory("pikachu_base_001"))
        player2.hand.add(card_instance_factory("pikachu_base_001"))

        rules = RulesConfig(prizes=PrizeConfig(use_prize_cards=True, count=6))

        game = GameState(
            game_id="test",
            rules=rules,
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=0,
            phase=TurnPhase.SETUP,
        )

        result = check_prizes_taken(game)

        assert result is None

    def test_player2_wins(self, extended_card_registry, card_instance_factory):
        """Test that player2 can win via points.

        The win condition check should work for either player, not just
        the current player.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 2
        player2.score = 4  # Player2 wins
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",  # It's player1's turn but player2 won
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_prizes_taken(game)

        assert result is not None
        assert result.winner_id == "player2"
        assert result.loser_id == "player1"


# ============================================================================
# check_no_pokemon_in_play Tests
# ============================================================================


class TestCheckNoPokemonInPlay:
    """Tests for the check_no_pokemon_in_play function."""

    def test_player_loses_with_no_pokemon(self, extended_card_registry, card_instance_factory):
        """Test that a player loses when they have no Pokemon in play.

        If a player's active is knocked out and they have no bench Pokemon,
        they lose the game.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        # Player1 has Pokemon
        player1.active.add(card_instance_factory("pikachu_base_001"))

        # Player2 has no Pokemon in play
        # (active and bench are empty)

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_no_pokemon_in_play(game)

        assert result is not None
        assert result.winner_id == "player1"  # Opponent wins
        assert result.loser_id == "player2"  # No pokemon player loses
        assert result.end_reason == GameEndReason.NO_POKEMON

    def test_player_survives_with_bench_pokemon(
        self, extended_card_registry, card_instance_factory
    ):
        """Test that a player doesn't lose if they have bench Pokemon.

        Even with no active Pokemon, having bench Pokemon means the game
        continues (they must select a new active).
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))

        # Player2 has no active but has bench
        player2.bench.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_no_pokemon_in_play(game)

        assert result is None

    def test_player_survives_with_active_pokemon(
        self, extended_card_registry, card_instance_factory
    ):
        """Test that a player doesn't lose if they have an active Pokemon.

        The normal case - player has an active Pokemon.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_no_pokemon_in_play(game)

        assert result is None

    def test_skipped_during_setup_phase(self, extended_card_registry, card_instance_factory):
        """Test that no-pokemon check is skipped during setup.

        During setup phase, players are still placing their initial Pokemon.
        Empty boards are expected and should not trigger a loss.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        # Both players have no Pokemon (setup phase)
        player1.hand.add(card_instance_factory("pikachu_base_001"))
        player2.hand.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=0,
            phase=TurnPhase.SETUP,
        )

        result = check_no_pokemon_in_play(game)

        assert result is None

    def test_player1_loses_with_no_pokemon(self, extended_card_registry, card_instance_factory):
        """Test that player1 can lose via no Pokemon.

        The check should work for any player, not just player2.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        # Player1 has no Pokemon
        # Player2 has active
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_no_pokemon_in_play(game)

        assert result is not None
        assert result.winner_id == "player2"
        assert result.loser_id == "player1"


# ============================================================================
# check_cannot_draw Tests
# ============================================================================


class TestCheckCannotDraw:
    """Tests for the check_cannot_draw function."""

    def test_player_loses_with_empty_deck_at_draw_phase(
        self, extended_card_registry, card_instance_factory
    ):
        """Test that a player loses if they can't draw at turn start.

        In Pokemon TCG, a player loses if they must draw a card at the
        start of their turn but their deck is empty.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        # Player1's deck is empty

        player2.active.add(card_instance_factory("pikachu_base_001"))
        player2.deck.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.DRAW,  # Draw phase!
        )

        result = check_cannot_draw(game)

        assert result is not None
        assert result.winner_id == "player2"
        assert result.loser_id == "player1"
        assert result.end_reason == GameEndReason.DECK_EMPTY
        assert "cannot draw" in result.reason.lower() or "deck empty" in result.reason.lower()

    def test_player_survives_with_cards_in_deck(
        self, extended_card_registry, card_instance_factory
    ):
        """Test that a player doesn't lose if they have cards to draw.

        Normal case - player has cards in deck.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        player1.deck.add(card_instance_factory("pikachu_base_001"))

        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.DRAW,
        )

        result = check_cannot_draw(game)

        assert result is None

    def test_empty_deck_ignored_outside_draw_phase(
        self, extended_card_registry, card_instance_factory
    ):
        """Test that empty deck doesn't trigger loss outside draw phase.

        The deck-out loss only applies at the start of draw phase.
        During main phase, having an empty deck is fine (cards might
        be shuffled back in, etc.).
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        # Empty deck but not draw phase

        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,  # Not draw phase
        )

        result = check_cannot_draw(game)

        assert result is None

    def test_opponent_deck_empty_not_checked(self, extended_card_registry, card_instance_factory):
        """Test that opponent's empty deck doesn't affect current player.

        The deck-out check only applies to the current player who must draw.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        player1.deck.add(card_instance_factory("pikachu_base_001"))

        player2.active.add(card_instance_factory("pikachu_base_001"))
        # Player2's deck is empty but it's player1's turn

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.DRAW,
        )

        result = check_cannot_draw(game)

        assert result is None


# ============================================================================
# check_turn_limit Tests
# ============================================================================


class TestCheckTurnLimit:
    """Tests for the check_turn_limit function."""

    def test_higher_score_wins_at_turn_limit(self, extended_card_registry, card_instance_factory):
        """Test that the player with higher score wins at turn limit.

        When the turn limit is reached, the player with more points wins.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 3
        player2.score = 2
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        rules = RulesConfig(
            win_conditions=WinConditionsConfig(turn_limit_enabled=True, turn_limit=30)
        )

        game = GameState(
            game_id="test",
            rules=rules,
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=31,  # Exceeded limit
            phase=TurnPhase.MAIN,
        )

        result = check_turn_limit(game)

        assert result is not None
        assert result.winner_id == "player1"
        assert result.loser_id == "player2"
        assert result.end_reason == GameEndReason.TURN_LIMIT

    def test_player2_wins_with_higher_score(self, extended_card_registry, card_instance_factory):
        """Test that player2 wins if they have higher score at turn limit.

        The win goes to whoever has more points, regardless of who is
        the current player.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 1
        player2.score = 3
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        rules = RulesConfig(
            win_conditions=WinConditionsConfig(turn_limit_enabled=True, turn_limit=30)
        )

        game = GameState(
            game_id="test",
            rules=rules,
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=31,
            phase=TurnPhase.MAIN,
        )

        result = check_turn_limit(game)

        assert result is not None
        assert result.winner_id == "player2"
        assert result.loser_id == "player1"

    def test_draw_on_equal_scores(self, extended_card_registry, card_instance_factory):
        """Test that equal scores result in a draw at turn limit.

        If both players have the same score when turn limit is reached,
        the game ends in a draw.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 2
        player2.score = 2
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        rules = RulesConfig(
            win_conditions=WinConditionsConfig(turn_limit_enabled=True, turn_limit=30)
        )

        game = GameState(
            game_id="test",
            rules=rules,
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=31,
            phase=TurnPhase.MAIN,
        )

        result = check_turn_limit(game)

        assert result is not None
        assert result.winner_id == ""  # Draw
        assert result.loser_id == ""  # Draw
        assert result.end_reason == GameEndReason.DRAW
        assert "draw" in result.reason.lower()

    def test_no_check_when_turn_limit_disabled(self, extended_card_registry, card_instance_factory):
        """Test that turn limit is not checked when disabled.

        Campaign mode or infinite games may disable the turn limit.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 1
        player2.score = 2
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        rules = RulesConfig(win_conditions=WinConditionsConfig(turn_limit_enabled=False))

        game = GameState(
            game_id="test",
            rules=rules,
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=100,  # Way past any reasonable limit
            phase=TurnPhase.MAIN,
        )

        result = check_turn_limit(game)

        assert result is None

    def test_no_result_before_turn_limit(self, extended_card_registry, card_instance_factory):
        """Test that no result is returned before turn limit is reached.

        The turn limit check should only trigger when the limit is exceeded.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 1
        player2.score = 2
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        rules = RulesConfig(
            win_conditions=WinConditionsConfig(turn_limit_enabled=True, turn_limit=30)
        )

        game = GameState(
            game_id="test",
            rules=rules,
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=30,  # Exactly at limit, not exceeded
            phase=TurnPhase.MAIN,
        )

        result = check_turn_limit(game)

        assert result is None

    def test_custom_turn_limit(self, extended_card_registry, card_instance_factory):
        """Test that custom turn limit is respected.

        Different game modes may use different turn limits.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 1
        player2.score = 0
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        rules = RulesConfig(
            win_conditions=WinConditionsConfig(turn_limit_enabled=True, turn_limit=10)
        )

        game = GameState(
            game_id="test",
            rules=rules,
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=11,  # Exceeded custom limit of 10
            phase=TurnPhase.MAIN,
        )

        result = check_turn_limit(game)

        assert result is not None
        assert result.winner_id == "player1"


# ============================================================================
# check_resignation Tests
# ============================================================================


class TestCheckResignation:
    """Tests for the check_resignation function."""

    def test_resignation_creates_correct_result(
        self, extended_card_registry, card_instance_factory
    ):
        """Test that resignation creates the correct WinResult.

        When a player resigns, their opponent wins immediately.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_resignation(game, "player1")

        assert result.winner_id == "player2"
        assert result.loser_id == "player1"
        assert result.end_reason == GameEndReason.RESIGNATION
        assert "resign" in result.reason.lower()

    def test_player2_resignation(self, extended_card_registry, card_instance_factory):
        """Test that player2 can resign.

        Either player should be able to resign.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_resignation(game, "player2")

        assert result.winner_id == "player1"
        assert result.loser_id == "player2"

    def test_resignation_invalid_player_raises(self, extended_card_registry, card_instance_factory):
        """Test that resignation with invalid player ID raises error.

        The resigning player must be a valid player in the game.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        with pytest.raises(ValueError, match="not found"):
            check_resignation(game, "invalid_player")


# ============================================================================
# check_timeout Tests
# ============================================================================


class TestCheckTimeout:
    """Tests for the check_timeout function."""

    def test_timeout_creates_correct_result(self, extended_card_registry, card_instance_factory):
        """Test that timeout creates the correct WinResult.

        When a player times out, their opponent wins.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_timeout(game, "player1")

        assert result.winner_id == "player2"
        assert result.loser_id == "player1"
        assert result.end_reason == GameEndReason.TIMEOUT
        assert "timed out" in result.reason.lower()

    def test_player2_timeout(self, extended_card_registry, card_instance_factory):
        """Test that player2 can time out.

        Either player should be able to time out.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_timeout(game, "player2")

        assert result.winner_id == "player1"
        assert result.loser_id == "player2"

    def test_timeout_invalid_player_raises(self, extended_card_registry, card_instance_factory):
        """Test that timeout with invalid player ID raises error.

        The timed out player must be a valid player in the game.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        with pytest.raises(ValueError, match="not found"):
            check_timeout(game, "invalid_player")


# ============================================================================
# check_win_conditions (Main Entry Point) Tests
# ============================================================================


class TestCheckWinConditions:
    """Tests for the check_win_conditions main entry point."""

    def test_returns_none_when_no_win(self, extended_card_registry, card_instance_factory):
        """Test that None is returned when no win condition is met.

        This is the normal case during gameplay.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 1
        player2.score = 2
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player1.deck.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))
        player2.deck.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_win_conditions(game)

        assert result is None

    def test_returns_none_when_game_already_over(
        self, extended_card_registry, card_instance_factory
    ):
        """Test that None is returned when game is already over.

        Once a game has ended, win conditions should not be re-checked.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 4  # Would normally trigger win
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
            winner_id="player1",  # Game already over
            end_reason=GameEndReason.PRIZES_TAKEN,
        )

        result = check_win_conditions(game)

        assert result is None

    def test_detects_prizes_win(self, extended_card_registry, card_instance_factory):
        """Test that prizes/points win is detected.

        The main entry point should correctly delegate to check_prizes_taken.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 4
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))
        player2.deck.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_win_conditions(game)

        assert result is not None
        assert result.end_reason == GameEndReason.PRIZES_TAKEN

    def test_detects_no_pokemon_win(self, extended_card_registry, card_instance_factory):
        """Test that no-Pokemon win is detected.

        The main entry point should correctly delegate to check_no_pokemon_in_play.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        player1.deck.add(card_instance_factory("pikachu_base_001"))
        # Player2 has no Pokemon

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_win_conditions(game)

        assert result is not None
        assert result.end_reason == GameEndReason.NO_POKEMON
        assert result.winner_id == "player1"

    def test_detects_cannot_draw_win(self, extended_card_registry, card_instance_factory):
        """Test that cannot-draw win is detected.

        The main entry point should correctly delegate to check_cannot_draw.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        # Player1's deck is empty

        player2.active.add(card_instance_factory("pikachu_base_001"))
        player2.deck.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.DRAW,  # Must be draw phase
        )

        result = check_win_conditions(game)

        assert result is not None
        assert result.end_reason == GameEndReason.DECK_EMPTY
        assert result.winner_id == "player2"

    def test_prizes_win_takes_priority_over_no_pokemon(
        self, extended_card_registry, card_instance_factory
    ):
        """Test that prizes win is checked before no-Pokemon.

        If a player scores enough points and the opponent has no Pokemon,
        the points win should be detected first (though in practice both
        would mean the same winner).
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 4  # Winning score
        player1.active.add(card_instance_factory("pikachu_base_001"))
        # Player2 has no Pokemon (also a loss condition)

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_win_conditions(game)

        assert result is not None
        # Prizes should be checked first
        assert result.end_reason == GameEndReason.PRIZES_TAKEN

    def test_respects_disabled_conditions(self, extended_card_registry, card_instance_factory):
        """Test that disabled win conditions are not checked.

        Free play mode may disable certain win conditions.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 4  # Would normally win
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player1.deck.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))
        player2.deck.add(card_instance_factory("pikachu_base_001"))

        rules = RulesConfig(
            win_conditions=WinConditionsConfig(
                all_prizes_taken=False,  # Disabled!
                no_pokemon_in_play=True,
                cannot_draw=True,
            )
        )

        game = GameState(
            game_id="test",
            rules=rules,
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_win_conditions(game)

        # Prizes condition disabled, so no win
        assert result is None


# ============================================================================
# apply_win_result Tests
# ============================================================================


class TestApplyWinResult:
    """Tests for the apply_win_result function."""

    def test_applies_normal_win(self, extended_card_registry, card_instance_factory):
        """Test that a normal win result is applied correctly.

        The game state should be updated with winner_id and end_reason.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = WinResult(
            winner_id="player1",
            loser_id="player2",
            end_reason=GameEndReason.PRIZES_TAKEN,
            reason="Player 1 wins",
        )

        apply_win_result(game, result)

        assert game.winner_id == "player1"
        assert game.end_reason == GameEndReason.PRIZES_TAKEN
        assert game.is_game_over()

    def test_applies_draw_result(self, extended_card_registry, card_instance_factory):
        """Test that a draw result is applied correctly.

        For draws, winner_id should be None and end_reason should be DRAW.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = WinResult(
            winner_id="",
            loser_id="",
            end_reason=GameEndReason.DRAW,
            reason="Game ends in a draw",
        )

        apply_win_result(game, result)

        assert game.winner_id is None
        assert game.end_reason == GameEndReason.DRAW

    def test_applies_different_end_reasons(self, extended_card_registry, card_instance_factory):
        """Test that different end reasons are applied correctly.

        Each end reason type should be stored in the game state.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        for end_reason in [
            GameEndReason.PRIZES_TAKEN,
            GameEndReason.NO_POKEMON,
            GameEndReason.DECK_EMPTY,
            GameEndReason.RESIGNATION,
            GameEndReason.TIMEOUT,
            GameEndReason.TURN_LIMIT,
        ]:
            player1.active.add(card_instance_factory("pikachu_base_001"))
            player2.active.add(card_instance_factory("pikachu_base_001"))

            game = GameState(
                game_id="test",
                rules=RulesConfig(),
                card_registry=extended_card_registry,
                players={"player1": player1, "player2": player2},
                turn_order=["player1", "player2"],
                current_player_id="player1",
                turn_number=5,
                phase=TurnPhase.MAIN,
            )

            result = WinResult(
                winner_id="player1",
                loser_id="player2",
                end_reason=end_reason,
                reason="Test",
            )

            apply_win_result(game, result)

            assert game.end_reason == end_reason


# ============================================================================
# Integration / Edge Case Tests
# ============================================================================


class TestWinConditionsEdgeCases:
    """Edge case and integration tests for win conditions."""

    def test_simultaneous_win_conditions(self, extended_card_registry, card_instance_factory):
        """Test behavior when multiple win conditions are met.

        In Pokemon TCG, prizes/points is typically checked first. If player1
        knocks out player2's last Pokemon and reaches 4 points, prizes win
        should be reported (though both conditions are met).
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 4  # Winning points
        player1.active.add(card_instance_factory("pikachu_base_001"))
        # Player2 has no Pokemon (also a loss)

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_win_conditions(game)

        assert result is not None
        # Prizes checked first
        assert result.end_reason == GameEndReason.PRIZES_TAKEN
        assert result.winner_id == "player1"

    def test_zero_points_to_win(self, extended_card_registry, card_instance_factory):
        """Test edge case where 0 points are required to win.

        This would mean everyone wins immediately, but the game should
        handle it gracefully.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 0
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        rules = RulesConfig(prizes=PrizeConfig(count=0))

        game = GameState(
            game_id="test",
            rules=rules,
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=1,
            phase=TurnPhase.MAIN,
        )

        result = check_prizes_taken(game)

        # Both players have >= 0 points, first one checked wins
        assert result is not None

    def test_all_conditions_disabled(self, extended_card_registry, card_instance_factory):
        """Test that game continues when all win conditions disabled.

        While unusual, free play mode could disable all standard win
        conditions (perhaps for practice/testing).
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 100  # Would normally win many times over
        player1.active.add(card_instance_factory("pikachu_base_001"))
        # Empty deck
        # Player2 has no Pokemon

        rules = RulesConfig(
            win_conditions=WinConditionsConfig(
                all_prizes_taken=False,
                no_pokemon_in_play=False,
                cannot_draw=False,
            )
        )

        game = GameState(
            game_id="test",
            rules=rules,
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.DRAW,
        )

        result = check_win_conditions(game)

        # All conditions disabled, no winner
        assert result is None

    def test_win_result_reason_contains_player_id(
        self, extended_card_registry, card_instance_factory
    ):
        """Test that win result reasons include player IDs for clarity.

        The reason string should indicate which player won/lost for logging.
        """
        player1 = PlayerState(player_id="player1")
        player2 = PlayerState(player_id="player2")

        player1.score = 4
        player1.active.add(card_instance_factory("pikachu_base_001"))
        player2.active.add(card_instance_factory("pikachu_base_001"))

        game = GameState(
            game_id="test",
            rules=RulesConfig(),
            card_registry=extended_card_registry,
            players={"player1": player1, "player2": player2},
            turn_order=["player1", "player2"],
            current_player_id="player1",
            turn_number=5,
            phase=TurnPhase.MAIN,
        )

        result = check_prizes_taken(game)

        assert result is not None
        assert "player1" in result.reason.lower()
