"""Action validation for the Mantimon TCG game engine.

This module validates all player actions against game state and rules configuration.
It is security-critical - all actions must pass validation before execution.

The validation system checks:
1. Universal conditions (game over, player turn, phase)
2. Forced action requirements
3. Action-specific rules and limits

Usage:
    result = validate_action(game, player_id, action)
    if not result.valid:
        return error_response(result.reason)
    # Proceed with action execution

Example:
    >>> game = GameState(...)
    >>> action = AttackAction(attack_index=0)
    >>> result = validate_action(game, "player1", action)
    >>> if result.valid:
    ...     execute_attack(game, "player1", action)
"""

from pydantic import BaseModel

from app.core.enums import (
    EnergyType,
    StatusCondition,
    TrainerType,
    TurnPhase,
)
from app.core.models.actions import (
    VALID_PHASES_FOR_ACTION,
    Action,
    AttachEnergyAction,
    AttackAction,
    EvolvePokemonAction,
    PassAction,
    PlayPokemonAction,
    PlayTrainerAction,
    ResignAction,
    RetreatAction,
    SelectActiveAction,
    SelectPrizeAction,
    UseAbilityAction,
)
from app.core.models.card import CardDefinition, CardInstance
from app.core.models.game_state import GameState, PlayerState, Zone


class ValidationResult(BaseModel):
    """Result of validating a player action.

    Attributes:
        valid: Whether the action is allowed.
        reason: Explanation if the action is invalid (None if valid).
    """

    valid: bool
    reason: str | None = None


def validate_action(game: GameState, player_id: str, action: Action) -> ValidationResult:
    """Validate whether a player action is legal.

    This is the main entry point for action validation. It performs:
    1. Universal checks (game over, turn ownership, phase validity)
    2. Forced action enforcement
    3. Action-specific validation

    Args:
        game: Current game state.
        player_id: ID of the player attempting the action.
        action: The action to validate.

    Returns:
        ValidationResult with valid=True if action is allowed,
        or valid=False with a reason explaining why not.
    """
    # 1. Check if game is over
    if game.is_game_over():
        return ValidationResult(valid=False, reason="Game is over")

    # 2. Check for forced action
    if game.has_forced_action():
        return _check_forced_action(game, player_id, action)

    # 3. Check if it's the player's turn (resign always allowed)
    if action.type != "resign" and not game.is_player_turn(player_id):
        return ValidationResult(
            valid=False,
            reason=f"Not your turn (current player: {game.current_player_id})",
        )

    # 4. Check if action is valid for current phase
    valid_phases = VALID_PHASES_FOR_ACTION.get(action.type, [])
    if game.phase.value not in valid_phases:
        return ValidationResult(
            valid=False,
            reason=f"Cannot perform {action.type} during {game.phase.value} phase "
            f"(valid phases: {', '.join(valid_phases)})",
        )

    # 5. Get player state
    player = game.players.get(player_id)
    if player is None:
        return ValidationResult(valid=False, reason=f"Player {player_id} not found")

    # 6. Dispatch to action-specific validator
    validators = {
        "play_pokemon": _validate_play_pokemon,
        "evolve": _validate_evolve_pokemon,
        "attach_energy": _validate_attach_energy,
        "play_trainer": _validate_play_trainer,
        "use_ability": _validate_use_ability,
        "attack": _validate_attack,
        "retreat": _validate_retreat,
        "pass": _validate_pass,
        "select_prize": _validate_select_prize,
        "select_active": _validate_select_active,
        "resign": _validate_resign,
    }

    validator = validators.get(action.type)
    if validator is None:
        return ValidationResult(valid=False, reason=f"Unknown action type: {action.type}")

    return validator(game, player, action)


# =============================================================================
# Universal Validation Helpers
# =============================================================================


