"""Integration tests for the GameEngine orchestrator.

This module tests the full game flow from creation through actions to win
conditions. These are integration tests that verify all components work
together correctly.

Test categories:
    - Game creation and initialization
    - Action validation through engine
    - Action execution and state changes
    - Turn management integration
    - Win condition detection
    - Full game playthrough scenarios
"""

import pytest

from app.core.config import RulesConfig
from app.core.engine import GameEngine
from app.core.enums import (
    CardType,
    EnergyType,
    GameEndReason,
    PokemonStage,
    PokemonVariant,
    StatusCondition,
    TrainerType,
    TurnPhase,
)
from app.core.models.actions import (
    AttachEnergyAction,
    AttackAction,
    EvolvePokemonAction,
    PassAction,
    PlayPokemonAction,
    PlayTrainerAction,
    ResignAction,
    RetreatAction,
    SelectActiveAction,
    UseAbilityAction,
)
from app.core.models.card import Ability, Attack, CardDefinition, CardInstance
from app.core.models.game_state import ForcedAction, GameState
from app.core.rng import SeededRandom

# =============================================================================
# Fixtures
# =============================================================================


@pytest.fixture
def seeded_rng() -> SeededRandom:
    """Create a seeded RNG for deterministic tests."""
    return SeededRandom(seed=42)


@pytest.fixture
def basic_pokemon_def() -> CardDefinition:
    """Create a basic Pokemon with an attack."""
    return CardDefinition(
        id="pikachu-001",
        name="Pikachu",
        card_type=CardType.POKEMON,
        stage=PokemonStage.BASIC,
        variant=PokemonVariant.NORMAL,
        hp=60,
        pokemon_type=EnergyType.LIGHTNING,
        attacks=[
            Attack(
                name="Thunder Shock",
                damage=20,
                cost=[EnergyType.LIGHTNING],
            ),
        ],
        retreat_cost=1,
    )


@pytest.fixture
def strong_pokemon_def() -> CardDefinition:
    """Create a strong Pokemon for knockout tests."""
    return CardDefinition(
        id="raichu-001",
        name="Raichu",
        card_type=CardType.POKEMON,
        stage=PokemonStage.STAGE_1,
        variant=PokemonVariant.NORMAL,
        hp=100,
        pokemon_type=EnergyType.LIGHTNING,
        evolves_from="Pikachu",
        attacks=[
            Attack(
                name="Thunder",
                damage=80,
                cost=[EnergyType.LIGHTNING, EnergyType.LIGHTNING],
            ),
        ],
        retreat_cost=2,
    )


@pytest.fixture
def energy_def() -> CardDefinition:
    """Create a basic energy card."""
    return CardDefinition(
        id="lightning-energy",
        name="Lightning Energy",
        card_type=CardType.ENERGY,
        energy_type=EnergyType.LIGHTNING,
    )


@pytest.fixture
def card_registry(
    basic_pokemon_def: CardDefinition,
    strong_pokemon_def: CardDefinition,
    energy_def: CardDefinition,
) -> dict[str, CardDefinition]:
    """Create a card registry with test cards."""
    return {
        basic_pokemon_def.id: basic_pokemon_def,
        strong_pokemon_def.id: strong_pokemon_def,
        energy_def.id: energy_def,
    }


@pytest.fixture
def player1_deck(
    basic_pokemon_def: CardDefinition, energy_def: CardDefinition
) -> list[CardInstance]:
    """Create a deck for player 1."""
    cards = []
    # Add 10 basic Pokemon
    for i in range(10):
        cards.append(
            CardInstance(instance_id=f"p1-pokemon-{i}", definition_id=basic_pokemon_def.id)
        )
    # Add 30 energy
    for i in range(30):
        cards.append(CardInstance(instance_id=f"p1-energy-{i}", definition_id=energy_def.id))
    return cards


@pytest.fixture
def player2_deck(
    basic_pokemon_def: CardDefinition, energy_def: CardDefinition
) -> list[CardInstance]:
    """Create a deck for player 2."""
    cards = []
    # Add 10 basic Pokemon
    for i in range(10):
        cards.append(
            CardInstance(instance_id=f"p2-pokemon-{i}", definition_id=basic_pokemon_def.id)
        )
    # Add 30 energy
    for i in range(30):
        cards.append(CardInstance(instance_id=f"p2-energy-{i}", definition_id=energy_def.id))
    return cards


@pytest.fixture
def engine(seeded_rng: SeededRandom) -> GameEngine:
    """Create a GameEngine with default rules and seeded RNG."""
    return GameEngine(rules=RulesConfig(), rng=seeded_rng)


# =============================================================================
# Game Creation Tests
# =============================================================================


class TestGameCreation:
    """Tests for game creation and initialization."""

    def test_create_game_success(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
    ):
        """
        Test successful game creation with valid inputs.

        Verifies game is created with correct initial state.
        """
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )

        assert result.success
        assert result.game is not None
        assert result.game.game_id is not None
        assert len(result.game.players) == 2
        assert result.game.turn_number == 1
        assert result.game.phase == TurnPhase.SETUP

    def test_create_game_deals_starting_hands(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
    ):
        """
        Test that game creation deals starting hands.

        Each player should have cards in hand after creation.
        """
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )

        assert result.success
        game = result.game
        assert len(game.players["player1"].hand) > 0
        assert len(game.players["player2"].hand) > 0

    def test_create_game_shuffles_decks(
        self,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
    ):
        """
        Test that game creation shuffles decks differently with different seeds.

        Verifies decks are actually shuffled and RNG affects order.
        """
        engine1 = GameEngine(rng=SeededRandom(seed=1))
        engine2 = GameEngine(rng=SeededRandom(seed=2))

        result1 = engine1.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": list(player1_deck), "player2": list(player2_deck)},
            card_registry=card_registry,
        )
        result2 = engine2.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": list(player1_deck), "player2": list(player2_deck)},
            card_registry=card_registry,
        )

        # Different seeds should result in different deck orders
        deck1 = [c.instance_id for c in result1.game.players["player1"].deck.cards]
        deck2 = [c.instance_id for c in result2.game.players["player1"].deck.cards]
        assert deck1 != deck2

    def test_create_game_wrong_player_count(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
    ):
        """
        Test that game creation fails with wrong player count.
        """
        result = engine.create_game(
            player_ids=["player1"],  # Only 1 player
            decks={"player1": player1_deck},
            card_registry=card_registry,
        )

        assert not result.success
        assert "2 players" in result.message

    def test_create_game_missing_deck(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
    ):
        """
        Test that game creation fails when a player has no deck.
        """
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck},  # Missing player2's deck
            card_registry=card_registry,
        )

        assert not result.success
        assert "player2" in result.message

    def test_create_game_deck_too_small(
        self,
        engine: GameEngine,
        basic_pokemon_def: CardDefinition,
        card_registry: dict[str, CardDefinition],
    ):
        """
        Test that game creation fails with undersized deck.
        """
        small_deck = [
            CardInstance(instance_id=f"card-{i}", definition_id=basic_pokemon_def.id)
            for i in range(10)  # Too small
        ]

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": small_deck, "player2": small_deck},
            card_registry=card_registry,
        )

        assert not result.success
        assert "too small" in result.message

    def test_create_game_no_basic_pokemon(
        self,
        engine: GameEngine,
        energy_def: CardDefinition,
        card_registry: dict[str, CardDefinition],
    ):
        """
        Test that game creation fails when deck has no Basic Pokemon.
        """
        energy_only_deck = [
            CardInstance(instance_id=f"energy-{i}", definition_id=energy_def.id) for i in range(40)
        ]

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": energy_only_deck, "player2": energy_only_deck},
            card_registry=card_registry,
        )

        assert not result.success
        assert "Basic Pokemon" in result.message


# =============================================================================
# Action Validation Tests
# =============================================================================


class TestActionValidation:
    """Tests for action validation through the engine."""

    @pytest.fixture
    def active_game(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
    ) -> GameState:
        """Create a game and set up for play."""
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game

        # Set up active Pokemon for both players
        p1 = game.players["player1"]
        p2 = game.players["player2"]

        # Find a basic Pokemon in each hand and play to active
        for card in list(p1.hand.cards):
            card_def = card_registry.get(card.definition_id)
            if card_def and card_def.is_basic_pokemon():
                p1.hand.remove(card.instance_id)
                p1.active.add(card)
                break

        for card in list(p2.hand.cards):
            card_def = card_registry.get(card.definition_id)
            if card_def and card_def.is_basic_pokemon():
                p2.hand.remove(card.instance_id)
                p2.active.add(card)
                break

        # Start the game
        game.phase = TurnPhase.MAIN
        return game

    def test_validate_action_wrong_turn(
        self,
        engine: GameEngine,
        active_game: GameState,
    ):
        """
        Test that actions are rejected when it's not your turn.
        """
        # It's player1's turn, player2 tries to act
        action = PassAction()
        result = engine.validate_action(active_game, "player2", action)

        assert not result.valid
        assert "Not your turn" in result.reason

    def test_validate_resign_always_allowed(
        self,
        engine: GameEngine,
        active_game: GameState,
    ):
        """
        Test that resignation is allowed even on opponent's turn.
        """
        action = ResignAction()
        result = engine.validate_action(active_game, "player2", action)

        assert result.valid


# =============================================================================
# Action Execution Tests
# =============================================================================


