"""Tests for the rules validation system.

This module tests all action validation logic to ensure the game engine
correctly enforces rules, turn order, phase restrictions, and action legality.

These tests are security-critical - they verify that invalid actions are rejected
and that players cannot cheat by performing illegal moves.
"""

from app.core.config import (
    ActiveConfig,
    EnergyConfig,
    EvolutionConfig,
    FirstTurnConfig,
    RetreatConfig,
    TrainerConfig,
)
from app.core.enums import (
    StatusCondition,
    TurnPhase,
)
from app.core.models.actions import (
    AttachEnergyAction,
    AttackAction,
    EvolvePokemonAction,
    PassAction,
    PlayPokemonAction,
    PlayTrainerAction,
    ResignAction,
    RetreatAction,
    SelectActiveAction,
    SelectPrizeAction,
    UseAbilityAction,
)
from app.core.models.card import CardInstance
from app.core.models.game_state import ForcedAction, GameState
from app.core.rules_validator import validate_action

# =============================================================================
# Test Universal Validation
# =============================================================================


class TestUniversalValidation:
    """Tests for universal validation checks applied to all actions."""

    def test_game_over_blocks_all_actions(self, game_over_state: GameState) -> None:
        """
        Verify that no actions are allowed when the game is over.

        Once a winner is declared, the game should reject all actions
        to prevent any further state changes.
        """
        action = PassAction()
        result = validate_action(game_over_state, "player1", action)

        assert result.valid is False
        assert "Game is over" in result.reason

    def test_wrong_player_turn_rejected(self, game_in_main_phase: GameState) -> None:
        """
        Verify that actions from the wrong player are rejected.

        Only the current player should be able to take actions
        (except resign which is always allowed).
        """
        # player1's turn, player2 tries to act
        action = PassAction()
        result = validate_action(game_in_main_phase, "player2", action)

        assert result.valid is False
        assert "Not your turn" in result.reason
        assert "player1" in result.reason

    def test_resign_allowed_on_opponents_turn(self, game_in_main_phase: GameState) -> None:
        """
        Verify that resign is always allowed, even on opponent's turn.

        A player should be able to concede at any time, regardless of
        whose turn it is.
        """
        action = ResignAction()
        result = validate_action(game_in_main_phase, "player2", action)

        assert result.valid is True

    def test_wrong_phase_rejected_attack_in_main(self, game_in_main_phase: GameState) -> None:
        """
        Verify that attack actions are rejected during main phase.

        Attacks can only be performed during the attack phase.
        """
        action = AttackAction(attack_index=0)
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "Cannot perform attack during main phase" in result.reason
        assert "attack" in result.reason.lower()

    def test_wrong_phase_rejected_evolve_in_attack(self, game_in_attack_phase: GameState) -> None:
        """
        Verify that evolve actions are rejected during attack phase.

        Evolution can only happen during the main phase.
        """
        action = EvolvePokemonAction(
            evolution_card_id="hand_raichu",
            target_pokemon_id="some_pikachu",
        )
        result = validate_action(game_in_attack_phase, "player1", action)

        assert result.valid is False
        assert "Cannot perform evolve during attack phase" in result.reason

    def test_pass_valid_in_main_phase(self, game_in_main_phase: GameState) -> None:
        """
        Verify that pass is valid during main phase.

        Players can pass to skip their main phase actions.
        """
        action = PassAction()
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_pass_valid_in_attack_phase(self, game_in_attack_phase: GameState) -> None:
        """
        Verify that pass is valid during attack phase.

        Players can pass to end their turn without attacking.
        """
        action = PassAction()
        result = validate_action(game_in_attack_phase, "player1", action)

        assert result.valid is True

    def test_player_not_found(self, game_in_main_phase: GameState) -> None:
        """
        Verify that actions from unknown players are rejected.

        This is a safety check for malformed requests.
        """
        action = PassAction()
        result = validate_action(game_in_main_phase, "unknown_player", action)

        assert result.valid is False
        assert "Not your turn" in result.reason


# =============================================================================
# Test Forced Action Handling
# =============================================================================


class TestForcedActionValidation:
    """Tests for forced action enforcement."""

    def test_forced_action_blocks_other_player(self, game_with_forced_action: GameState) -> None:
        """
        Verify that only the player with the forced action can act.

        When a forced action is pending (e.g., select new active after KO),
        the other player cannot take any actions.
        """
        # player2 has the forced action, player1 tries to act
        action = PassAction()
        result = validate_action(game_with_forced_action, "player1", action)

        assert result.valid is False
        assert "Waiting for player2" in result.reason

    def test_forced_action_wrong_action_type(self, game_with_forced_action: GameState) -> None:
        """
        Verify that the forced action player must use the correct action type.

        If select_active is required, playing a trainer card should fail.
        """
        # player2 must select_active, but tries to pass
        action = PassAction()
        result = validate_action(game_with_forced_action, "player2", action)

        assert result.valid is False
        assert "Must complete select_active action" in result.reason

    def test_forced_action_correct_action_allowed(self, game_with_forced_action: GameState) -> None:
        """
        Verify that the correct forced action is allowed.

        When player2 needs to select a new active and provides that action,
        it should be validated.
        """
        # player2 has benched Pokemon p2_bench1
        action = SelectActiveAction(pokemon_id="p2_bench1")
        result = validate_action(game_with_forced_action, "player2", action)

        assert result.valid is True


# =============================================================================
# Test PlayPokemonAction Validation
# =============================================================================