def _check_forced_action(game: GameState, player_id: str, action: Action) -> ValidationResult:
    """Check if the action satisfies a forced action requirement.

    When a forced action is pending, only the specified player can act,
    and only with the specified action type. Only the first action in the
    queue is checked - subsequent actions are handled after completion.
    """
    forced = game.get_current_forced_action()

    if forced.player_id != player_id:
        return ValidationResult(
            valid=False,
            reason=f"Waiting for {forced.player_id} to complete required action: {forced.reason}",
        )

    if action.type != forced.action_type:
        return ValidationResult(
            valid=False,
            reason=f"Must complete {forced.action_type} action: {forced.reason}",
        )

    # The action matches the forced action requirement - now validate it normally
    player = game.players.get(player_id)
    if player is None:
        return ValidationResult(valid=False, reason=f"Player {player_id} not found")

    # For forced actions, we skip phase validation since they can happen at special times
    validators = {
        "select_active": _validate_select_active,
        "select_prize": _validate_select_prize,
    }

    validator = validators.get(action.type)
    if validator is None:
        return ValidationResult(valid=False, reason=f"Invalid forced action type: {action.type}")

    return validator(game, player, action)


def _check_first_turn_restriction(
    game: GameState, restriction_name: str, action_description: str
) -> ValidationResult | None:
    """Check if a first-turn restriction applies.

    Args:
        game: Current game state.
        restriction_name: Name of the restriction flag in FirstTurnConfig.
        action_description: Human-readable description for error message.

    Returns:
        ValidationResult if restricted, None if allowed.
    """
    if not game.is_first_turn():
        return None  # Not first turn, no restriction

    # Check the specific restriction
    first_turn_config = game.rules.first_turn
    can_perform = getattr(first_turn_config, restriction_name, True)

    if not can_perform:
        return ValidationResult(valid=False, reason=f"Cannot {action_description} on first turn")

    return None  # Allowed


def _get_card_from_zone(zone: Zone, card_id: str) -> CardInstance | None:
    """Get a card from a zone by instance ID."""
    return zone.get(card_id)


def _get_card_definition(game: GameState, definition_id: str) -> CardDefinition | None:
    """Get a card definition from the registry."""
    return game.card_registry.get(definition_id)


def _get_pokemon_in_play(player: PlayerState, pokemon_id: str) -> CardInstance | None:
    """Get a Pokemon that is in play (active or bench)."""
    # Check active
    card = player.active.get(pokemon_id)
    if card:
        return card

    # Check bench
    return player.bench.get(pokemon_id)


def _is_paralyzed_or_asleep(pokemon: CardInstance) -> StatusCondition | None:
    """Check if Pokemon has Paralyzed or Asleep status.

    Returns the blocking status condition, or None if neither applies.
    """
    if pokemon.has_status(StatusCondition.PARALYZED):
        return StatusCondition.PARALYZED
    if pokemon.has_status(StatusCondition.ASLEEP):
        return StatusCondition.ASLEEP
    return None


# =============================================================================
# Energy Cost Validation
# =============================================================================


def _get_attached_energy_types(game: GameState, pokemon: CardInstance) -> list[EnergyType]:
    """Get list of energy types provided by attached energy cards.

    Special energy can provide multiple types, so this returns a flat list
    of all energy types available from attached cards.

    Args:
        game: Current game state (for card registry lookup).
        pokemon: The Pokemon with attached energy.

    Returns:
        List of EnergyType values provided by attached energy.
    """
    energy_types: list[EnergyType] = []

    # Energy cards are now CardInstance objects stored directly on the Pokemon
    for energy_card in pokemon.attached_energy:
        # Get the definition
        definition = game.card_registry.get(energy_card.definition_id)
        if definition is None:
            continue

        # Add all energy types this card provides
        if definition.energy_provides:
            energy_types.extend(definition.energy_provides)
        elif definition.energy_type:
            # Fallback: basic energy provides its type
            energy_types.append(definition.energy_type)

    return energy_types