class TestActionExecution:
    """Tests for action execution through the engine."""

    @pytest.fixture
    def ready_game(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
        energy_def: CardDefinition,
    ) -> GameState:
        """Create a game ready for action execution testing."""
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game

        # Set up active and bench Pokemon
        p1 = game.players["player1"]
        p2 = game.players["player2"]

        # Player 1: active + 1 bench + energy in hand
        active1 = CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id)
        bench1 = CardInstance(instance_id="p1-bench-1", definition_id=basic_pokemon_def.id)
        energy1 = CardInstance(instance_id="p1-energy-hand", definition_id=energy_def.id)
        p1.active.add(active1)
        p1.bench.add(bench1)
        p1.hand.add(energy1)

        # Player 2: active only
        active2 = CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id)
        p2.active.add(active2)

        game.phase = TurnPhase.MAIN
        game.turn_number = 2  # Not first turn
        return game

    @pytest.mark.asyncio
    async def test_execute_attach_energy(
        self,
        engine: GameEngine,
        ready_game: GameState,
    ):
        """
        Test executing an attach energy action.
        """
        action = AttachEnergyAction(
            energy_card_id="p1-energy-hand",
            target_pokemon_id="p1-active",
            from_energy_zone=False,
        )

        result = await engine.execute_action(ready_game, "player1", action)

        assert result.success
        assert "Energy attached" in result.message

        # Verify energy is attached (now stored as CardInstance objects)
        active = ready_game.players["player1"].get_active_pokemon()
        assert any(e.instance_id == "p1-energy-hand" for e in active.attached_energy)

    @pytest.mark.asyncio
    async def test_execute_attack(
        self,
        engine: GameEngine,
        ready_game: GameState,
        energy_def: CardDefinition,
    ):
        """
        Test executing an attack action.
        """
        # Attach energy - energy CardInstance is stored directly on the Pokemon
        p1 = ready_game.players["player1"]
        energy = CardInstance(instance_id="attack-energy", definition_id=energy_def.id)
        p1.get_active_pokemon().attach_energy(energy)

        # Need to be in ATTACK phase for attack action
        ready_game.phase = TurnPhase.ATTACK

        action = AttackAction(attack_index=0)

        result = await engine.execute_action(ready_game, "player1", action)

        assert result.success
        assert "Thunder Shock" in result.message
        assert "20 damage" in result.message

        # Verify damage dealt
        defender = ready_game.players["player2"].get_active_pokemon()
        assert defender.damage == 20

        # Phase should advance to END
        assert ready_game.phase == TurnPhase.END

    @pytest.mark.asyncio
    async def test_execute_pass(
        self,
        engine: GameEngine,
        ready_game: GameState,
    ):
        """
        Test executing a pass action.
        """
        action = PassAction()

        result = await engine.execute_action(ready_game, "player1", action)

        assert result.success
        assert ready_game.phase == TurnPhase.END

    @pytest.mark.asyncio
    async def test_execute_resign(
        self,
        engine: GameEngine,
        ready_game: GameState,
    ):
        """
        Test executing a resignation.
        """
        action = ResignAction()

        result = await engine.execute_action(ready_game, "player1", action)

        assert result.success
        assert result.win_result is not None
        assert result.win_result.winner_id == "player2"
        assert result.win_result.end_reason == GameEndReason.RESIGNATION

    @pytest.mark.asyncio
    async def test_execute_retreat(
        self,
        engine: GameEngine,
        ready_game: GameState,
    ):
        """
        Test executing a retreat action.
        """
        # Attach energy for retreat cost (now a CardInstance)
        active = ready_game.players["player1"].get_active_pokemon()
        retreat_energy = CardInstance(instance_id="retreat-energy", definition_id="fire_energy")
        active.attach_energy(retreat_energy)

        action = RetreatAction(
            new_active_id="p1-bench-1",
            energy_to_discard=["retreat-energy"],
        )

        result = await engine.execute_action(ready_game, "player1", action)

        assert result.success
        assert "Retreated" in result.message

        # Verify Pokemon swapped
        new_active = ready_game.players["player1"].get_active_pokemon()
        assert new_active.instance_id == "p1-bench-1"

    @pytest.mark.asyncio
    async def test_execute_invalid_action_fails(
        self,
        engine: GameEngine,
        ready_game: GameState,
    ):
        """
        Test that invalid actions return failure.
        """
        # Try to attach non-existent energy
        action = AttachEnergyAction(
            energy_card_id="nonexistent-energy",
            target_pokemon_id="p1-active",
            from_energy_zone=False,
        )

        result = await engine.execute_action(ready_game, "player1", action)

        assert not result.success


# =============================================================================
# Turn Management Tests
# =============================================================================


class TestTurnManagement:
    """Tests for turn management through the engine."""

    @pytest.fixture
    def game_at_start(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
    ) -> GameState:
        """Create a game at the start of a turn."""
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game

        # Set up active Pokemon
        p1 = game.players["player1"]
        p2 = game.players["player2"]
        p1.active.add(CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id))
        p2.active.add(CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id))

        game.phase = TurnPhase.SETUP
        game.turn_number = 1
        return game

    def test_start_turn(
        self,
        engine: GameEngine,
        game_at_start: GameState,
    ):
        """
        Test starting a turn through the engine.
        """
        result = engine.start_turn(game_at_start)

        assert result.success
        assert game_at_start.phase == TurnPhase.MAIN

    def test_end_turn(
        self,
        engine: GameEngine,
        game_at_start: GameState,
    ):
        """
        Test ending a turn through the engine.
        """
        game_at_start.phase = TurnPhase.END
        original_player = game_at_start.current_player_id

        result = engine.end_turn(game_at_start)

        assert result.success
        assert game_at_start.current_player_id != original_player


# =============================================================================
# Win Condition Tests
# =============================================================================


class TestWinConditions:
    """Tests for win condition detection through the engine."""

    @pytest.fixture
    def near_win_game(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
    ) -> GameState:
        """Create a game where player1 is close to winning."""
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game

        # Player 1 has 3 points (needs 4 to win)
        game.players["player1"].score = 3

        # Set up active Pokemon
        p1 = game.players["player1"]
        p2 = game.players["player2"]
        p1.active.add(CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id))

        # Player 2 active has 50 damage (60 HP, 20 more will KO)
        p2_active = CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id)
        p2_active.damage = 50
        p2.active.add(p2_active)

        game.phase = TurnPhase.MAIN
        game.turn_number = 5
        return game

    @pytest.mark.asyncio
    async def test_knockout_triggers_win(
        self,
        engine: GameEngine,
        near_win_game: GameState,
        energy_def: CardDefinition,
    ):
        """
        Test that a knockout that reaches win threshold ends the game.
        """
        # Attack will deal 20 damage, which KOs the defender (50 + 20 = 70 > 60)
        # This gives player1 their 4th point, winning the game
        p1 = near_win_game.players["player1"]
        energy = CardInstance(instance_id="attack-energy", definition_id=energy_def.id)
        # Energy CardInstance is now stored directly on the Pokemon
        p1.get_active_pokemon().attach_energy(energy)

        # Need to be in ATTACK phase
        near_win_game.phase = TurnPhase.ATTACK

        action = AttackAction(attack_index=0)
        result = await engine.execute_action(near_win_game, "player1", action)

        assert result.success
        assert result.win_result is not None
        assert result.win_result.winner_id == "player1"
        assert result.win_result.end_reason == GameEndReason.PRIZES_TAKEN

    def test_timeout_ends_game(
        self,
        engine: GameEngine,
        near_win_game: GameState,
    ):
        """
        Test that timeout triggers win for opponent.
        """
        result = engine.handle_timeout(near_win_game, "player1")

        assert result.success
        assert result.win_result is not None
        assert result.win_result.winner_id == "player2"
        assert result.win_result.end_reason == GameEndReason.TIMEOUT


# =============================================================================
# Visibility Tests
# =============================================================================


class TestVisibility:
    """Tests for visibility filtering through the engine."""

    @pytest.fixture
    def active_game(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
    ) -> GameState:
        """Create an active game for visibility tests."""
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        return result.game

    def test_get_visible_state(
        self,
        engine: GameEngine,
        active_game: GameState,
    ):
        """
        Test getting a visible state through the engine.
        """
        visible = engine.get_visible_state(active_game, "player1")

        assert visible.viewer_id == "player1"
        assert visible.game_id == active_game.game_id
        assert len(visible.players) == 2

    def test_get_spectator_state(
        self,
        engine: GameEngine,
        active_game: GameState,
    ):
        """
        Test getting a spectator state through the engine.
        """
        visible = engine.get_spectator_state(active_game)

        assert visible.viewer_id == "__spectator__"
        # No hands should be visible
        for player_state in visible.players.values():
            assert len(player_state.hand.cards) == 0


# =============================================================================
# Integration Scenario Tests
# =============================================================================


class TestIntegrationScenarios:
    """Full game scenario tests."""

    @pytest.mark.asyncio
    async def test_full_turn_cycle(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
    ):
        """
        Test a complete turn cycle: create game -> start turn -> actions -> end turn.
        """
        # Create game
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game
        assert result.success

        # Set up active Pokemon
        p1 = game.players["player1"]
        p2 = game.players["player2"]
        p1.active.add(CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id))
        p2.active.add(CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id))

        # Start turn
        start_result = engine.start_turn(game)
        assert start_result.success
        assert game.phase == TurnPhase.MAIN

        # Execute pass action
        pass_result = await engine.execute_action(game, game.current_player_id, PassAction())
        assert pass_result.success
        assert game.phase == TurnPhase.END

        # End turn
        end_result = engine.end_turn(game)
        assert end_result.success

        # Verify turn advanced
        assert game.current_player_id == "player2"


# =============================================================================
# Engine End Turn Knockout Tests (Issue #6 verification)
# =============================================================================