class TestPlayPokemonValidation:
    """Tests for playing Basic Pokemon from hand."""

    def test_valid_basic_to_bench(self, game_in_main_phase: GameState) -> None:
        """
        Verify that playing a Basic Pokemon to bench is valid.

        During main phase, Basic Pokemon can be played from hand to bench.
        """
        action = PlayPokemonAction(card_instance_id="hand_basic")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_valid_basic_to_active_during_setup(self, game_in_setup_phase: GameState) -> None:
        """
        Verify that playing Basic Pokemon to active during setup is valid.

        During setup, players can place their starting Pokemon as active.
        """
        action = PlayPokemonAction(
            card_instance_id="p1_hand_basic1",
            to_active=True,
        )
        result = validate_action(game_in_setup_phase, "player1", action)

        assert result.valid is True

    def test_valid_basic_to_bench_during_setup(self, game_in_setup_phase: GameState) -> None:
        """
        Verify that playing Basic Pokemon to bench during setup is valid.

        During setup, players can also place Pokemon on the bench.
        """
        action = PlayPokemonAction(
            card_instance_id="p1_hand_basic2",
            to_active=False,
        )
        result = validate_action(game_in_setup_phase, "player1", action)

        assert result.valid is True

    def test_invalid_card_not_in_hand(self, game_in_main_phase: GameState) -> None:
        """
        Verify that playing a card not in hand fails.

        Players can only play cards they actually have.
        """
        action = PlayPokemonAction(card_instance_id="nonexistent_card")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not found in hand" in result.reason

    def test_invalid_not_basic_pokemon(self, game_in_main_phase: GameState) -> None:
        """
        Verify that playing a Stage 1 Pokemon directly fails.

        Only Basic Pokemon can be played directly to the bench.
        """
        # hand_raichu is a Stage 1 Pokemon
        action = PlayPokemonAction(card_instance_id="hand_raichu")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not Basic" in result.reason

    def test_invalid_not_pokemon_card(self, game_in_main_phase: GameState) -> None:
        """
        Verify that playing a non-Pokemon card as a Pokemon fails.

        Energy and Trainer cards cannot be played as Pokemon.
        """
        # hand_energy is an energy card
        action = PlayPokemonAction(card_instance_id="hand_energy")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not a Pokemon card" in result.reason

    def test_invalid_bench_full(self, game_in_main_phase: GameState, card_instance_factory) -> None:
        """
        Verify that playing Pokemon when bench is full fails.

        The bench has a maximum size from RulesConfig.
        """
        # Fill the bench to capacity
        player = game_in_main_phase.players["player1"]
        while len(player.bench) < game_in_main_phase.rules.bench.max_size:
            player.bench.add(
                card_instance_factory(
                    "pikachu_base_001", instance_id=f"bench_fill_{len(player.bench)}"
                )
            )

        action = PlayPokemonAction(card_instance_id="hand_basic")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "Bench is full" in result.reason

    def test_invalid_active_already_set_during_setup(
        self, game_in_setup_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that setting active twice during setup fails (standard rules).

        By default (max_active=1), players can only have one active Pokemon.
        """
        # Set up an active Pokemon
        player = game_in_setup_phase.players["player1"]
        player.active.add(card_instance_factory("pikachu_base_001", instance_id="active"))

        action = PlayPokemonAction(
            card_instance_id="p1_hand_basic1",
            to_active=True,
        )
        result = validate_action(game_in_setup_phase, "player1", action)

        assert result.valid is False
        assert "Active slot(s) full" in result.reason

    def test_valid_double_battle_second_active(
        self, game_in_setup_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that placing a second active is valid in double battle mode.

        When max_active=2 (double battles), players can have two active Pokemon.
        """
        # Enable double battle mode
        game_in_setup_phase.rules.active = ActiveConfig(max_active=2)

        # Set up first active Pokemon
        player = game_in_setup_phase.players["player1"]
        player.active.add(card_instance_factory("pikachu_base_001", instance_id="active1"))

        # Try to place second active - should succeed
        action = PlayPokemonAction(
            card_instance_id="p1_hand_basic1",
            to_active=True,
        )
        result = validate_action(game_in_setup_phase, "player1", action)

        assert result.valid is True

    def test_invalid_double_battle_third_active(
        self, game_in_setup_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that placing a third active fails even in double battle mode.

        When max_active=2, the third active Pokemon should be rejected.
        """
        # Enable double battle mode
        game_in_setup_phase.rules.active = ActiveConfig(max_active=2)

        # Set up two active Pokemon
        player = game_in_setup_phase.players["player1"]
        player.active.add(card_instance_factory("pikachu_base_001", instance_id="active1"))
        player.active.add(card_instance_factory("charmander_base_001", instance_id="active2"))

        # Try to place third active - should fail
        action = PlayPokemonAction(
            card_instance_id="p1_hand_basic1",
            to_active=True,
        )
        result = validate_action(game_in_setup_phase, "player1", action)

        assert result.valid is False
        assert "Active slot(s) full" in result.reason
        assert "2/2" in result.reason


# =============================================================================
# Test EvolvePokemonAction Validation
# =============================================================================


class TestEvolvePokemonValidation:
    """Tests for evolving Pokemon."""

    def test_valid_stage1_from_basic(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that evolving Basic -> Stage 1 is valid.

        Raichu evolves from Pikachu.
        """
        # Get the active Pikachu's instance_id
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]

        action = EvolvePokemonAction(
            evolution_card_id="hand_raichu",
            target_pokemon_id=pikachu.instance_id,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_valid_stage1_from_basic_on_bench(self, game_in_main_phase: GameState) -> None:
        """
        Verify that evolving a benched Pokemon is valid.

        Charmeleon evolves from Charmander on the bench.
        """
        # Get the benched Charmander's instance_id
        player = game_in_main_phase.players["player1"]
        charmander = player.bench.cards[0]

        action = EvolvePokemonAction(
            evolution_card_id="hand_charmeleon",
            target_pokemon_id=charmander.instance_id,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_invalid_card_not_in_hand(self, game_in_main_phase: GameState) -> None:
        """
        Verify that evolving with a card not in hand fails.

        The evolution card must be in the player's hand.
        """
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]

        action = EvolvePokemonAction(
            evolution_card_id="nonexistent_card",
            target_pokemon_id=pikachu.instance_id,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not found in hand" in result.reason

    def test_invalid_target_not_in_play(self, game_in_main_phase: GameState) -> None:
        """
        Verify that evolving a Pokemon not in play fails.

        The target must be in the active or bench zone.
        """
        action = EvolvePokemonAction(
            evolution_card_id="hand_raichu",
            target_pokemon_id="nonexistent_pokemon",
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not found in play" in result.reason

    def test_invalid_wrong_evolution_chain(self, game_in_main_phase: GameState) -> None:
        """
        Verify that evolving with wrong Pokemon fails.

        Charmeleon evolves from Charmander, not Pikachu.
        """
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]

        action = EvolvePokemonAction(
            evolution_card_id="hand_charmeleon",
            target_pokemon_id=pikachu.instance_id,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "evolves from 'Charmander'" in result.reason
        assert "not from 'Pikachu'" in result.reason

    def test_invalid_same_turn_as_played(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that evolving same turn as played fails (default rules).

        Pokemon cannot evolve the same turn they were played.
        """
        player = game_in_main_phase.players["player1"]

        # Add a Pikachu that was played this turn
        new_pikachu = card_instance_factory(
            "pikachu_base_001",
            instance_id="just_played_pikachu",
            turn_played=game_in_main_phase.turn_number,  # Same turn
        )
        player.bench.add(new_pikachu)

        action = EvolvePokemonAction(
            evolution_card_id="hand_raichu",
            target_pokemon_id="just_played_pikachu",
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "was played this turn" in result.reason

    def test_invalid_same_turn_as_evolved(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that double evolution in same turn fails (default rules).

        Pokemon cannot evolve twice in the same turn.
        """
        player = game_in_main_phase.players["player1"]

        # Add a Charmeleon that evolved this turn
        charmeleon = card_instance_factory(
            "charmeleon_base_001",
            instance_id="just_evolved_charmeleon",
            turn_played=1,
            turn_evolved=game_in_main_phase.turn_number,  # Same turn
        )
        charmeleon.turn_evolved = game_in_main_phase.turn_number
        player.bench.add(charmeleon)

        # Add Charizard to hand for evolution
        player.hand.add(card_instance_factory("charizard_base_001", instance_id="hand_charizard"))

        action = EvolvePokemonAction(
            evolution_card_id="hand_charizard",
            target_pokemon_id="just_evolved_charmeleon",
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "already evolved this turn" in result.reason

    def test_invalid_first_turn_of_game(self, game_first_turn: GameState) -> None:
        """
        Verify that evolution on first turn fails (default rules).

        No Pokemon can evolve on the very first turn of the game.
        """
        player = game_first_turn.players["player1"]
        pikachu = player.active.cards[0]
        # Set turn_played to 0 (setup) so same-turn-as-played doesn't trigger first
        pikachu.turn_played = 0

        action = EvolvePokemonAction(
            evolution_card_id="hand_raichu",
            target_pokemon_id=pikachu.instance_id,
        )
        result = validate_action(game_first_turn, "player1", action)

        assert result.valid is False
        assert "first turn" in result.reason.lower()

    def test_valid_same_turn_with_rule_override(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that same-turn evolution works with rule override.

        When rules.evolution.same_turn_as_played is True, this is allowed.
        """
        # Modify rules to allow same-turn evolution
        game_in_main_phase.rules.evolution = EvolutionConfig(
            same_turn_as_played=True,
            same_turn_as_evolution=False,
            first_turn_of_game=False,
        )

        player = game_in_main_phase.players["player1"]

        # Add a Pikachu that was played this turn
        new_pikachu = card_instance_factory(
            "pikachu_base_001",
            instance_id="just_played_pikachu",
            turn_played=game_in_main_phase.turn_number,
        )
        player.bench.add(new_pikachu)

        action = EvolvePokemonAction(
            evolution_card_id="hand_raichu",
            target_pokemon_id="just_played_pikachu",
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_valid_first_turn_with_rule_override(self, game_first_turn: GameState) -> None:
        """
        Verify that first-turn evolution works with rule override.

        When rules.evolution.first_turn_of_game is True, this is allowed.
        """
        # Modify rules to allow first-turn evolution
        game_first_turn.rules.evolution = EvolutionConfig(
            same_turn_as_played=False,
            same_turn_as_evolution=False,
            first_turn_of_game=True,
        )

        # Need to ensure Pokemon was played on a previous turn
        # This is a first turn, so we need to adjust turn_played
        player = game_first_turn.players["player1"]
        pikachu = player.active.cards[0]
        pikachu.turn_played = 0  # Played during setup

        action = EvolvePokemonAction(
            evolution_card_id="hand_raichu",
            target_pokemon_id=pikachu.instance_id,
        )
        result = validate_action(game_first_turn, "player1", action)

        assert result.valid is True


# =============================================================================
# Test AttachEnergyAction Validation
# =============================================================================


class TestAttachEnergyValidation:
    """Tests for attaching energy cards."""

    def test_valid_from_hand(self, game_in_main_phase: GameState) -> None:
        """
        Verify that attaching energy from hand is valid.

        Standard energy attachment from hand to active Pokemon.
        """
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]

        action = AttachEnergyAction(
            energy_card_id="hand_energy",
            target_pokemon_id=pikachu.instance_id,
            from_energy_zone=False,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_valid_from_energy_zone(self, game_in_main_phase: GameState) -> None:
        """
        Verify that attaching energy from energy zone is valid.

        Pokemon Pocket style: energy flipped to zone, then attached.
        """
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]

        action = AttachEnergyAction(
            energy_card_id="zone_energy",
            target_pokemon_id=pikachu.instance_id,
            from_energy_zone=True,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_valid_to_benched_pokemon(self, game_in_main_phase: GameState) -> None:
        """
        Verify that attaching energy to benched Pokemon is valid.

        Energy can be attached to any Pokemon in play.
        """
        player = game_in_main_phase.players["player1"]
        charmander = player.bench.cards[0]

        action = AttachEnergyAction(
            energy_card_id="hand_energy",
            target_pokemon_id=charmander.instance_id,
            from_energy_zone=False,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_invalid_card_not_in_hand(self, game_in_main_phase: GameState) -> None:
        """
        Verify that attaching energy not in hand fails.

        The energy card must be in the specified zone.
        """
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]

        action = AttachEnergyAction(
            energy_card_id="nonexistent_energy",
            target_pokemon_id=pikachu.instance_id,
            from_energy_zone=False,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not found in hand" in result.reason

    def test_invalid_card_not_in_energy_zone(self, game_in_main_phase: GameState) -> None:
        """
        Verify that attaching from energy zone when card is in hand fails.

        The from_energy_zone flag must match where the card actually is.
        """
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]

        # hand_energy is in hand, not energy_zone
        action = AttachEnergyAction(
            energy_card_id="hand_energy",
            target_pokemon_id=pikachu.instance_id,
            from_energy_zone=True,  # Wrong zone!
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not found in energy zone" in result.reason

    def test_invalid_not_energy_card(self, game_in_main_phase: GameState) -> None:
        """
        Verify that attaching a non-energy card fails.

        Only Energy cards can be attached as energy.
        """
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]

        # hand_potion is an Item card, not energy
        action = AttachEnergyAction(
            energy_card_id="hand_potion",
            target_pokemon_id=pikachu.instance_id,
            from_energy_zone=False,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not an Energy card" in result.reason

    def test_invalid_target_not_in_play(self, game_in_main_phase: GameState) -> None:
        """
        Verify that attaching to Pokemon not in play fails.

        Target must be in active or bench zone.
        """
        action = AttachEnergyAction(
            energy_card_id="hand_energy",
            target_pokemon_id="nonexistent_pokemon",
            from_energy_zone=False,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not found in play" in result.reason

    def test_invalid_limit_exceeded(self, game_in_main_phase: GameState) -> None:
        """
        Verify that exceeding energy attachment limit fails.

        Default rules allow 1 energy attachment per turn.
        """
        player = game_in_main_phase.players["player1"]
        player.energy_attachments_this_turn = 1  # Already attached one

        pikachu = player.active.cards[0]

        action = AttachEnergyAction(
            energy_card_id="hand_energy",
            target_pokemon_id=pikachu.instance_id,
            from_energy_zone=False,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "Energy attachment limit reached" in result.reason
        assert "1/1" in result.reason

    def test_invalid_first_turn_restriction(self, game_first_turn: GameState) -> None:
        """
        Verify that first turn energy restriction is enforced.

        Default rules prohibit energy attachment on first turn.
        """
        player = game_first_turn.players["player1"]
        pikachu = player.active.cards[0]

        action = AttachEnergyAction(
            energy_card_id="hand_energy",
            target_pokemon_id=pikachu.instance_id,
            from_energy_zone=False,
        )
        result = validate_action(game_first_turn, "player1", action)

        assert result.valid is False
        assert "first turn" in result.reason.lower()

    def test_valid_second_attachment_with_higher_limit(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that multiple attachments work with higher limit.

        When rules allow 2 attachments per turn, second one should work.
        """
        # Modify rules to allow 2 energy per turn
        game_in_main_phase.rules.energy = EnergyConfig(attachments_per_turn=2)

        player = game_in_main_phase.players["player1"]
        player.energy_attachments_this_turn = 1  # Already attached one

        # Add another energy to hand
        player.hand.add(card_instance_factory("lightning_energy_001", instance_id="second_energy"))

        pikachu = player.active.cards[0]

        action = AttachEnergyAction(
            energy_card_id="second_energy",
            target_pokemon_id=pikachu.instance_id,
            from_energy_zone=False,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_valid_first_turn_with_rule_override(self, game_first_turn: GameState) -> None:
        """
        Verify that first turn energy works with rule override.

        When rules.first_turn.can_attach_energy is True, this is allowed.
        """
        game_first_turn.rules.first_turn = FirstTurnConfig(
            can_draw=True,
            can_attack=True,
            can_play_supporter=True,
            can_attach_energy=True,  # Allow first turn energy
        )

        player = game_first_turn.players["player1"]
        pikachu = player.active.cards[0]

        action = AttachEnergyAction(
            energy_card_id="hand_energy",
            target_pokemon_id=pikachu.instance_id,
            from_energy_zone=False,
        )
        result = validate_action(game_first_turn, "player1", action)

        assert result.valid is True


# =============================================================================
# Test PlayTrainerAction Validation
# =============================================================================


class TestPlayTrainerValidation:
    """Tests for playing Trainer cards."""

    def test_valid_item_card(self, game_in_main_phase: GameState) -> None:
        """
        Verify that playing an Item card is valid.

        Items can be played unlimited times per turn (by default).
        """
        action = PlayTrainerAction(card_instance_id="hand_potion")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_valid_first_supporter(self, game_in_main_phase: GameState) -> None:
        """
        Verify that playing the first Supporter card is valid.

        One Supporter can be played per turn.
        """
        action = PlayTrainerAction(card_instance_id="hand_supporter")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_valid_first_stadium(self, game_in_main_phase: GameState) -> None:
        """
        Verify that playing a Stadium card is valid.

        One Stadium can be played per turn.
        """
        action = PlayTrainerAction(card_instance_id="hand_stadium")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_invalid_card_not_in_hand(self, game_in_main_phase: GameState) -> None:
        """
        Verify that playing a card not in hand fails.
        """
        action = PlayTrainerAction(card_instance_id="nonexistent_card")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not found in hand" in result.reason

    def test_invalid_second_supporter(self, game_in_main_phase: GameState) -> None:
        """
        Verify that playing a second Supporter fails.

        Default rules allow only 1 Supporter per turn.
        """
        player = game_in_main_phase.players["player1"]
        player.supporters_played_this_turn = 1  # Already played one

        action = PlayTrainerAction(card_instance_id="hand_supporter")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "Supporter limit reached" in result.reason
        assert "1/1" in result.reason

    def test_invalid_second_stadium(self, game_in_main_phase: GameState) -> None:
        """
        Verify that playing a second Stadium fails.

        Default rules allow only 1 Stadium per turn.
        """
        player = game_in_main_phase.players["player1"]
        player.stadiums_played_this_turn = 1  # Already played one

        action = PlayTrainerAction(card_instance_id="hand_stadium")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "Stadium limit reached" in result.reason

    def test_invalid_same_stadium_in_play(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that playing the same Stadium already in play fails.

        You cannot play a Stadium if an identical one is already in play.
        """
        # Put a Pokemon Center in play
        game_in_main_phase.stadium_in_play = card_instance_factory(
            "pokemon_center_001", instance_id="stadium_in_play"
        )

        # Try to play another Pokemon Center
        action = PlayTrainerAction(card_instance_id="hand_stadium")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "same stadium already in play" in result.reason

    def test_valid_different_stadium_replaces(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that playing a different Stadium is valid.

        A new Stadium can replace an existing one if they're different.
        """
        # Put a different stadium in play (need to add one to registry first)
        game_in_main_phase.card_registry["other_stadium"] = game_in_main_phase.card_registry[
            "pokemon_center_001"
        ].model_copy(update={"id": "other_stadium", "name": "Other Stadium"})

        game_in_main_phase.stadium_in_play = card_instance_factory(
            "other_stadium", instance_id="stadium_in_play"
        )

        # Try to play Pokemon Center (different from Other Stadium)
        action = PlayTrainerAction(card_instance_id="hand_stadium")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_valid_same_stadium_when_replace_enabled(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that playing the same Stadium is allowed when stadium_same_name_replace is enabled.

        Some house rules may allow replacing a stadium with the same stadium
        (e.g., to refresh effects or reset counters).
        """
        # Enable same-name stadium replacement
        game_in_main_phase.rules.trainer.stadium_same_name_replace = True

        # Put a Pokemon Center in play
        game_in_main_phase.stadium_in_play = card_instance_factory(
            "pokemon_center_001", instance_id="stadium_in_play"
        )

        # Try to play another Pokemon Center - should succeed with config enabled
        action = PlayTrainerAction(card_instance_id="hand_stadium")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_invalid_first_turn_supporter(self, game_first_turn: GameState) -> None:
        """
        Verify that first turn Supporter restriction is enforced.

        Default rules allow Supporters on first turn, so we need to change rules.
        """
        # Change rules to prohibit first-turn supporters
        game_first_turn.rules.first_turn = FirstTurnConfig(
            can_draw=True,
            can_attack=True,
            can_play_supporter=False,  # Prohibit
            can_attach_energy=False,
        )

        action = PlayTrainerAction(card_instance_id="hand_supporter")
        result = validate_action(game_first_turn, "player1", action)

        assert result.valid is False
        assert "first turn" in result.reason.lower()

    def test_invalid_tool_no_target(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that playing a Tool without a target fails.

        Tool cards must specify a Pokemon to attach to.
        """
        # Add a tool card to hand
        player = game_in_main_phase.players["player1"]
        player.hand.add(card_instance_factory("choice_band_001", instance_id="hand_tool"))

        action = PlayTrainerAction(
            card_instance_id="hand_tool",
            targets=[],  # No target specified
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "require a target Pokemon" in result.reason

    def test_invalid_tool_target_not_in_play(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that attaching a Tool to Pokemon not in play fails.
        """
        player = game_in_main_phase.players["player1"]
        player.hand.add(card_instance_factory("choice_band_001", instance_id="hand_tool"))

        action = PlayTrainerAction(
            card_instance_id="hand_tool",
            targets=["nonexistent_pokemon"],
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not found in play" in result.reason

    def test_invalid_tool_slots_full(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that attaching a Tool when slots are full fails.

        Default rules allow 1 tool per Pokemon.
        """
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]

        # Already has a tool attached
        pikachu.attached_tools.append("existing_tool_id")

        player.hand.add(card_instance_factory("choice_band_001", instance_id="hand_tool"))

        action = PlayTrainerAction(
            card_instance_id="hand_tool",
            targets=[pikachu.instance_id],
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "maximum tools attached" in result.reason

    def test_valid_tool_attachment(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that attaching a Tool to a Pokemon is valid.
        """
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]

        player.hand.add(card_instance_factory("choice_band_001", instance_id="hand_tool"))

        action = PlayTrainerAction(
            card_instance_id="hand_tool",
            targets=[pikachu.instance_id],
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_valid_item_unlimited(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that multiple Items can be played per turn.

        Items have no limit by default.
        """
        player = game_in_main_phase.players["player1"]
        player.items_played_this_turn = 5  # Already played several

        action = PlayTrainerAction(card_instance_id="hand_potion")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_invalid_item_with_limit_exceeded(self, game_in_main_phase: GameState) -> None:
        """
        Verify that Item limit is enforced when set.

        When rules.trainer.items_per_turn is set, it's enforced.
        """
        # Set an item limit
        game_in_main_phase.rules.trainer = TrainerConfig(
            supporters_per_turn=1,
            stadiums_per_turn=1,
            items_per_turn=2,  # Limit items to 2
            tools_per_pokemon=1,
        )

        player = game_in_main_phase.players["player1"]
        player.items_played_this_turn = 2  # Already at limit

        action = PlayTrainerAction(card_instance_id="hand_potion")
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "Item limit reached" in result.reason


# =============================================================================
# Test UseAbilityAction Validation
# =============================================================================


class TestUseAbilityValidation:
    """Tests for using Pokemon abilities."""

    def test_valid_first_use(self, game_in_main_phase: GameState, card_instance_factory) -> None:
        """
        Verify that using an ability for the first time is valid.
        """
        player = game_in_main_phase.players["player1"]

        # Add Shaymin EX with ability to bench
        shaymin = card_instance_factory("shaymin_ex_001", instance_id="shaymin")
        player.bench.add(shaymin)

        action = UseAbilityAction(
            pokemon_id="shaymin",
            ability_index=0,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_invalid_pokemon_not_in_play(self, game_in_main_phase: GameState) -> None:
        """
        Verify that using ability on Pokemon not in play fails.
        """
        action = UseAbilityAction(
            pokemon_id="nonexistent_pokemon",
            ability_index=0,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not found in play" in result.reason

    def test_invalid_ability_index_out_of_range(self, game_in_main_phase: GameState) -> None:
        """
        Verify that using invalid ability index fails.

        Pikachu has no abilities.
        """
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]

        action = UseAbilityAction(
            pokemon_id=pikachu.instance_id,
            ability_index=0,  # Pikachu has no abilities
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "Invalid ability index" in result.reason
        assert "has 0 abilities" in result.reason

    def test_invalid_second_use_once_per_turn(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that using once-per-turn ability twice fails.
        """
        player = game_in_main_phase.players["player1"]

        # Add Shaymin EX with ability
        shaymin = card_instance_factory("shaymin_ex_001", instance_id="shaymin")
        shaymin.ability_uses_this_turn = {0: 1}  # Already used ability index 0
        player.bench.add(shaymin)

        action = UseAbilityAction(
            pokemon_id="shaymin",
            ability_index=0,
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "already used" in result.reason


# =============================================================================
# Test AttackAction Validation
# =============================================================================


class TestAttackValidation:
    """Tests for declaring attacks."""

    def test_valid_with_exact_energy(self, game_in_attack_phase: GameState) -> None:
        """
        Verify that attacking with exact energy requirement is valid.

        Pikachu's Thunder Shock costs 1 Lightning energy.
        """
        action = AttackAction(attack_index=0)
        result = validate_action(game_in_attack_phase, "player1", action)

        assert result.valid is True

    def test_valid_colorless_satisfied_by_any(
        self, game_in_attack_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that Colorless energy can be satisfied by any type.

        Add a Pokemon with Colorless cost and attach Fire energy.
        Energy CardInstances are now stored directly on the Pokemon.
        """
        player = game_in_attack_phase.players["player1"]

        # Replace active with Raichu (costs: Lightning, Lightning, Colorless)
        player.active.clear()
        raichu = card_instance_factory("raichu_base_001", instance_id="test_raichu")
        # Create energy CardInstances and attach them directly
        energy1 = card_instance_factory("lightning_energy_001", instance_id="energy_1")
        energy2 = card_instance_factory("lightning_energy_001", instance_id="energy_2")
        energy3 = card_instance_factory("fire_energy_001", instance_id="energy_3")
        # Attach 2 Lightning + 1 Fire (Fire satisfies Colorless)
        raichu.attach_energy(energy1)
        raichu.attach_energy(energy2)
        raichu.attach_energy(energy3)
        player.active.add(raichu)

        action = AttackAction(attack_index=0)
        result = validate_action(game_in_attack_phase, "player1", action)

        assert result.valid is True

    def test_invalid_not_attack_phase(self, game_in_main_phase: GameState) -> None:
        """
        Verify that attacking during main phase fails.
        """
        action = AttackAction(attack_index=0)
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "Cannot perform attack during main phase" in result.reason

    def test_invalid_no_active_pokemon(self, game_in_attack_phase: GameState) -> None:
        """
        Verify that attacking without active Pokemon fails.
        """
        player = game_in_attack_phase.players["player1"]
        player.active.clear()

        action = AttackAction(attack_index=0)
        result = validate_action(game_in_attack_phase, "player1", action)

        assert result.valid is False
        assert "No active Pokemon" in result.reason

    def test_invalid_attack_index_out_of_range(self, game_in_attack_phase: GameState) -> None:
        """
        Verify that invalid attack index fails.

        Pikachu only has 1 attack (index 0).
        """
        action = AttackAction(attack_index=5)  # Invalid
        result = validate_action(game_in_attack_phase, "player1", action)

        assert result.valid is False
        assert "Invalid attack index" in result.reason

    def test_invalid_insufficient_energy(self, game_in_attack_phase: GameState) -> None:
        """
        Verify that attacking without enough energy fails.
        """
        player = game_in_attack_phase.players["player1"]
        pikachu = player.active.cards[0]
        pikachu.attached_energy.clear()  # Remove all energy

        action = AttackAction(attack_index=0)
        result = validate_action(game_in_attack_phase, "player1", action)

        assert result.valid is False
        assert "Insufficient" in result.reason

    def test_invalid_paralyzed(self, game_in_attack_phase: GameState) -> None:
        """
        Verify that Paralyzed Pokemon cannot attack.
        """
        player = game_in_attack_phase.players["player1"]
        pikachu = player.active.cards[0]
        pikachu.add_status(StatusCondition.PARALYZED)

        action = AttackAction(attack_index=0)
        result = validate_action(game_in_attack_phase, "player1", action)

        assert result.valid is False
        assert "paralyzed" in result.reason.lower()

    def test_invalid_asleep(self, game_in_attack_phase: GameState) -> None:
        """
        Verify that Asleep Pokemon cannot attack.
        """
        player = game_in_attack_phase.players["player1"]
        pikachu = player.active.cards[0]
        pikachu.add_status(StatusCondition.ASLEEP)

        action = AttackAction(attack_index=0)
        result = validate_action(game_in_attack_phase, "player1", action)

        assert result.valid is False
        assert "asleep" in result.reason.lower()

    def test_valid_confused_can_attempt(self, game_in_attack_phase: GameState) -> None:
        """
        Verify that Confused Pokemon can attempt to attack.

        Confusion is resolved at execution time (coin flip), not validation.
        """
        player = game_in_attack_phase.players["player1"]
        pikachu = player.active.cards[0]
        pikachu.add_status(StatusCondition.CONFUSED)

        action = AttackAction(attack_index=0)
        result = validate_action(game_in_attack_phase, "player1", action)

        assert result.valid is True

    def test_invalid_first_turn_restriction(self, game_first_turn: GameState) -> None:
        """
        Verify that first turn attack restriction is enforced.

        Note: Default rules ALLOW first turn attack, so we need to change rules.
        """
        # Change rules and phase
        game_first_turn.rules.first_turn = FirstTurnConfig(
            can_draw=True,
            can_attack=False,  # Prohibit
            can_play_supporter=True,
            can_attach_energy=False,
        )
        game_first_turn.phase = TurnPhase.ATTACK

        # Add energy to active
        player = game_first_turn.players["player1"]
        pikachu = player.active.cards[0]
        # Attach energy as a CardInstance
        energy = CardInstance(
            instance_id="test_energy",
            definition_id="lightning_energy_001",
        )
        pikachu.attach_energy(energy)

        action = AttackAction(attack_index=0)
        result = validate_action(game_first_turn, "player1", action)

        assert result.valid is False
        assert "first turn" in result.reason.lower()

    def test_valid_first_turn_with_rule_override(self, game_first_turn: GameState) -> None:
        """
        Verify that first turn attack works with default rules.

        Default Mantimon rules allow first turn attacks.
        """
        game_first_turn.phase = TurnPhase.ATTACK

        # Add energy to active as CardInstance
        player = game_first_turn.players["player1"]
        pikachu = player.active.cards[0]
        energy = CardInstance(
            instance_id="test_energy",
            definition_id="lightning_energy_001",
        )
        pikachu.attach_energy(energy)

        action = AttackAction(attack_index=0)
        result = validate_action(game_first_turn, "player1", action)

        assert result.valid is True


# =============================================================================
# Test RetreatAction Validation
# =============================================================================


class TestRetreatValidation:
    """Tests for retreating active Pokemon."""

    def test_valid_with_energy(self, game_in_main_phase: GameState, card_instance_factory) -> None:
        """
        Verify that retreating with sufficient energy is valid.

        Pikachu has retreat cost of 1.
        """
        player = game_in_main_phase.players["player1"]
        charmander = player.bench.cards[0]

        # Pikachu already has 1 energy attached (energy_lightning_1)
        action = RetreatAction(
            new_active_id=charmander.instance_id,
            energy_to_discard=["energy_lightning_1"],
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_valid_free_retreat_zero_cost(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that Pokemon with 0 retreat cost can retreat freely.
        """
        player = game_in_main_phase.players["player1"]

        # Replace active with a Pokemon with 0 retreat cost
        player.active.clear()
        # We need a card with retreat_cost=0. Let's modify Pikachu's definition temporarily
        zero_retreat = card_instance_factory("pikachu_base_001", instance_id="zero_retreat_pokemon")
        player.active.add(zero_retreat)

        # Modify the definition to have 0 retreat cost
        pikachu_def = game_in_main_phase.card_registry["pikachu_base_001"]
        original_cost = pikachu_def.retreat_cost
        game_in_main_phase.card_registry["pikachu_base_001"] = pikachu_def.model_copy(
            update={"retreat_cost": 0}
        )

        charmander = player.bench.cards[0]

        action = RetreatAction(
            new_active_id=charmander.instance_id,
            energy_to_discard=[],  # No energy needed
        )
        result = validate_action(game_in_main_phase, "player1", action)

        # Restore original
        game_in_main_phase.card_registry["pikachu_base_001"] = pikachu_def.model_copy(
            update={"retreat_cost": original_cost}
        )

        assert result.valid is True

    def test_valid_with_retreat_cost_modifier(self, game_in_main_phase: GameState) -> None:
        """
        Verify that retreat cost modifiers are respected.

        A Pokemon with retreat_cost_modifier=-1 reduces the cost.
        """
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]
        pikachu.retreat_cost_modifier = -1  # Reduces cost to 0

        charmander = player.bench.cards[0]

        action = RetreatAction(
            new_active_id=charmander.instance_id,
            energy_to_discard=[],  # No energy needed due to modifier
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_invalid_no_active(self, game_in_main_phase: GameState) -> None:
        """
        Verify that retreating without active Pokemon fails.
        """
        player = game_in_main_phase.players["player1"]
        player.active.clear()

        charmander = player.bench.cards[0]

        action = RetreatAction(
            new_active_id=charmander.instance_id,
            energy_to_discard=[],
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "No active Pokemon" in result.reason

    def test_invalid_no_bench(self, game_in_main_phase: GameState) -> None:
        """
        Verify that retreating without benched Pokemon fails.
        """
        player = game_in_main_phase.players["player1"]
        player.bench.clear()

        action = RetreatAction(
            new_active_id="some_pokemon",
            energy_to_discard=["energy_lightning_1"],
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "No benched Pokemon" in result.reason

    def test_invalid_new_active_not_on_bench(self, game_in_main_phase: GameState) -> None:
        """
        Verify that retreating to non-benched Pokemon fails.
        """
        action = RetreatAction(
            new_active_id="nonexistent_pokemon",
            energy_to_discard=["energy_lightning_1"],
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not found on bench" in result.reason

    def test_invalid_insufficient_energy(self, game_in_main_phase: GameState) -> None:
        """
        Verify that retreating without enough energy fails.
        """
        player = game_in_main_phase.players["player1"]
        charmander = player.bench.cards[0]

        # Pikachu has retreat cost 1, but we discard 0 energy
        action = RetreatAction(
            new_active_id=charmander.instance_id,
            energy_to_discard=[],  # Not enough!
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "Insufficient energy to retreat" in result.reason

    def test_invalid_paralyzed(self, game_in_main_phase: GameState) -> None:
        """
        Verify that Paralyzed Pokemon cannot retreat.
        """
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]
        pikachu.add_status(StatusCondition.PARALYZED)
        charmander = player.bench.cards[0]

        action = RetreatAction(
            new_active_id=charmander.instance_id,
            energy_to_discard=["energy_lightning_1"],
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "paralyzed" in result.reason.lower()

    def test_invalid_asleep(self, game_in_main_phase: GameState) -> None:
        """
        Verify that Asleep Pokemon cannot retreat.
        """
        player = game_in_main_phase.players["player1"]
        pikachu = player.active.cards[0]
        pikachu.add_status(StatusCondition.ASLEEP)
        charmander = player.bench.cards[0]

        action = RetreatAction(
            new_active_id=charmander.instance_id,
            energy_to_discard=["energy_lightning_1"],
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "asleep" in result.reason.lower()

    def test_invalid_retreat_limit_exceeded(self, game_in_main_phase: GameState) -> None:
        """
        Verify that retreat limit is enforced.

        Default rules allow 1 retreat per turn.
        """
        player = game_in_main_phase.players["player1"]
        player.retreats_this_turn = 1  # Already retreated

        charmander = player.bench.cards[0]

        action = RetreatAction(
            new_active_id=charmander.instance_id,
            energy_to_discard=["energy_lightning_1"],
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "Retreat limit reached" in result.reason

    def test_valid_free_retreat_rule(self, game_in_main_phase: GameState) -> None:
        """
        Verify that free retreat rule works.

        When rules.retreat.free_retreat_cost is True, no energy needed.
        """
        game_in_main_phase.rules.retreat = RetreatConfig(
            retreats_per_turn=1,
            free_retreat_cost=True,  # Free retreat!
        )

        player = game_in_main_phase.players["player1"]
        charmander = player.bench.cards[0]

        action = RetreatAction(
            new_active_id=charmander.instance_id,
            energy_to_discard=[],  # No energy needed
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_invalid_energy_not_attached(self, game_in_main_phase: GameState) -> None:
        """
        Verify that discarding unattached energy fails.

        The energy to discard must actually be attached to the active Pokemon.
        """
        player = game_in_main_phase.players["player1"]
        charmander = player.bench.cards[0]

        action = RetreatAction(
            new_active_id=charmander.instance_id,
            energy_to_discard=["not_attached_energy"],  # Not attached!
        )
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not attached" in result.reason


# =============================================================================
# Test SelectPrizeAction Validation
# =============================================================================


class TestSelectPrizeValidation:
    """Tests for selecting prize cards."""

    def test_valid_prize_selection(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that selecting a valid prize is allowed.
        """
        # Enable prize cards mode
        game_in_main_phase.rules.prizes.use_prize_cards = True
        game_in_main_phase.phase = TurnPhase.END

        # Add prize cards
        player = game_in_main_phase.players["player1"]
        for i in range(4):
            player.prizes.add(card_instance_factory("pikachu_base_001", instance_id=f"prize_{i}"))

        # Set up forced action
        game_in_main_phase.add_forced_action(
            ForcedAction(
                player_id="player1",
                action_type="select_prize",
                reason="Knocked out opponent's Pokemon",
            )
        )

        action = SelectPrizeAction(prize_index=0)
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_invalid_not_using_prize_cards(self, game_in_main_phase: GameState) -> None:
        """
        Verify that prize selection in point-based mode fails.

        Default rules use points, not prize cards.
        """
        # Default: use_prize_cards=False
        game_in_main_phase.phase = TurnPhase.END

        # Set up forced action
        game_in_main_phase.add_forced_action(
            ForcedAction(
                player_id="player1",
                action_type="select_prize",
                reason="Test",
            )
        )

        action = SelectPrizeAction(prize_index=0)
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "not using prize cards" in result.reason

    def test_invalid_prize_index_out_of_range(
        self, game_in_main_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify that invalid prize index fails.
        """
        game_in_main_phase.rules.prizes.use_prize_cards = True
        game_in_main_phase.phase = TurnPhase.END

        # Add only 2 prize cards
        player = game_in_main_phase.players["player1"]
        for i in range(2):
            player.prizes.add(card_instance_factory("pikachu_base_001", instance_id=f"prize_{i}"))

        game_in_main_phase.add_forced_action(
            ForcedAction(
                player_id="player1",
                action_type="select_prize",
                reason="Test",
            )
        )

        action = SelectPrizeAction(prize_index=5)  # Out of range!
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "Invalid prize index" in result.reason

    def test_invalid_no_prizes_remaining(self, game_in_main_phase: GameState) -> None:
        """
        Verify that selecting prize with none remaining fails.
        """
        game_in_main_phase.rules.prizes.use_prize_cards = True
        game_in_main_phase.phase = TurnPhase.END

        # No prizes
        player = game_in_main_phase.players["player1"]
        player.prizes.clear()

        game_in_main_phase.add_forced_action(
            ForcedAction(
                player_id="player1",
                action_type="select_prize",
                reason="Test",
            )
        )

        action = SelectPrizeAction(prize_index=0)
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "No prize cards remaining" in result.reason


# =============================================================================
# Test SelectActiveAction Validation
# =============================================================================


class TestSelectActiveValidation:
    """Tests for selecting new active Pokemon after knockout."""

    def test_valid_after_knockout(self, game_with_forced_action: GameState) -> None:
        """
        Verify that selecting new active after KO is valid.
        """
        # player2 needs to select new active, has p2_bench1 on bench
        action = SelectActiveAction(pokemon_id="p2_bench1")
        result = validate_action(game_with_forced_action, "player2", action)

        assert result.valid is True

    def test_invalid_active_not_empty(self, game_in_main_phase: GameState) -> None:
        """
        Verify that selecting active when already have one fails.
        """
        # player1 already has active Pokemon
        action = SelectActiveAction(pokemon_id="some_pokemon")

        # Need to set phase for select_active - but since we have active, should fail
        game_in_main_phase.phase = TurnPhase.END

        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is False
        assert "Already have an active Pokemon" in result.reason

    def test_invalid_target_not_on_bench(self, game_with_forced_action: GameState) -> None:
        """
        Verify that selecting non-benched Pokemon fails.
        """
        action = SelectActiveAction(pokemon_id="nonexistent_pokemon")
        result = validate_action(game_with_forced_action, "player2", action)

        assert result.valid is False
        assert "not found on bench" in result.reason


# =============================================================================
# Test ResignAction Validation
# =============================================================================


class TestResignValidation:
    """Tests for resigning from the game."""

    def test_valid_any_phase(self, game_in_main_phase: GameState) -> None:
        """
        Verify that resign is valid during main phase.
        """
        action = ResignAction()
        result = validate_action(game_in_main_phase, "player1", action)

        assert result.valid is True

    def test_valid_during_setup(self, game_in_setup_phase: GameState) -> None:
        """
        Verify that resign is valid during setup phase.
        """
        action = ResignAction()
        result = validate_action(game_in_setup_phase, "player1", action)

        assert result.valid is True

    def test_valid_opponents_turn(self, game_in_main_phase: GameState) -> None:
        """
        Verify that player can resign even on opponent's turn.
        """
        action = ResignAction()
        result = validate_action(game_in_main_phase, "player2", action)

        assert result.valid is True


# =============================================================================
# Test Energy Cost Calculation
# =============================================================================


class TestEnergyCostCalculation:
    """Tests for energy cost validation helper functions."""

    def test_exact_match_single_type(self, game_in_attack_phase: GameState) -> None:
        """
        Verify exact energy match works.

        Pikachu with 1 Lightning can use Thunder Shock (costs 1 Lightning).
        """
        action = AttackAction(attack_index=0)
        result = validate_action(game_in_attack_phase, "player1", action)

        assert result.valid is True

    def test_excess_energy_allowed(
        self, game_in_attack_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify having more energy than required is fine.
        """
        player = game_in_attack_phase.players["player1"]
        pikachu = player.active.cards[0]

        # Add extra energy as CardInstance
        extra_energy = card_instance_factory("lightning_energy_001", instance_id="extra_energy")
        pikachu.attach_energy(extra_energy)

        action = AttackAction(attack_index=0)
        result = validate_action(game_in_attack_phase, "player1", action)

        assert result.valid is True

    def test_wrong_type_insufficient(
        self, game_in_attack_phase: GameState, card_instance_factory
    ) -> None:
        """
        Verify wrong energy type doesn't satisfy specific cost.

        Thunder Shock needs Lightning, Fire won't work.
        """
        player = game_in_attack_phase.players["player1"]
        pikachu = player.active.cards[0]

        # Replace Lightning with Fire as CardInstance
        pikachu.attached_energy.clear()
        fire_energy = card_instance_factory("fire_energy_001", instance_id="fire_energy")
        pikachu.attach_energy(fire_energy)

        action = AttackAction(attack_index=0)
        result = validate_action(game_in_attack_phase, "player1", action)

        assert result.valid is False
        assert "Insufficient lightning energy" in result.reason