def _can_pay_energy_cost(
    game: GameState, pokemon: CardInstance, cost: list[EnergyType]
) -> tuple[bool, str | None]:
    """Check if attached energy can satisfy an attack/retreat cost.

    Algorithm:
    1. Get list of energy types provided by all attached energy cards
    2. Create working copy of available energy
    3. For each SPECIFIC (non-colorless) energy in cost:
       - Find and remove matching energy from available
       - If no match, return failure
    4. For each COLORLESS energy in cost:
       - Find and remove any remaining energy
       - If none left, return failure
    5. Return success

    Args:
        game: Current game state.
        pokemon: The Pokemon attempting to pay the cost.
        cost: List of energy types required.

    Returns:
        Tuple of (can_pay: bool, error_message: str | None).
    """
    available = _get_attached_energy_types(game, pokemon)

    # Separate specific and colorless costs
    specific_costs = [e for e in cost if e != EnergyType.COLORLESS]
    colorless_count = len([e for e in cost if e == EnergyType.COLORLESS])

    # Try to pay specific costs first
    for energy_type in specific_costs:
        if energy_type in available:
            available.remove(energy_type)
        else:
            return (
                False,
                f"Insufficient {energy_type.value} energy "
                f"(need {specific_costs.count(energy_type)}, "
                f"have {_get_attached_energy_types(game, pokemon).count(energy_type)})",
            )

    # Pay colorless costs with any remaining energy
    if colorless_count > len(available):
        total_needed = len(cost)
        total_have = len(_get_attached_energy_types(game, pokemon))
        return (
            False,
            f"Insufficient energy (need {total_needed}, have {total_have})",
        )

    return (True, None)


# =============================================================================
# Action-Specific Validators
# =============================================================================


def _validate_play_pokemon(
    game: GameState, player: PlayerState, action: PlayPokemonAction
) -> ValidationResult:
    """Validate playing a Basic Pokemon from hand.

    Checks:
    - Card is in player's hand
    - Card is a Basic Pokemon
    - There's space to play it (bench not full, or active empty in setup)
    """
    # Check card is in hand
    card = _get_card_from_zone(player.hand, action.card_instance_id)
    if card is None:
        return ValidationResult(
            valid=False,
            reason=f"Card {action.card_instance_id} not found in hand",
        )

    # Get card definition
    definition = _get_card_definition(game, card.definition_id)
    if definition is None:
        return ValidationResult(
            valid=False,
            reason=f"Card definition {card.definition_id} not found",
        )

    # Check it's a Pokemon
    if not definition.is_pokemon():
        return ValidationResult(
            valid=False,
            reason=f"'{definition.name}' is not a Pokemon card",
        )

    # Check it's a Basic Pokemon
    if not definition.is_basic_pokemon():
        return ValidationResult(
            valid=False,
            reason=f"'{definition.name}' is a {definition.stage.value} Pokemon, not Basic. "
            "Only Basic Pokemon can be played directly.",
        )

    # Check placement is valid
    if game.phase == TurnPhase.SETUP:
        # During setup, can play to active (if space) or bench (if space)
        if action.to_active:
            max_active = game.rules.active.max_active
            current_active = len(player.active)
            if current_active >= max_active:
                return ValidationResult(
                    valid=False,
                    reason=f"Active slot(s) full ({current_active}/{max_active})",
                )
        else:
            if not player.can_bench_pokemon(game.rules):
                return ValidationResult(
                    valid=False,
                    reason=f"Bench is full ({game.rules.bench.max_size}/{game.rules.bench.max_size})",
                )
    else:
        # During main phase, can only play to bench
        if not player.can_bench_pokemon(game.rules):
            return ValidationResult(
                valid=False,
                reason=f"Bench is full ({game.rules.bench.max_size}/{game.rules.bench.max_size})",
            )

    return ValidationResult(valid=True)