class TestEngineEndTurnKnockouts:
    """Tests verifying GameEngine.end_turn() processes status knockouts.

    These tests verify Issue #6 from SYSTEM_REVIEW.md:
    The engine's end_turn() should properly process knockouts from status
    damage, including moving Pokemon to discard, awarding points, and
    triggering win conditions.
    """

    @pytest.fixture
    def knockout_game(
        self,
        seeded_rng: SeededRandom,
        basic_pokemon_def: CardDefinition,
        energy_def: CardDefinition,
    ) -> tuple[GameEngine, GameState]:
        """Create a game set up for knockout testing."""
        engine = GameEngine(rules=RulesConfig(), rng=seeded_rng)

        # Create decks with minimum required size (40 cards)
        p1_deck = [
            CardInstance(instance_id=f"p1-deck-{i}", definition_id=basic_pokemon_def.id)
            for i in range(40)
        ]
        p2_deck = [
            CardInstance(instance_id=f"p2-deck-{i}", definition_id=basic_pokemon_def.id)
            for i in range(40)
        ]

        registry = {
            basic_pokemon_def.id: basic_pokemon_def,
            energy_def.id: energy_def,
        }

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": p1_deck, "player2": p2_deck},
            card_registry=registry,
        )
        game = result.game

        # Set up active Pokemon for both players
        p1 = game.players["player1"]
        p2 = game.players["player2"]
        p1.active.add(CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id))
        p1.bench.add(CardInstance(instance_id="p1-bench", definition_id=basic_pokemon_def.id))
        p2.active.add(CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id))

        # Start the game properly
        engine.start_turn(game)
        game.phase = TurnPhase.END

        return engine, game

    def test_engine_end_turn_processes_status_knockout(
        self,
        knockout_game: tuple[GameEngine, GameState],
        basic_pokemon_def: CardDefinition,
    ):
        """
        Test that engine.end_turn() processes status knockouts completely.

        Verifies Issue #6: The engine should process knockouts from
        TurnManager's end_turn result, not just return them in the result.
        """
        engine, game = knockout_game
        player = game.get_current_player()
        active = player.get_active_pokemon()

        # Set up for lethal poison damage
        active.damage = 50
        active.add_status(StatusCondition.POISONED)

        result = engine.end_turn(game)

        # Engine should successfully process the turn
        assert result.success
        # Pokemon should be in discard
        assert "p1-active" in player.discard
        # Active zone should be empty
        assert len(player.active) == 0

    def test_engine_end_turn_returns_win_result_on_knockout(
        self,
        knockout_game: tuple[GameEngine, GameState],
        basic_pokemon_def: CardDefinition,
    ):
        """
        Test that engine.end_turn() returns win result when knockout causes win.

        If the status knockout triggers a win condition, the ActionResult
        should contain the win_result.
        """
        engine, game = knockout_game
        player = game.get_current_player()
        active = player.get_active_pokemon()

        # Clear bench so knockout causes "no Pokemon" win
        player.bench.cards.clear()

        # Set up for lethal poison damage
        active.damage = 50
        active.add_status(StatusCondition.POISONED)

        result = engine.end_turn(game)

        # Should have win result
        assert result.win_result is not None
        assert result.win_result.winner_id == "player2"
        assert result.win_result.end_reason == GameEndReason.NO_POKEMON

    def test_engine_end_turn_awards_points_for_status_knockout(
        self,
        knockout_game: tuple[GameEngine, GameState],
        basic_pokemon_def: CardDefinition,
    ):
        """
        Test that engine.end_turn() awards points to opponent for status KO.

        The full knockout flow through the engine should award points.
        """
        engine, game = knockout_game
        player = game.get_current_player()
        opponent = game.players["player2"]
        active = player.get_active_pokemon()

        initial_score = opponent.score

        # Set up for lethal poison damage
        active.damage = 50
        active.add_status(StatusCondition.POISONED)

        engine.end_turn(game)

        # Opponent should have gained 1 point
        assert opponent.score == initial_score + 1


# =============================================================================
# SelectPrizeAction Tests (Issue #11)
# =============================================================================


class TestSelectPrizeAction:
    """Tests for SelectPrizeAction execution.

    These tests verify Issue #11: SelectPrizeAction executor is implemented
    and prize card mode works correctly.
    """

    @pytest.fixture
    def prize_game(
        self,
        seeded_rng: SeededRandom,
        basic_pokemon_def: CardDefinition,
        energy_def: CardDefinition,
    ) -> tuple[GameEngine, GameState]:
        """Create a game with prize card mode enabled."""
        from app.core.config import PrizeConfig

        rules = RulesConfig()
        rules.prizes = PrizeConfig(
            count=6,
            use_prize_cards=True,
            prize_selection_random=False,  # Player chooses prizes
        )
        engine = GameEngine(rules=rules, rng=seeded_rng)

        # Create decks (need 40+ cards)
        p1_deck = [
            CardInstance(instance_id=f"p1-deck-{i}", definition_id=basic_pokemon_def.id)
            for i in range(40)
        ]
        p2_deck = [
            CardInstance(instance_id=f"p2-deck-{i}", definition_id=basic_pokemon_def.id)
            for i in range(40)
        ]

        registry = {
            basic_pokemon_def.id: basic_pokemon_def,
            energy_def.id: energy_def,
        }

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": p1_deck, "player2": p2_deck},
            card_registry=registry,
        )
        game = result.game

        # Set up active Pokemon
        p1 = game.players["player1"]
        p2 = game.players["player2"]
        p1.active.add(CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id))
        p2.active.add(CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id))

        return engine, game

    @pytest.mark.asyncio
    async def test_execute_select_prize_adds_to_hand(
        self,
        prize_game: tuple[GameEngine, GameState],
    ):
        """
        Test that selecting a prize adds the card to hand.

        Basic prize selection functionality.
        """
        from app.core.models.actions import SelectPrizeAction
        from app.core.models.game_state import ForcedAction

        engine, game = prize_game
        player = game.players["player1"]

        # Ensure player has prizes
        assert len(player.prizes) > 0

        # Start turn (this draws a card), then record hand size
        engine.start_turn(game)
        initial_hand_size = len(player.hand)
        initial_prize_count = len(player.prizes)

        # Set up forced action (as if a knockout happened)
        game.add_forced_action(
            ForcedAction(
                player_id="player1",
                action_type="select_prize",
                reason="Select a prize card",
                params={"count": 1},
            )
        )

        result = await engine.execute_action(game, "player1", SelectPrizeAction(prize_index=0))

        assert result.success
        assert len(player.hand) == initial_hand_size + 1
        assert len(player.prizes) == initial_prize_count - 1

    @pytest.mark.asyncio
    async def test_execute_select_prize_invalid_index(
        self,
        prize_game: tuple[GameEngine, GameState],
    ):
        """
        Test that invalid prize index is rejected.

        Validation should catch out-of-bounds index.
        """
        from app.core.models.actions import SelectPrizeAction

        engine, game = prize_game
        engine.start_turn(game)

        # Try to select prize at invalid index
        result = await engine.execute_action(game, "player1", SelectPrizeAction(prize_index=99))

        # Should fail validation
        assert not result.success

    @pytest.mark.asyncio
    async def test_execute_select_prize_triggers_win(
        self,
        prize_game: tuple[GameEngine, GameState],
    ):
        """
        Test that taking the last prize triggers a win.

        Prize card mode win condition.
        """
        from app.core.models.actions import SelectPrizeAction
        from app.core.models.game_state import ForcedAction

        engine, game = prize_game
        player = game.players["player1"]

        # Remove all but one prize
        while len(player.prizes) > 1:
            player.prizes.cards.pop()

        engine.start_turn(game)
        # Set up forced action for prize selection
        game.add_forced_action(
            ForcedAction(
                player_id="player1",
                action_type="select_prize",
                reason="Select your last prize card",
                params={"count": 1},
            )
        )

        result = await engine.execute_action(game, "player1", SelectPrizeAction(prize_index=0))

        assert result.success
        assert result.win_result is not None
        assert result.win_result.winner_id == "player1"
        assert result.win_result.end_reason == GameEndReason.PRIZES_TAKEN


# =============================================================================
# Turn Limit Check Tests (Issue #15)
# =============================================================================


class TestTurnLimitCheck:
    """Tests verifying turn limit is checked at turn start.

    These tests verify Issue #15: start_turn() checks turn limit before
    proceeding with the turn.
    """

    @pytest.fixture
    def turn_limit_game(
        self,
        seeded_rng: SeededRandom,
        basic_pokemon_def: CardDefinition,
        energy_def: CardDefinition,
    ) -> tuple[GameEngine, GameState]:
        """Create a game with turn limit enabled."""
        from app.core.config import WinConditionsConfig

        rules = RulesConfig()
        rules.win_conditions = WinConditionsConfig(
            turn_limit_enabled=True,
            turn_limit=10,
        )
        engine = GameEngine(rules=rules, rng=seeded_rng)

        # Create decks
        p1_deck = [
            CardInstance(instance_id=f"p1-deck-{i}", definition_id=basic_pokemon_def.id)
            for i in range(40)
        ]
        p2_deck = [
            CardInstance(instance_id=f"p2-deck-{i}", definition_id=basic_pokemon_def.id)
            for i in range(40)
        ]

        registry = {
            basic_pokemon_def.id: basic_pokemon_def,
            energy_def.id: energy_def,
        }

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": p1_deck, "player2": p2_deck},
            card_registry=registry,
        )
        game = result.game

        # Set up active Pokemon
        p1 = game.players["player1"]
        p2 = game.players["player2"]
        p1.active.add(CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id))
        p2.active.add(CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id))

        return engine, game

    def test_start_turn_turn_limit_ends_game(
        self,
        turn_limit_game: tuple[GameEngine, GameState],
    ):
        """
        Test that start_turn ends game when turn limit is exceeded.

        Verifies Issue #15: turn limit is checked before turn starts.
        When one player has a higher score, they win with TURN_LIMIT reason.
        """
        engine, game = turn_limit_game

        # Give player1 a score advantage
        game.players["player1"].score = 2
        game.players["player2"].score = 1

        # Set turn number past limit
        game.turn_number = 11  # Limit is 10

        result = engine.start_turn(game)

        assert not result.success
        assert result.win_result is not None
        assert result.win_result.end_reason == GameEndReason.TURN_LIMIT
        assert result.win_result.winner_id == "player1"

    def test_start_turn_turn_limit_winner_by_score(
        self,
        turn_limit_game: tuple[GameEngine, GameState],
    ):
        """
        Test that higher score wins when turn limit is reached.

        Standard turn limit resolution - higher score wins.
        """
        engine, game = turn_limit_game

        # Set scores
        game.players["player1"].score = 3
        game.players["player2"].score = 5

        # Set turn number past limit
        game.turn_number = 11

        result = engine.start_turn(game)

        assert not result.success
        assert result.win_result is not None
        assert result.win_result.winner_id == "player2"  # Higher score
        assert result.win_result.loser_id == "player1"

    def test_start_turn_turn_limit_not_exceeded(
        self,
        turn_limit_game: tuple[GameEngine, GameState],
    ):
        """
        Test that turn proceeds normally when limit not exceeded.

        Game should continue if turn number is within limit.
        """
        engine, game = turn_limit_game

        # Set turn number within limit
        game.turn_number = 5

        result = engine.start_turn(game)

        assert result.success
        assert result.win_result is None

    def test_start_turn_turn_limit_disabled(
        self,
        seeded_rng: SeededRandom,
        basic_pokemon_def: CardDefinition,
        energy_def: CardDefinition,
    ):
        """
        Test that turn limit is not checked when disabled.

        Game should continue past "limit" if feature is disabled.
        """
        from app.core.config import WinConditionsConfig

        rules = RulesConfig()
        rules.win_conditions = WinConditionsConfig(
            turn_limit_enabled=False,
            turn_limit=10,
        )
        engine = GameEngine(rules=rules, rng=seeded_rng)

        # Create game
        p1_deck = [
            CardInstance(instance_id=f"p1-deck-{i}", definition_id=basic_pokemon_def.id)
            for i in range(40)
        ]
        p2_deck = [
            CardInstance(instance_id=f"p2-deck-{i}", definition_id=basic_pokemon_def.id)
            for i in range(40)
        ]

        registry = {
            basic_pokemon_def.id: basic_pokemon_def,
            energy_def.id: energy_def,
        }

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": p1_deck, "player2": p2_deck},
            card_registry=registry,
        )
        game = result.game

        # Set up active Pokemon
        game.players["player1"].active.add(
            CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id)
        )
        game.players["player2"].active.add(
            CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id)
        )

        # Set turn number way past "limit"
        game.turn_number = 100

        start_result = engine.start_turn(game)

        # Should succeed - limit is disabled
        assert start_result.success
        assert start_result.win_result is None

    def test_start_turn_turn_limit_draw(
        self,
        turn_limit_game: tuple[GameEngine, GameState],
    ):
        """
        Test that equal scores result in a draw when turn limit is reached.

        When both players have the same score at turn limit, the game
        ends in a draw with DRAW end reason.
        """
        engine, game = turn_limit_game

        # Set equal scores
        game.players["player1"].score = 3
        game.players["player2"].score = 3

        # Set turn number past limit
        game.turn_number = 11  # Limit is 10

        result = engine.start_turn(game)

        assert not result.success
        assert result.win_result is not None
        assert result.win_result.end_reason == GameEndReason.DRAW
        assert result.win_result.winner_id == ""  # No winner in a draw