def _validate_evolve_pokemon(
    game: GameState, player: PlayerState, action: EvolvePokemonAction
) -> ValidationResult:
    """Validate evolving a Pokemon.

    Checks:
    - Evolution card is in player's hand
    - Target Pokemon is in play
    - Evolution chain is correct (evolves_from matches target's name)
    - Evolution timing rules (not same turn as played, not first turn, etc.)
    """
    # Check evolution card is in hand
    evo_card = _get_card_from_zone(player.hand, action.evolution_card_id)
    if evo_card is None:
        return ValidationResult(
            valid=False,
            reason=f"Evolution card {action.evolution_card_id} not found in hand",
        )

    # Get evolution card definition
    evo_def = _get_card_definition(game, evo_card.definition_id)
    if evo_def is None:
        return ValidationResult(
            valid=False,
            reason=f"Card definition {evo_card.definition_id} not found",
        )

    # Check it's a Pokemon
    if not evo_def.is_pokemon():
        return ValidationResult(
            valid=False,
            reason=f"'{evo_def.name}' is not a Pokemon card",
        )

    # Check it's an evolution (not Basic)
    if evo_def.is_basic_pokemon() and not evo_def.requires_evolution_from_variant():
        return ValidationResult(
            valid=False,
            reason=f"'{evo_def.name}' is a Basic Pokemon, not an evolution",
        )

    # Check target Pokemon is in play
    target = _get_pokemon_in_play(player, action.target_pokemon_id)
    if target is None:
        return ValidationResult(
            valid=False,
            reason=f"Target Pokemon {action.target_pokemon_id} not found in play",
        )

    # Get target definition
    target_def = _get_card_definition(game, target.definition_id)
    if target_def is None:
        return ValidationResult(
            valid=False,
            reason=f"Target card definition {target.definition_id} not found",
        )

    # Check evolution chain matches
    if evo_def.evolves_from != target_def.name:
        return ValidationResult(
            valid=False,
            reason=f"'{evo_def.name}' evolves from '{evo_def.evolves_from}', "
            f"not from '{target_def.name}'",
        )

    # Check evolution timing - not same turn as played
    if target.turn_played == game.turn_number and not game.rules.evolution.same_turn_as_played:
        return ValidationResult(
            valid=False,
            reason=f"Cannot evolve '{target_def.name}': Pokemon was played this turn "
            f"(turn {game.turn_number})",
        )

    # Check evolution timing - not same turn as previous evolution
    if target.turn_evolved == game.turn_number and not game.rules.evolution.same_turn_as_evolution:
        return ValidationResult(
            valid=False,
            reason=f"Cannot evolve '{target_def.name}': Pokemon already evolved this turn",
        )

    # Check first turn restriction
    if game.is_first_turn() and not game.rules.evolution.first_turn_of_game:
        return ValidationResult(
            valid=False,
            reason="Cannot evolve Pokemon on the first turn of the game",
        )

    return ValidationResult(valid=True)


def _validate_attach_energy(
    game: GameState, player: PlayerState, action: AttachEnergyAction
) -> ValidationResult:
    """Validate attaching an energy card.

    Checks:
    - Energy card is in hand (or energy zone if from_energy_zone=True)
    - Card is an Energy card
    - Target Pokemon is in play and belongs to player
    - Energy attachment limit not exceeded
    - First turn restrictions
    """
    # Determine source zone
    if action.from_energy_zone:
        source_zone = player.energy_zone
        source_name = "energy zone"
    else:
        source_zone = player.hand
        source_name = "hand"

    # Check card is in source zone
    card = _get_card_from_zone(source_zone, action.energy_card_id)
    if card is None:
        return ValidationResult(
            valid=False,
            reason=f"Energy card {action.energy_card_id} not found in {source_name}",
        )

    # Get card definition
    definition = _get_card_definition(game, card.definition_id)
    if definition is None:
        return ValidationResult(
            valid=False,
            reason=f"Card definition {card.definition_id} not found",
        )

    # Check it's an Energy card
    if not definition.is_energy():
        return ValidationResult(
            valid=False,
            reason=f"'{definition.name}' is not an Energy card",
        )

    # Check target Pokemon is in play
    target = _get_pokemon_in_play(player, action.target_pokemon_id)
    if target is None:
        return ValidationResult(
            valid=False,
            reason=f"Target Pokemon {action.target_pokemon_id} not found in play",
        )

    # Check energy attachment limit
    if not player.can_attach_energy(game.rules):
        limit = game.rules.energy.attachments_per_turn
        return ValidationResult(
            valid=False,
            reason=f"Energy attachment limit reached "
            f"({player.energy_attachments_this_turn}/{limit} this turn)",
        )

    # Check first turn restriction
    restriction = _check_first_turn_restriction(game, "can_attach_energy", "attach energy")
    if restriction is not None:
        return restriction

    return ValidationResult(valid=True)