# =============================================================================
# Game Creation - Energy Deck and Prize Card Tests
# =============================================================================


class TestGameCreationAdvanced:
    """Tests for advanced game creation features."""

    @pytest.fixture
    def energy_deck(self, energy_def: CardDefinition) -> list[CardInstance]:
        """Create an energy deck for Pokemon Pocket style energy."""
        return [
            CardInstance(instance_id=f"edeck-energy-{i}", definition_id=energy_def.id)
            for i in range(20)
        ]

    def test_create_game_with_energy_deck(
        self,
        seeded_rng: SeededRandom,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        energy_deck: list[CardInstance],
    ):
        """
        Test game creation with separate energy decks (Pokemon Pocket style).

        Verifies energy decks are shuffled and assigned to each player's
        energy_deck zone for the flip-to-gain mechanic.
        """
        engine = GameEngine(rules=RulesConfig(), rng=seeded_rng)

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
            energy_decks={
                "player1": list(energy_deck),
                "player2": [
                    CardInstance(instance_id=f"p2-edeck-{i}", definition_id="lightning-energy")
                    for i in range(20)
                ],
            },
        )

        assert result.success
        game = result.game
        # Energy decks should be populated
        assert len(game.players["player1"].energy_deck) == 20
        assert len(game.players["player2"].energy_deck) == 20

    def test_create_game_with_prize_cards(
        self,
        seeded_rng: SeededRandom,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
    ):
        """
        Test game creation with prize card mode enabled.

        Verifies prize cards are dealt from the deck to the prizes zone.
        """
        rules = RulesConfig()
        rules.prizes.use_prize_cards = True
        rules.prizes.count = 6
        engine = GameEngine(rules=rules, rng=seeded_rng)

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )

        assert result.success
        game = result.game
        # Prize cards should be dealt
        assert len(game.players["player1"].prizes) == 6
        assert len(game.players["player2"].prizes) == 6

    def test_create_game_deck_too_large(
        self,
        engine: GameEngine,
        basic_pokemon_def: CardDefinition,
        energy_def: CardDefinition,
        card_registry: dict[str, CardDefinition],
    ):
        """
        Test that game creation fails with oversized deck.

        Default max deck size is 60, so 70 cards should fail.
        """
        large_deck = []
        for i in range(10):
            large_deck.append(
                CardInstance(instance_id=f"pokemon-{i}", definition_id=basic_pokemon_def.id)
            )
        for i in range(60):
            large_deck.append(CardInstance(instance_id=f"energy-{i}", definition_id=energy_def.id))

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": large_deck, "player2": large_deck},
            card_registry=card_registry,
        )

        assert not result.success
        assert "too large" in result.message


# =============================================================================
# Action Execution - Play Pokemon Tests
# =============================================================================


class TestPlayPokemonAction:
    """Tests for playing Pokemon from hand to field."""

    @pytest.fixture
    def game_for_pokemon(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
    ) -> GameState:
        """Create a game ready for playing Pokemon."""
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game

        # Player 1 has no active yet (clear setup)
        p1 = game.players["player1"]
        p1.active.cards.clear()

        # Add a basic Pokemon to hand
        basic = CardInstance(instance_id="hand-pikachu", definition_id=basic_pokemon_def.id)
        p1.hand.add(basic)

        # Player 2 has active
        p2 = game.players["player2"]
        p2.active.add(CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id))

        game.phase = TurnPhase.MAIN
        game.turn_number = 2
        return game

    @pytest.mark.asyncio
    async def test_play_pokemon_to_active(
        self,
        engine: GameEngine,
        game_for_pokemon: GameState,
    ):
        """
        Test playing a Basic Pokemon to active when no active exists.

        Pokemon should be placed in the active zone and marked with turn played.
        """
        action = PlayPokemonAction(card_instance_id="hand-pikachu")
        result = await engine.execute_action(game_for_pokemon, "player1", action)

        assert result.success
        assert "active" in result.message.lower()

        # Verify Pokemon is now active
        active = game_for_pokemon.players["player1"].get_active_pokemon()
        assert active is not None
        assert active.instance_id == "hand-pikachu"
        assert active.turn_played == game_for_pokemon.turn_number

    @pytest.mark.asyncio
    async def test_play_pokemon_to_bench(
        self,
        engine: GameEngine,
        game_for_pokemon: GameState,
        basic_pokemon_def: CardDefinition,
    ):
        """
        Test playing a Basic Pokemon to bench when active exists.

        Pokemon should be placed on the bench when player already has an active.
        """
        # Give player an active first
        p1 = game_for_pokemon.players["player1"]
        p1.active.add(
            CardInstance(instance_id="existing-active", definition_id=basic_pokemon_def.id)
        )

        action = PlayPokemonAction(card_instance_id="hand-pikachu")
        result = await engine.execute_action(game_for_pokemon, "player1", action)

        assert result.success
        assert "bench" in result.message.lower()

        # Verify Pokemon is on bench
        assert "hand-pikachu" in p1.bench

    @pytest.mark.asyncio
    async def test_play_pokemon_card_not_in_hand(
        self,
        engine: GameEngine,
        game_for_pokemon: GameState,
    ):
        """
        Test that playing a non-existent card fails.
        """
        action = PlayPokemonAction(card_instance_id="nonexistent-card")
        result = await engine.execute_action(game_for_pokemon, "player1", action)

        assert not result.success


# =============================================================================
# Action Execution - Evolve Pokemon Tests
# =============================================================================


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

    @pytest.fixture
    def evolution_card_def(self, basic_pokemon_def: CardDefinition) -> CardDefinition:
        """Create a Stage 1 evolution card that evolves from basic_pokemon_def."""
        return CardDefinition(
            id="raichu-evo",
            name="Raichu",
            card_type=CardType.POKEMON,
            stage=PokemonStage.STAGE_1,
            variant=PokemonVariant.NORMAL,
            hp=100,
            pokemon_type=EnergyType.LIGHTNING,
            evolves_from=basic_pokemon_def.name,  # Must match the Pokemon's name
            attacks=[
                Attack(
                    name="Thunder",
                    damage=80,
                    cost=[EnergyType.LIGHTNING, EnergyType.LIGHTNING],
                ),
            ],
            retreat_cost=2,
        )

    @pytest.fixture
    def game_for_evolution(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
        evolution_card_def: CardDefinition,
    ) -> tuple[GameState, dict[str, CardDefinition]]:
        """Create a game ready for evolution testing."""
        # Add evolution card to registry
        registry = dict(card_registry)
        registry[evolution_card_def.id] = evolution_card_def

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=registry,
        )
        game = result.game

        p1 = game.players["player1"]
        p2 = game.players["player2"]

        # Player 1: active Pikachu (played last turn) with energy and damage
        active = CardInstance(instance_id="active-pikachu", definition_id=basic_pokemon_def.id)
        active.turn_played = 1  # Played last turn, can evolve
        active.damage = 20
        # Attach energy as CardInstance
        energy = CardInstance(instance_id="attached-energy-1", definition_id="fire_energy")
        active.attach_energy(energy)
        p1.active.add(active)

        # Player 1: Raichu in hand
        evo = CardInstance(instance_id="hand-raichu", definition_id=evolution_card_def.id)
        p1.hand.add(evo)

        # Player 2 active
        p2.active.add(CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id))

        game.phase = TurnPhase.MAIN
        game.turn_number = 2  # Not first turn
        return game, registry

    @pytest.mark.asyncio
    async def test_evolve_pokemon_success(
        self,
        engine: GameEngine,
        game_for_evolution: tuple[GameState, dict],
    ):
        """
        Test successfully evolving a Pokemon.

        Evolution should transfer energy and damage from the base Pokemon.
        """
        game, registry = game_for_evolution

        action = EvolvePokemonAction(
            evolution_card_id="hand-raichu",
            target_pokemon_id="active-pikachu",
        )
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        assert "evolved" in result.message.lower()

        # Verify evolution happened
        active = game.players["player1"].get_active_pokemon()
        assert active.instance_id == "hand-raichu"
        assert active.definition_id == "raichu-evo"

        # Verify energy and damage transferred
        assert any(e.instance_id == "attached-energy-1" for e in active.attached_energy)
        assert active.damage == 20

        # Verify old Pokemon is in evolution stack (cards_underneath), not discard
        assert any(c.instance_id == "active-pikachu" for c in active.cards_underneath)

    @pytest.mark.asyncio
    async def test_evolve_pokemon_not_in_hand(
        self,
        engine: GameEngine,
        game_for_evolution: tuple[GameState, dict],
    ):
        """
        Test that evolving with a card not in hand fails.
        """
        game, _ = game_for_evolution

        action = EvolvePokemonAction(
            evolution_card_id="nonexistent-raichu",
            target_pokemon_id="active-pikachu",
        )
        result = await engine.execute_action(game, "player1", action)

        assert not result.success
        assert "not found in hand" in result.message.lower()

    @pytest.mark.asyncio
    async def test_evolve_pokemon_target_not_found(
        self,
        engine: GameEngine,
        game_for_evolution: tuple[GameState, dict],
    ):
        """
        Test that evolving a non-existent target fails.
        """
        game, _ = game_for_evolution

        action = EvolvePokemonAction(
            evolution_card_id="hand-raichu",
            target_pokemon_id="nonexistent-pikachu",
        )
        result = await engine.execute_action(game, "player1", action)

        assert not result.success
        # Evolution card should be returned to hand
        assert "hand-raichu" in game.players["player1"].hand