def _validate_play_trainer(
    game: GameState, player: PlayerState, action: PlayTrainerAction
) -> ValidationResult:
    """Validate playing a Trainer card.

    Checks:
    - Card is in player's hand
    - Card is a Trainer card
    - Per-subtype limits (Supporter, Stadium, Item, Tool)
    - First turn Supporter restriction
    - Stadium replacement rules
    - Tool attachment validation
    """
    # Check card is in hand
    card = _get_card_from_zone(player.hand, action.card_instance_id)
    if card is None:
        return ValidationResult(
            valid=False,
            reason=f"Card {action.card_instance_id} not found in hand",
        )

    # Get card definition
    definition = _get_card_definition(game, card.definition_id)
    if definition is None:
        return ValidationResult(
            valid=False,
            reason=f"Card definition {card.definition_id} not found",
        )

    # Check it's a Trainer card
    if not definition.is_trainer():
        return ValidationResult(
            valid=False,
            reason=f"'{definition.name}' is not a Trainer card",
        )

    # Validate based on trainer subtype
    trainer_type = definition.trainer_type

    if trainer_type == TrainerType.SUPPORTER:
        # Check supporter limit
        if not player.can_play_supporter(game.rules):
            limit = game.rules.trainer.supporters_per_turn
            return ValidationResult(
                valid=False,
                reason=f"Supporter limit reached "
                f"({player.supporters_played_this_turn}/{limit} this turn)",
            )

        # Check first turn restriction
        restriction = _check_first_turn_restriction(
            game, "can_play_supporter", "play Supporter cards"
        )
        if restriction is not None:
            return restriction

    elif trainer_type == TrainerType.STADIUM:
        # Check stadium limit
        if not player.can_play_stadium(game.rules):
            limit = game.rules.trainer.stadiums_per_turn
            return ValidationResult(
                valid=False,
                reason=f"Stadium limit reached "
                f"({player.stadiums_played_this_turn}/{limit} this turn)",
            )

        # Check if same stadium is already in play (unless same-name replace is enabled)
        if game.stadium_in_play is not None:
            current_stadium_def = _get_card_definition(game, game.stadium_in_play.definition_id)
            is_same_name = current_stadium_def and current_stadium_def.name == definition.name
            if is_same_name and not game.rules.trainer.stadium_same_name_replace:
                return ValidationResult(
                    valid=False,
                    reason=f"Cannot play '{definition.name}': same stadium already in play",
                )

    elif trainer_type == TrainerType.ITEM:
        # Check item limit (if set)
        if not player.can_play_item(game.rules):
            limit = game.rules.trainer.items_per_turn
            return ValidationResult(
                valid=False,
                reason=f"Item limit reached ({player.items_played_this_turn}/{limit} this turn)",
            )

    elif trainer_type == TrainerType.TOOL:
        # Tools attach to Pokemon - need a target
        if not action.targets:
            return ValidationResult(
                valid=False,
                reason="Tool cards require a target Pokemon",
            )

        target_id = action.targets[0]
        target = _get_pokemon_in_play(player, target_id)
        if target is None:
            return ValidationResult(
                valid=False,
                reason=f"Target Pokemon {target_id} not found in play",
            )

        # Check tool slot limit
        max_tools = game.rules.trainer.tools_per_pokemon
        current_tools = len(target.attached_tools)
        if current_tools >= max_tools:
            return ValidationResult(
                valid=False,
                reason=f"Pokemon already has maximum tools attached ({current_tools}/{max_tools})",
            )

    return ValidationResult(valid=True)


def _validate_use_ability(
    game: GameState, player: PlayerState, action: UseAbilityAction
) -> ValidationResult:
    """Validate using a Pokemon's ability.

    Checks:
    - Pokemon is in play and belongs to player
    - Ability index is valid
    - Ability usage limit not exceeded
    """
    # Check Pokemon is in play
    pokemon = _get_pokemon_in_play(player, action.pokemon_id)
    if pokemon is None:
        return ValidationResult(
            valid=False,
            reason=f"Pokemon {action.pokemon_id} not found in play",
        )

    # Get Pokemon definition
    definition = _get_card_definition(game, pokemon.definition_id)
    if definition is None:
        return ValidationResult(
            valid=False,
            reason=f"Card definition {pokemon.definition_id} not found",
        )

    # Check ability index is valid
    if action.ability_index < 0 or action.ability_index >= len(definition.abilities):
        return ValidationResult(
            valid=False,
            reason=f"Invalid ability index {action.ability_index} "
            f"(Pokemon has {len(definition.abilities)} abilities)",
        )

    # Get the ability
    ability = definition.abilities[action.ability_index]

    # Check ability usage limit
    if not pokemon.can_use_ability(ability, action.ability_index):
        uses_limit = ability.uses_per_turn
        current_uses = pokemon.get_ability_uses(action.ability_index)
        return ValidationResult(
            valid=False,
            reason=f"Ability '{ability.name}' already used ({current_uses}/{uses_limit} this turn)",
        )

    return ValidationResult(valid=True)


def _validate_attack(
    game: GameState, player: PlayerState, action: AttackAction
) -> ValidationResult:
    """Validate declaring an attack.

    Checks:
    - Player has an active Pokemon
    - Attack index is valid
    - Pokemon has enough energy for the attack cost
    - Pokemon is not Paralyzed or Asleep
    - First turn attack restrictions
    """
    # Check player has active Pokemon
    active = player.get_active_pokemon()
    if active is None:
        return ValidationResult(
            valid=False,
            reason="No active Pokemon to attack with",
        )

    # Get Pokemon definition
    definition = _get_card_definition(game, active.definition_id)
    if definition is None:
        return ValidationResult(
            valid=False,
            reason=f"Card definition {active.definition_id} not found",
        )

    # Check attack index is valid
    if action.attack_index < 0 or action.attack_index >= len(definition.attacks):
        return ValidationResult(
            valid=False,
            reason=f"Invalid attack index {action.attack_index} "
            f"(Pokemon has {len(definition.attacks)} attacks)",
        )

    # Get the attack
    attack = definition.attacks[action.attack_index]

    # Get effective attack cost (may be modified)
    effective_cost = active.effective_attack_cost(action.attack_index, attack.cost)

    # Check energy cost
    can_pay, error_msg = _can_pay_energy_cost(game, active, effective_cost)
    if not can_pay:
        return ValidationResult(
            valid=False,
            reason=f"Cannot use '{attack.name}': {error_msg}",
        )

    # Check status conditions
    blocking_status = _is_paralyzed_or_asleep(active)
    if blocking_status is not None:
        return ValidationResult(
            valid=False,
            reason=f"Cannot attack: Active Pokemon is {blocking_status.value}",
        )

    # Note: Confused Pokemon CAN attempt to attack (may fail at execution time)

    # Check first turn restriction
    restriction = _check_first_turn_restriction(game, "can_attack", "attack")
    if restriction is not None:
        return restriction

    return ValidationResult(valid=True)