# =============================================================================
# Action Execution - Play Trainer Tests
# =============================================================================


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

    @pytest.fixture
    def item_card_def(self) -> CardDefinition:
        """Create an Item trainer card."""
        return CardDefinition(
            id="potion-001",
            name="Potion",
            card_type=CardType.TRAINER,
            trainer_type=TrainerType.ITEM,
        )

    @pytest.fixture
    def supporter_card_def(self) -> CardDefinition:
        """Create a Supporter trainer card."""
        return CardDefinition(
            id="professor-001",
            name="Professor's Research",
            card_type=CardType.TRAINER,
            trainer_type=TrainerType.SUPPORTER,
        )

    @pytest.fixture
    def stadium_card_def(self) -> CardDefinition:
        """Create a Stadium trainer card."""
        return CardDefinition(
            id="stadium-001",
            name="Training Court",
            card_type=CardType.TRAINER,
            trainer_type=TrainerType.STADIUM,
        )

    @pytest.fixture
    def game_for_trainer(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
        item_card_def: CardDefinition,
        supporter_card_def: CardDefinition,
        stadium_card_def: CardDefinition,
    ) -> tuple[GameState, dict[str, CardDefinition]]:
        """Create a game ready for trainer card testing."""
        registry = dict(card_registry)
        registry[item_card_def.id] = item_card_def
        registry[supporter_card_def.id] = supporter_card_def
        registry[stadium_card_def.id] = stadium_card_def

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=registry,
        )
        game = result.game

        p1 = game.players["player1"]
        p2 = game.players["player2"]

        # Active Pokemon
        p1.active.add(CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id))
        p2.active.add(CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id))

        # Add trainers to hand
        p1.hand.add(CardInstance(instance_id="hand-potion", definition_id=item_card_def.id))
        p1.hand.add(CardInstance(instance_id="hand-professor", definition_id=supporter_card_def.id))
        p1.hand.add(CardInstance(instance_id="hand-stadium", definition_id=stadium_card_def.id))

        game.phase = TurnPhase.MAIN
        game.turn_number = 2
        return game, registry

    @pytest.mark.asyncio
    async def test_play_item_card(
        self,
        engine: GameEngine,
        game_for_trainer: tuple[GameState, dict],
    ):
        """
        Test playing an Item trainer card.

        Item cards should be played and discarded, incrementing the item counter.
        """
        game, _ = game_for_trainer
        p1 = game.players["player1"]

        action = PlayTrainerAction(card_instance_id="hand-potion")
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        assert "Trainer card played" in result.message

        # Card should be discarded
        assert "hand-potion" in p1.discard
        assert "hand-potion" not in p1.hand

        # Counter should be incremented
        assert p1.items_played_this_turn == 1

    @pytest.mark.asyncio
    async def test_play_supporter_card(
        self,
        engine: GameEngine,
        game_for_trainer: tuple[GameState, dict],
    ):
        """
        Test playing a Supporter trainer card.

        Supporter cards increment the supporter counter (limited per turn).
        """
        game, _ = game_for_trainer
        p1 = game.players["player1"]

        action = PlayTrainerAction(card_instance_id="hand-professor")
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        assert p1.supporters_played_this_turn == 1

    @pytest.mark.asyncio
    async def test_play_stadium_card(
        self,
        engine: GameEngine,
        game_for_trainer: tuple[GameState, dict],
    ):
        """
        Test playing a Stadium trainer card.

        Stadium cards go into play (not discarded) and replace existing stadiums.
        """
        game, _ = game_for_trainer

        action = PlayTrainerAction(card_instance_id="hand-stadium")
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        assert "Stadium played" in result.message

        # Stadium should be in play
        assert game.stadium_in_play is not None
        assert game.stadium_in_play.instance_id == "hand-stadium"

    @pytest.fixture
    def different_stadium_def(self) -> CardDefinition:
        """Create a different stadium card for replacement tests."""
        return CardDefinition(
            id="stadium-002",
            name="Power Plant",
            card_type=CardType.TRAINER,
            trainer_type=TrainerType.STADIUM,
        )

    @pytest.mark.asyncio
    async def test_play_stadium_replaces_existing(
        self,
        engine: GameEngine,
        game_for_trainer: tuple[GameState, dict],
        stadium_card_def: CardDefinition,
        different_stadium_def: CardDefinition,
    ):
        """
        Test that playing a new Stadium replaces the existing one.

        The old stadium should be discarded.
        """
        game, _ = game_for_trainer
        # Add the different stadium to the game's card registry
        game.card_registry[different_stadium_def.id] = different_stadium_def
        p1 = game.players["player1"]

        # Put an existing stadium in play (owned by player2)
        old_stadium = CardInstance(instance_id="old-stadium", definition_id=stadium_card_def.id)
        game.stadium_in_play = old_stadium
        game.stadium_owner_id = "player2"  # player2 owns the existing stadium

        # Add a different stadium to hand (different from old one)
        new_stadium = CardInstance(
            instance_id="new-stadium", definition_id=different_stadium_def.id
        )
        p1.hand.add(new_stadium)

        action = PlayTrainerAction(card_instance_id="new-stadium")
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        # New stadium should be in play
        assert game.stadium_in_play.instance_id == "new-stadium"
        # New stadium should be owned by player1
        assert game.stadium_owner_id == "player1"
        # Old stadium should be discarded to its OWNER's discard (player2)
        p2 = game.players["player2"]
        assert "old-stadium" in p2.discard

    @pytest.mark.asyncio
    async def test_play_stadium_replace_own_stadium(
        self,
        engine: GameEngine,
        game_for_trainer: tuple[GameState, dict],
        stadium_card_def: CardDefinition,
        different_stadium_def: CardDefinition,
    ):
        """
        Test that replacing own stadium discards to own pile.

        When a player replaces their own stadium, the old one goes to
        their own discard pile (not the opponent's).
        """
        game, _ = game_for_trainer
        game.card_registry[different_stadium_def.id] = different_stadium_def
        p1 = game.players["player1"]

        # Put player1's stadium in play
        old_stadium = CardInstance(instance_id="old-stadium", definition_id=stadium_card_def.id)
        game.stadium_in_play = old_stadium
        game.stadium_owner_id = "player1"  # player1 owns the existing stadium

        # Add a different stadium to hand
        new_stadium = CardInstance(
            instance_id="new-stadium", definition_id=different_stadium_def.id
        )
        p1.hand.add(new_stadium)

        action = PlayTrainerAction(card_instance_id="new-stadium")
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        assert game.stadium_in_play.instance_id == "new-stadium"
        assert game.stadium_owner_id == "player1"
        # Old stadium goes to player1's discard (the owner)
        assert "old-stadium" in p1.discard

    @pytest.mark.asyncio
    async def test_play_first_stadium_sets_owner(
        self,
        engine: GameEngine,
        game_for_trainer: tuple[GameState, dict],
        stadium_card_def: CardDefinition,
    ):
        """
        Test that playing the first stadium sets the owner.

        When no stadium is in play, playing one sets both the
        stadium_in_play and stadium_owner_id.
        """
        game, _ = game_for_trainer
        p1 = game.players["player1"]

        # No stadium in play initially
        assert game.stadium_in_play is None
        assert game.stadium_owner_id is None

        # Add stadium to hand
        stadium = CardInstance(instance_id="first-stadium", definition_id=stadium_card_def.id)
        p1.hand.add(stadium)

        action = PlayTrainerAction(card_instance_id="first-stadium")
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        assert game.stadium_in_play.instance_id == "first-stadium"
        assert game.stadium_owner_id == "player1"

    @pytest.mark.asyncio
    async def test_play_trainer_card_not_found(
        self,
        engine: GameEngine,
        game_for_trainer: tuple[GameState, dict],
    ):
        """
        Test that playing a non-existent trainer card fails.
        """
        game, _ = game_for_trainer

        action = PlayTrainerAction(card_instance_id="nonexistent-trainer")
        result = await engine.execute_action(game, "player1", action)

        assert not result.success


# =============================================================================
# Action Execution - Use Ability Tests
# =============================================================================


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

    @pytest.fixture
    def pokemon_with_ability_def(self) -> CardDefinition:
        """Create a Pokemon with an ability."""
        return CardDefinition(
            id="pikachu-ability",
            name="Pikachu",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            variant=PokemonVariant.NORMAL,
            hp=60,
            pokemon_type=EnergyType.LIGHTNING,
            abilities=[
                Ability(
                    name="Static",
                    description="Flip a coin. If heads, your opponent's Active Pokemon is Paralyzed.",
                    effect_id="paralyze_on_flip",
                    uses_per_turn=1,
                ),
            ],
            attacks=[
                Attack(name="Thunder Shock", damage=20, cost=[EnergyType.LIGHTNING]),
            ],
            retreat_cost=1,
        )

    @pytest.fixture
    def game_for_ability(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
        pokemon_with_ability_def: CardDefinition,
    ) -> tuple[GameState, dict[str, CardDefinition]]:
        """Create a game ready for ability testing."""
        registry = dict(card_registry)
        registry[pokemon_with_ability_def.id] = pokemon_with_ability_def

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=registry,
        )
        game = result.game

        p1 = game.players["player1"]
        p2 = game.players["player2"]

        # Player 1 has Pokemon with ability as active
        active = CardInstance(
            instance_id="ability-pikachu", definition_id=pokemon_with_ability_def.id
        )
        p1.active.add(active)

        # Player 2 has active
        p2.active.add(CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id))

        game.phase = TurnPhase.MAIN
        game.turn_number = 2
        return game, registry

    @pytest.mark.asyncio
    async def test_use_ability_success(
        self,
        engine: GameEngine,
        game_for_ability: tuple[GameState, dict],
    ):
        """
        Test successfully using a Pokemon's ability.

        Ability should be marked as used.
        """
        game, _ = game_for_ability

        action = UseAbilityAction(
            pokemon_id="ability-pikachu",
            ability_index=0,
        )
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        assert "Static" in result.message

        # Ability should be recorded as used (ability index 0)
        active = game.players["player1"].get_active_pokemon()
        assert active.get_ability_uses(0) >= 1

    @pytest.mark.asyncio
    async def test_use_ability_pokemon_not_found(
        self,
        engine: GameEngine,
        game_for_ability: tuple[GameState, dict],
    ):
        """
        Test that using ability on non-existent Pokemon fails.
        """
        game, _ = game_for_ability

        action = UseAbilityAction(
            pokemon_id="nonexistent-pokemon",
            ability_index=0,
        )
        result = await engine.execute_action(game, "player1", action)

        assert not result.success
        assert "not found" in result.message.lower()

    @pytest.mark.asyncio
    async def test_use_ability_invalid_index(
        self,
        engine: GameEngine,
        game_for_ability: tuple[GameState, dict],
    ):
        """
        Test that using an invalid ability index fails.
        """
        game, _ = game_for_ability

        action = UseAbilityAction(
            pokemon_id="ability-pikachu",
            ability_index=5,  # Invalid index
        )
        result = await engine.execute_action(game, "player1", action)

        assert not result.success
        assert "Invalid ability index" in result.message


# =============================================================================
# Action Execution - Select Active Tests (Forced Action)
# =============================================================================


class TestSelectActiveAction:
    """Tests for selecting a new active Pokemon (forced action after KO)."""

    @pytest.fixture
    def game_with_forced_action(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
    ) -> GameState:
        """Create a game with a forced action to select a new active."""
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game

        p1 = game.players["player1"]
        p2 = game.players["player2"]

        # Player 1 has no active (was KO'd) but has bench
        p1.bench.add(CardInstance(instance_id="p1-bench-1", definition_id=basic_pokemon_def.id))
        p1.bench.add(CardInstance(instance_id="p1-bench-2", definition_id=basic_pokemon_def.id))

        # Player 2 has active
        p2.active.add(CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id))

        # Set forced action
        game.add_forced_action(
            ForcedAction(
                player_id="player1",
                action_type="select_active",
                reason="Active Pokemon was knocked out",
            )
        )

        game.phase = TurnPhase.MAIN
        game.turn_number = 2
        return game

    @pytest.mark.asyncio
    async def test_select_active_success(
        self,
        engine: GameEngine,
        game_with_forced_action: GameState,
    ):
        """
        Test successfully selecting a new active Pokemon.

        The selected Pokemon should move from bench to active and forced action cleared.
        """
        action = SelectActiveAction(pokemon_id="p1-bench-1")
        result = await engine.execute_action(game_with_forced_action, "player1", action)

        assert result.success
        assert "New active" in result.message

        # Pokemon should now be active
        p1 = game_with_forced_action.players["player1"]
        active = p1.get_active_pokemon()
        assert active is not None
        assert active.instance_id == "p1-bench-1"

        # Should not be on bench anymore
        assert "p1-bench-1" not in p1.bench

        # Forced action should be cleared
        assert not game_with_forced_action.has_forced_action()

    @pytest.mark.asyncio
    async def test_select_active_not_on_bench(
        self,
        engine: GameEngine,
        game_with_forced_action: GameState,
    ):
        """
        Test that selecting a Pokemon not on bench fails.
        """
        action = SelectActiveAction(pokemon_id="nonexistent-pokemon")
        result = await engine.execute_action(game_with_forced_action, "player1", action)

        assert not result.success
        assert "not found on bench" in result.message.lower()


# =============================================================================
# Deck-Out and Win Condition Edge Cases
# =============================================================================


class TestDeckOutAndEdgeCases:
    """Tests for deck-out and edge case scenarios."""

    @pytest.fixture
    def game_about_to_deck_out(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
    ) -> GameState:
        """Create a game where player1 has an empty deck (will deck out on draw)."""
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game

        p1 = game.players["player1"]
        p2 = game.players["player2"]

        # Player 1 has empty deck
        p1.deck.cards.clear()

        # Both have actives
        p1.active.add(CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id))
        p2.active.add(CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id))

        game.phase = TurnPhase.SETUP
        game.turn_number = 5
        return game

    def test_deck_out_on_turn_start(
        self,
        engine: GameEngine,
        game_about_to_deck_out: GameState,
    ):
        """
        Test that drawing with an empty deck triggers loss.

        When a player cannot draw at turn start, they lose the game.
        """
        result = engine.start_turn(game_about_to_deck_out)

        # Turn start should fail due to deck out
        assert not result.success
        assert result.win_result is not None
        assert result.win_result.winner_id == "player2"
        assert result.win_result.end_reason == GameEndReason.DECK_EMPTY

    @pytest.fixture
    def game_for_attack_edge_cases(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
    ) -> GameState:
        """Create a game for testing attack edge cases."""
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game

        p1 = game.players["player1"]
        p2 = game.players["player2"]

        p1.active.add(CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id))
        p2.active.add(CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id))

        game.phase = TurnPhase.ATTACK
        game.turn_number = 2
        return game

    @pytest.mark.asyncio
    async def test_attack_without_energy(
        self,
        engine: GameEngine,
        game_for_attack_edge_cases: GameState,
    ):
        """
        Test that attacking without required energy fails validation.
        """
        action = AttackAction(attack_index=0)
        result = await engine.execute_action(game_for_attack_edge_cases, "player1", action)

        # Should fail validation due to insufficient energy
        assert not result.success

    @pytest.mark.asyncio
    async def test_attack_invalid_index(
        self,
        engine: GameEngine,
        game_for_attack_edge_cases: GameState,
        energy_def: CardDefinition,
    ):
        """
        Test that using an invalid attack index fails.
        """
        # Attach energy so energy check passes
        p1 = game_for_attack_edge_cases.players["player1"]
        energy = CardInstance(instance_id="test-energy", definition_id=energy_def.id)
        p1.discard.add(energy)
        p1.get_active_pokemon().attach_energy(energy.instance_id)

        action = AttackAction(attack_index=99)  # Invalid index
        result = await engine.execute_action(game_for_attack_edge_cases, "player1", action)

        assert not result.success

    @pytest.mark.asyncio
    async def test_retreat_without_bench(
        self,
        engine: GameEngine,
        game_for_attack_edge_cases: GameState,
    ):
        """
        Test that retreating without a bench Pokemon fails.
        """
        game_for_attack_edge_cases.phase = TurnPhase.MAIN

        action = RetreatAction(
            new_active_id="nonexistent",
            energy_to_discard=[],
        )
        result = await engine.execute_action(game_for_attack_edge_cases, "player1", action)

        assert not result.success


# =============================================================================
# Confusion Status - Attack Tests
# =============================================================================


class TestConfusionAttack:
    """Tests for confusion status during attack execution.

    When a confused Pokemon attacks, it must flip a coin:
    - Heads: attack proceeds normally
    - Tails: attack fails and Pokemon damages itself

    These tests verify the engine correctly handles both outcomes,
    including edge cases like self-KO from confusion damage.
    """

    @pytest.mark.asyncio
    async def test_confused_attack_heads_proceeds_normally(
        self,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
        energy_def: CardDefinition,
    ):
        """
        Test that a confused Pokemon's attack proceeds normally on heads.

        When the confusion coin flip is heads, the attack should execute
        as if the Pokemon was not confused, dealing normal damage to defender.
        """
        from app.core.rng import SeededRandom

        # Create engine - we'll replace RNG after game creation
        engine = GameEngine()

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game

        # Replace RNG with one that will flip heads (seed=1 produces heads)
        engine.rng = SeededRandom(seed=1)

        p1 = game.players["player1"]
        p2 = game.players["player2"]

        attacker = CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id)
        energy = CardInstance(instance_id="attacker-energy", definition_id=energy_def.id)
        attacker.attach_energy(energy)
        p1.active.add(attacker)

        defender = CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id)
        p2.active.add(defender)

        attacker.add_status(StatusCondition.CONFUSED)
        game.current_player_id = "player1"  # Set player1's turn
        game.phase = TurnPhase.ATTACK
        game.turn_number = 2

        action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        assert "heads" in result.message.lower()
        # Defender should have taken damage
        assert defender.damage > 0
        # Attacker should not have self-damage
        assert attacker.damage == 0

    @pytest.mark.asyncio
    async def test_confused_attack_tails_fails_with_self_damage(
        self,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
        energy_def: CardDefinition,
    ):
        """
        Test that a confused Pokemon damages itself on tails.

        When the confusion coin flip is tails, the attack should fail
        and the Pokemon should deal self-damage (default 30).
        """
        from app.core.rng import SeededRandom

        # Create engine - we'll replace RNG after game creation
        engine = GameEngine()

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game

        # Replace RNG with one that will flip tails (seed=0 produces tails)
        engine.rng = SeededRandom(seed=0)

        p1 = game.players["player1"]
        p2 = game.players["player2"]

        attacker = CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id)
        energy = CardInstance(instance_id="attacker-energy", definition_id=energy_def.id)
        attacker.attach_energy(energy)
        p1.active.add(attacker)

        defender = CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id)
        p2.active.add(defender)

        attacker.add_status(StatusCondition.CONFUSED)
        game.current_player_id = "player1"  # Set player1's turn
        game.phase = TurnPhase.ATTACK
        game.turn_number = 2

        action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player1", action)

        # Action still succeeds (coin was flipped), but attack failed
        assert result.success
        assert "tails" in result.message.lower()
        assert "self-damage" in result.message.lower() or "30" in result.message
        # Attacker should have self-damage (default 30)
        assert attacker.damage == 30
        # Defender should NOT have taken damage
        assert defender.damage == 0

    @pytest.mark.asyncio
    async def test_confused_attack_tails_self_ko(
        self,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
        energy_def: CardDefinition,
    ):
        """
        Test that confusion self-damage can knock out the attacker.

        If the attacker has low HP remaining, the 30 self-damage from
        confusion can knock it out. The opponent should receive points/prizes.
        """
        from app.core.rng import SeededRandom

        # Create engine - we'll replace RNG after game creation
        engine = GameEngine()

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game

        # Replace RNG with one that will flip tails (seed=0 produces tails)
        engine.rng = SeededRandom(seed=0)

        p1 = game.players["player1"]
        p2 = game.players["player2"]

        attacker = CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id)
        energy = CardInstance(instance_id="attacker-energy", definition_id=energy_def.id)
        attacker.attach_energy(energy)
        # Give attacker damage so self-damage will KO it (60 HP Pokemon)
        attacker.damage = 40  # 40 + 30 self-damage = 70 > 60 HP
        p1.active.add(attacker)

        # Add bench Pokemon so game doesn't end immediately
        bench = CardInstance(instance_id="p1-bench", definition_id=basic_pokemon_def.id)
        p1.bench.add(bench)

        defender = CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id)
        p2.active.add(defender)

        attacker.add_status(StatusCondition.CONFUSED)
        game.current_player_id = "player1"  # Set player1's turn
        game.phase = TurnPhase.ATTACK
        game.turn_number = 2

        initial_p2_score = p2.score

        action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        # Attacker should be knocked out (in discard, not in active)
        assert p1.active.get(attacker.instance_id) is None
        assert p1.discard.get(attacker.instance_id) is not None
        # Opponent should have scored
        assert p2.score > initial_p2_score

    @pytest.mark.asyncio
    async def test_confused_attack_uses_config_self_damage(
        self,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
        energy_def: CardDefinition,
    ):
        """
        Test that confusion self-damage is configurable via RulesConfig.

        The self-damage amount should come from rules.status.confusion_self_damage.
        """
        from app.core.config import RulesConfig, StatusConfig
        from app.core.rng import SeededRandom

        # Create engine - we'll replace RNG after game creation
        engine = GameEngine()

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game

        # Replace RNG with one that will flip tails (seed=0 produces tails)
        engine.rng = SeededRandom(seed=0)

        # Custom rules with different confusion damage
        game.rules = RulesConfig(status=StatusConfig(confusion_self_damage=50))

        p1 = game.players["player1"]
        p2 = game.players["player2"]

        attacker = CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id)
        energy = CardInstance(instance_id="attacker-energy", definition_id=energy_def.id)
        attacker.attach_energy(energy)
        p1.active.add(attacker)

        defender = CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id)
        p2.active.add(defender)

        attacker.add_status(StatusCondition.CONFUSED)
        game.current_player_id = "player1"  # Set player1's turn
        game.phase = TurnPhase.ATTACK
        game.turn_number = 2

        action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        # Attacker should have custom self-damage amount
        assert attacker.damage == 50