def _validate_retreat(
    game: GameState, player: PlayerState, action: RetreatAction
) -> ValidationResult:
    """Validate retreating the active Pokemon.

    Checks:
    - Player has an active Pokemon
    - Player has a benched Pokemon to swap to
    - New active is on the bench
    - Can pay retreat cost with specified energy
    - Pokemon is not Paralyzed or Asleep
    - Retreat limit not exceeded
    """
    # Check player has active Pokemon
    active = player.get_active_pokemon()
    if active is None:
        return ValidationResult(
            valid=False,
            reason="No active Pokemon to retreat",
        )

    # Check player has benched Pokemon
    if not player.has_benched_pokemon():
        return ValidationResult(
            valid=False,
            reason="No benched Pokemon to switch to",
        )

    # Check new active is on bench
    new_active = player.bench.get(action.new_active_id)
    if new_active is None:
        return ValidationResult(
            valid=False,
            reason=f"Pokemon {action.new_active_id} not found on bench",
        )

    # Get active Pokemon definition
    definition = _get_card_definition(game, active.definition_id)
    if definition is None:
        return ValidationResult(
            valid=False,
            reason=f"Card definition {active.definition_id} not found",
        )

    # Calculate effective retreat cost
    base_cost = definition.retreat_cost
    effective_cost = active.effective_retreat_cost(base_cost)

    # Check if free retreat is enabled in rules
    if game.rules.retreat.free_retreat_cost:
        effective_cost = 0

    # Validate energy to discard
    energy_count = len(action.energy_to_discard)

    if energy_count < effective_cost:
        return ValidationResult(
            valid=False,
            reason=f"Insufficient energy to retreat "
            f"(need {effective_cost}, discarding {energy_count})",
        )

    # Check that all energy to discard is actually attached
    # attached_energy is now a list of CardInstance objects
    attached_energy_ids = [e.instance_id for e in active.attached_energy]
    for energy_id in action.energy_to_discard:
        if energy_id not in attached_energy_ids:
            return ValidationResult(
                valid=False,
                reason=f"Energy {energy_id} is not attached to active Pokemon",
            )

    # Check status conditions
    blocking_status = _is_paralyzed_or_asleep(active)
    if blocking_status is not None:
        return ValidationResult(
            valid=False,
            reason=f"Cannot retreat: Active Pokemon is {blocking_status.value}",
        )

    # Check retreat limit
    if not player.can_retreat(game.rules):
        limit = game.rules.retreat.retreats_per_turn
        return ValidationResult(
            valid=False,
            reason=f"Retreat limit reached ({player.retreats_this_turn}/{limit} this turn)",
        )

    return ValidationResult(valid=True)


def _validate_pass(game: GameState, player: PlayerState, action: PassAction) -> ValidationResult:
    """Validate passing without taking an action.

    Pass is always valid in the correct phases (main, attack).
    Phase validation is done in the main validate_action function.
    """
    return ValidationResult(valid=True)


def _validate_select_prize(
    game: GameState, player: PlayerState, action: SelectPrizeAction
) -> ValidationResult:
    """Validate selecting a prize card.

    Checks:
    - Game is using prize cards mode (not point-based)
    - Prize index is valid
    - Player has prizes remaining
    """
    # Check game is using prize cards
    if not game.rules.prizes.use_prize_cards:
        return ValidationResult(
            valid=False,
            reason="Game is not using prize cards (point-based scoring)",
        )

    # Check player has prizes
    prize_count = len(player.prizes)
    if prize_count == 0:
        return ValidationResult(
            valid=False,
            reason="No prize cards remaining",
        )

    # Check prize index is valid
    if action.prize_index < 0 or action.prize_index >= prize_count:
        return ValidationResult(
            valid=False,
            reason=f"Invalid prize index {action.prize_index} (valid range: 0-{prize_count - 1})",
        )

    return ValidationResult(valid=True)


def _validate_select_active(
    game: GameState, player: PlayerState, action: SelectActiveAction
) -> ValidationResult:
    """Validate selecting a new active Pokemon.

    This is used after a knockout when player needs to choose a new active.

    Checks:
    - Active zone is empty (post-KO state)
    - Selected Pokemon is on the bench
    """
    # For forced actions, we need to check the correct player
    # The forced_action handling already ensures it's the right player

    # Check active zone is empty (need to select new active)
    if player.has_active_pokemon():
        return ValidationResult(
            valid=False,
            reason="Already have an active Pokemon",
        )

    # Check selected Pokemon is on bench
    pokemon = player.bench.get(action.pokemon_id)
    if pokemon is None:
        return ValidationResult(
            valid=False,
            reason=f"Pokemon {action.pokemon_id} not found on bench",
        )

    return ValidationResult(valid=True)


def _validate_resign(
    game: GameState, player: PlayerState, action: ResignAction
) -> ValidationResult:
    """Validate resigning from the game.

    Resign is always valid - a player can concede at any time.
    """
    return ValidationResult(valid=True)