# =============================================================================
# Attach Energy - Energy Zone Tests
# =============================================================================


class TestAttachEnergyFromEnergyZone:
    """Tests for attaching energy from energy zone (Pokemon Pocket style)."""

    @pytest.fixture
    def game_with_energy_zone(
        self,
        engine: GameEngine,
        player1_deck: list[CardInstance],
        player2_deck: list[CardInstance],
        card_registry: dict[str, CardDefinition],
        basic_pokemon_def: CardDefinition,
        energy_def: CardDefinition,
    ) -> GameState:
        """Create a game with energy in the energy zone."""
        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": player1_deck, "player2": player2_deck},
            card_registry=card_registry,
        )
        game = result.game

        p1 = game.players["player1"]
        p2 = game.players["player2"]

        # Active Pokemon
        p1.active.add(CardInstance(instance_id="p1-active", definition_id=basic_pokemon_def.id))
        p2.active.add(CardInstance(instance_id="p2-active", definition_id=basic_pokemon_def.id))

        # Energy in energy zone (flipped from energy deck)
        energy = CardInstance(instance_id="zone-energy", definition_id=energy_def.id)
        p1.energy_zone.add(energy)

        game.phase = TurnPhase.MAIN
        game.turn_number = 2
        return game

    @pytest.mark.asyncio
    async def test_attach_energy_from_zone(
        self,
        engine: GameEngine,
        game_with_energy_zone: GameState,
    ):
        """
        Test attaching energy from the energy zone.

        Energy zone is used in Pokemon Pocket style where energy is flipped
        from a separate deck into a zone, then attached from there.
        """
        action = AttachEnergyAction(
            energy_card_id="zone-energy",
            target_pokemon_id="p1-active",
            from_energy_zone=True,
        )
        result = await engine.execute_action(game_with_energy_zone, "player1", action)

        assert result.success

        # Energy should be attached to active (now stored as CardInstance)
        p1 = game_with_energy_zone.players["player1"]
        active = p1.get_active_pokemon()
        assert any(e.instance_id == "zone-energy" for e in active.attached_energy)

        # Energy zone should be empty
        assert "zone-energy" not in p1.energy_zone

    @pytest.mark.asyncio
    async def test_attach_energy_to_bench(
        self,
        engine: GameEngine,
        game_with_energy_zone: GameState,
        basic_pokemon_def: CardDefinition,
    ):
        """
        Test attaching energy to a bench Pokemon.
        """
        # Add a bench Pokemon
        p1 = game_with_energy_zone.players["player1"]
        p1.bench.add(CardInstance(instance_id="p1-bench", definition_id=basic_pokemon_def.id))

        action = AttachEnergyAction(
            energy_card_id="zone-energy",
            target_pokemon_id="p1-bench",
            from_energy_zone=True,
        )
        result = await engine.execute_action(game_with_energy_zone, "player1", action)

        assert result.success

        # Energy should be attached to bench Pokemon (now stored as CardInstance)
        bench_pokemon = p1.bench.get("p1-bench")
        assert any(e.instance_id == "zone-energy" for e in bench_pokemon.attached_energy)

    @pytest.mark.asyncio
    async def test_attach_energy_target_not_found(
        self,
        engine: GameEngine,
        game_with_energy_zone: GameState,
    ):
        """
        Test that attaching energy to non-existent Pokemon fails and returns energy.
        """
        action = AttachEnergyAction(
            energy_card_id="zone-energy",
            target_pokemon_id="nonexistent-pokemon",
            from_energy_zone=True,
        )
        result = await engine.execute_action(game_with_energy_zone, "player1", action)

        assert not result.success

        # Energy should be returned to zone
        p1 = game_with_energy_zone.players["player1"]
        assert "zone-energy" in p1.energy_zone


# =============================================================================
# Weakness and Resistance Tests
# =============================================================================


class TestWeaknessResistance:
    """Tests for weakness and resistance damage calculations.

    These tests verify that the engine correctly applies weakness and resistance
    modifiers during attack damage calculation. Tests cover:
    - Additive weakness (+X damage)
    - Multiplicative weakness (xN damage)
    - Additive resistance (-X damage)
    - Multiplicative resistance (fractional damage)
    - Combined weakness + resistance scenarios
    - Type matching (only applies when types match)
    """

    @pytest.fixture
    def lightning_attacker_def(self) -> CardDefinition:
        """Lightning-type attacker with a simple attack."""
        return CardDefinition(
            id="pikachu-test",
            name="Pikachu",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=60,
            pokemon_type=EnergyType.LIGHTNING,
            attacks=[
                Attack(name="Thunder Shock", damage=10, cost=[]),
            ],
            retreat_cost=1,
        )

    @pytest.fixture
    def fire_attacker_def(self) -> CardDefinition:
        """Fire-type attacker with a simple attack."""
        return CardDefinition(
            id="charmander-test",
            name="Charmander",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=70,
            pokemon_type=EnergyType.FIRE,
            attacks=[
                Attack(name="Ember", damage=30, cost=[]),
            ],
            retreat_cost=1,
        )

    @pytest.fixture
    def grass_weak_to_lightning_def(self) -> CardDefinition:
        """Grass Pokemon with additive Lightning weakness (+20)."""
        from app.core.enums import ModifierMode
        from app.core.models.card import WeaknessResistance

        return CardDefinition(
            id="bulbasaur-test",
            name="Bulbasaur",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=70,
            pokemon_type=EnergyType.GRASS,
            attacks=[Attack(name="Vine Whip", damage=20, cost=[])],
            weakness=WeaknessResistance(
                energy_type=EnergyType.LIGHTNING,
                mode=ModifierMode.ADDITIVE,
                value=20,
            ),
            retreat_cost=1,
        )

    @pytest.fixture
    def water_weak_to_lightning_x2_def(self) -> CardDefinition:
        """Water Pokemon with multiplicative Lightning weakness (x2)."""
        from app.core.enums import ModifierMode
        from app.core.models.card import WeaknessResistance

        return CardDefinition(
            id="squirtle-test",
            name="Squirtle",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=60,
            pokemon_type=EnergyType.WATER,
            attacks=[Attack(name="Bubble", damage=20, cost=[])],
            weakness=WeaknessResistance(
                energy_type=EnergyType.LIGHTNING,
                mode=ModifierMode.MULTIPLICATIVE,
                value=2,
            ),
            retreat_cost=1,
        )

    @pytest.fixture
    def grass_resists_water_def(self) -> CardDefinition:
        """Grass Pokemon with Water resistance (-30)."""
        from app.core.enums import ModifierMode
        from app.core.models.card import WeaknessResistance

        return CardDefinition(
            id="tangela-test",
            name="Tangela",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=80,
            pokemon_type=EnergyType.GRASS,
            attacks=[Attack(name="Bind", damage=20, cost=[])],
            resistance=WeaknessResistance(
                energy_type=EnergyType.WATER,
                mode=ModifierMode.ADDITIVE,
                value=-30,
            ),
            retreat_cost=2,
        )

    @pytest.fixture
    def grass_weak_and_resistant_def(self) -> CardDefinition:
        """Grass Pokemon with Fire weakness and Water resistance."""
        from app.core.enums import ModifierMode
        from app.core.models.card import WeaknessResistance

        return CardDefinition(
            id="oddish-test",
            name="Oddish",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=50,
            pokemon_type=EnergyType.GRASS,
            attacks=[Attack(name="Absorb", damage=10, cost=[])],
            weakness=WeaknessResistance(
                energy_type=EnergyType.FIRE,
                mode=ModifierMode.ADDITIVE,
                value=20,
            ),
            resistance=WeaknessResistance(
                energy_type=EnergyType.WATER,
                mode=ModifierMode.ADDITIVE,
                value=-30,
            ),
            retreat_cost=1,
        )

    @pytest.fixture
    def energy_def(self) -> CardDefinition:
        """Basic Lightning energy."""
        return CardDefinition(
            id="lightning-energy",
            name="Lightning Energy",
            card_type=CardType.ENERGY,
            energy_type=EnergyType.LIGHTNING,
            energy_provides=[EnergyType.LIGHTNING],
        )

    def _create_battle_game(
        self,
        attacker_def: CardDefinition,
        defender_def: CardDefinition,
        energy_def: CardDefinition,
    ) -> tuple[GameEngine, GameState]:
        """Helper to create a game ready for attack testing.

        Sets up:
        - Player1 has attacker as active, in attack phase
        - Player2 has defender as active
        - Registry with all card definitions
        """
        rng = SeededRandom(seed=42)
        engine = GameEngine(rules=RulesConfig(), rng=rng)

        # Create card instances
        attacker = CardInstance(instance_id="attacker-1", definition_id=attacker_def.id)
        defender = CardInstance(instance_id="defender-1", definition_id=defender_def.id)

        # Create minimal decks (pad with attacker copies)
        p1_deck = [
            CardInstance(instance_id=f"p1-card-{i}", definition_id=attacker_def.id)
            for i in range(40)
        ]
        p2_deck = [
            CardInstance(instance_id=f"p2-card-{i}", definition_id=defender_def.id)
            for i in range(40)
        ]
        p1_energy = [
            CardInstance(instance_id=f"p1-energy-{i}", definition_id=energy_def.id)
            for i in range(20)
        ]
        p2_energy = [
            CardInstance(instance_id=f"p2-energy-{i}", definition_id=energy_def.id)
            for i in range(20)
        ]

        result = engine.create_game(
            player_ids=["player1", "player2"],
            decks={"player1": p1_deck, "player2": p2_deck},
            energy_decks={"player1": p1_energy, "player2": p2_energy},
            card_registry={
                attacker_def.id: attacker_def,
                defender_def.id: defender_def,
                energy_def.id: energy_def,
            },
        )
        game = result.game

        # Set up the battlefield
        p1 = game.players["player1"]
        p2 = game.players["player2"]

        # Clear active zones and place our test Pokemon
        p1.active.clear()
        p2.active.clear()
        p1.active.add(attacker)
        p2.active.add(defender)

        # Set game state for attack
        game.phase = TurnPhase.ATTACK
        game.current_player_id = "player1"
        game.turn_number = 1

        return engine, game

    @pytest.mark.asyncio
    async def test_weakness_additive_increases_damage(
        self,
        lightning_attacker_def: CardDefinition,
        grass_weak_to_lightning_def: CardDefinition,
        energy_def: CardDefinition,
    ):
        """
        Test that additive weakness (+20) correctly increases damage.

        Setup: Pikachu (Lightning) attacks Bulbasaur (weak to Lightning +20)
        Attack: Thunder Shock (10 damage)
        Expected: 10 base + 20 weakness = 30 damage
        """
        engine, game = self._create_battle_game(
            lightning_attacker_def,
            grass_weak_to_lightning_def,
            energy_def,
        )

        action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        defender = game.players["player2"].get_active_pokemon()
        assert defender.damage == 30  # 10 base + 20 weakness
        assert "weakness" in result.message.lower()

    @pytest.mark.asyncio
    async def test_weakness_multiplicative_doubles_damage(
        self,
        lightning_attacker_def: CardDefinition,
        water_weak_to_lightning_x2_def: CardDefinition,
        energy_def: CardDefinition,
    ):
        """
        Test that multiplicative weakness (x2) correctly doubles damage.

        Setup: Pikachu (Lightning) attacks Squirtle (weak to Lightning x2)
        Attack: Thunder Shock (10 damage)
        Expected: 10 base x 2 = 20 damage
        """
        engine, game = self._create_battle_game(
            lightning_attacker_def,
            water_weak_to_lightning_x2_def,
            energy_def,
        )

        action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        defender = game.players["player2"].get_active_pokemon()
        assert defender.damage == 20  # 10 base x 2 weakness
        assert "weakness" in result.message.lower()

    @pytest.mark.asyncio
    async def test_resistance_reduces_damage(
        self,
        energy_def: CardDefinition,
        grass_resists_water_def: CardDefinition,
    ):
        """
        Test that additive resistance (-30) correctly reduces damage.

        Setup: Water attacker attacks Tangela (resists Water -30)
        Attack: 30 damage
        Expected: 30 base - 30 resistance = 0 damage (minimum 0)
        """

        # Create a Water attacker
        water_attacker_def = CardDefinition(
            id="psyduck-test",
            name="Psyduck",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=50,
            pokemon_type=EnergyType.WATER,
            attacks=[Attack(name="Water Gun", damage=30, cost=[])],
            retreat_cost=1,
        )

        engine, game = self._create_battle_game(
            water_attacker_def,
            grass_resists_water_def,
            energy_def,
        )
        # Add water attacker to registry
        game.card_registry[water_attacker_def.id] = water_attacker_def

        action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        defender = game.players["player2"].get_active_pokemon()
        assert defender.damage == 0  # 30 base - 30 resistance = 0 (minimum)
        assert "resistance" in result.message.lower()

    @pytest.mark.asyncio
    async def test_no_weakness_normal_damage(
        self,
        fire_attacker_def: CardDefinition,
        grass_weak_to_lightning_def: CardDefinition,
        energy_def: CardDefinition,
    ):
        """
        Test that no weakness is applied when types don't match.

        Setup: Charmander (Fire) attacks Bulbasaur (weak to Lightning, not Fire)
        Attack: Ember (30 damage)
        Expected: 30 damage (no weakness bonus)
        """
        engine, game = self._create_battle_game(
            fire_attacker_def,
            grass_weak_to_lightning_def,
            energy_def,
        )

        action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        defender = game.players["player2"].get_active_pokemon()
        assert defender.damage == 30  # Just base damage, no weakness
        assert "weakness" not in result.message.lower()

    @pytest.mark.asyncio
    async def test_damage_minimum_zero(
        self,
        energy_def: CardDefinition,
    ):
        """
        Test that damage cannot go below zero with resistance.

        Setup: Attacker deals 10 damage, defender has -30 resistance
        Expected: 0 damage (not negative)
        """
        from app.core.enums import ModifierMode
        from app.core.models.card import WeaknessResistance

        # Create a weak attacker
        weak_attacker_def = CardDefinition(
            id="magikarp-test",
            name="Magikarp",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=30,
            pokemon_type=EnergyType.WATER,
            attacks=[Attack(name="Splash", damage=10, cost=[])],
            retreat_cost=1,
        )

        # Create defender with high resistance (using BASIC for simplicity)
        high_resist_def = CardDefinition(
            id="tangela-test-2",
            name="Tangela",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=80,
            pokemon_type=EnergyType.GRASS,
            attacks=[Attack(name="Bind", damage=20, cost=[])],
            resistance=WeaknessResistance(
                energy_type=EnergyType.WATER,
                mode=ModifierMode.ADDITIVE,
                value=-30,
            ),
            retreat_cost=2,
        )

        engine, game = self._create_battle_game(
            weak_attacker_def,
            high_resist_def,
            energy_def,
        )
        game.card_registry[weak_attacker_def.id] = weak_attacker_def
        game.card_registry[high_resist_def.id] = high_resist_def

        action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        defender = game.players["player2"].get_active_pokemon()
        assert defender.damage == 0  # 10 - 30 = -20, but minimum 0

    @pytest.mark.asyncio
    async def test_weakness_causes_knockout(
        self,
        lightning_attacker_def: CardDefinition,
        energy_def: CardDefinition,
    ):
        """
        Test that weakness bonus damage can cause a knockout.

        Setup: Pikachu (60 HP attacker) attacks 30 HP defender weak to Lightning (+20)
        Attack: 10 damage + 20 weakness = 30 damage, should knock out 30 HP defender
        """
        from app.core.enums import ModifierMode
        from app.core.models.card import WeaknessResistance

        # Create a low HP defender weak to Lightning
        low_hp_defender_def = CardDefinition(
            id="voltorb-test",
            name="Voltorb",
            card_type=CardType.POKEMON,
            stage=PokemonStage.BASIC,
            hp=30,  # Will be KO'd by 30 damage
            pokemon_type=EnergyType.LIGHTNING,
            attacks=[Attack(name="Tackle", damage=10, cost=[])],
            weakness=WeaknessResistance(
                energy_type=EnergyType.LIGHTNING,  # Weak to itself for test
                mode=ModifierMode.ADDITIVE,
                value=20,
            ),
            retreat_cost=1,
        )

        engine, game = self._create_battle_game(
            lightning_attacker_def,
            low_hp_defender_def,
            energy_def,
        )
        game.card_registry[low_hp_defender_def.id] = low_hp_defender_def

        # Track initial score
        initial_score = game.players["player1"].score

        action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        # Knockout should have happened - check via:
        # 1. Message contains weakness
        assert "weakness" in result.message.lower()
        # 2. Attacker scored a point (knockout awards points)
        assert game.players["player1"].score > initial_score
        # 3. The attack state change shows final damage of 30
        attack_change = next(
            (sc for sc in result.state_changes if sc.get("type") == "attack"),
            None,
        )
        assert attack_change is not None
        assert attack_change["final_damage"] == 30  # 10 base + 20 weakness

    @pytest.mark.asyncio
    async def test_state_changes_include_weakness_info(
        self,
        lightning_attacker_def: CardDefinition,
        grass_weak_to_lightning_def: CardDefinition,
        energy_def: CardDefinition,
    ):
        """
        Test that state_changes includes weakness/resistance information.

        This is important for UI animations and logging.
        """
        engine, game = self._create_battle_game(
            lightning_attacker_def,
            grass_weak_to_lightning_def,
            energy_def,
        )

        action = AttackAction(attack_index=0)
        result = await engine.execute_action(game, "player1", action)

        assert result.success
        # Find the attack state change
        attack_change = next(
            (sc for sc in result.state_changes if sc.get("type") == "attack"),
            None,
        )
        assert attack_change is not None
        assert attack_change["base_damage"] == 10
        assert attack_change["final_damage"] == 30
        assert attack_change["weakness_applied"] is not None
        assert attack_change["weakness_applied"]["type"] == "lightning"
