"""
Substitution Rules - Baseball substitution validation logic.

Enforces official baseball substitution rules:
- Players can only enter game once (no re-entry)
- Substitutes must be from active roster
- Substitutes must not already be in game
- Substitutions respect game flow (timing, positions)

Author: Claude
Date: 2025-11-03
"""

import logging
from dataclasses import dataclass

from app.models.game_models import GameState, LineupPlayerState, TeamLineupState

logger = logging.getLogger(f"{__name__}.SubstitutionRules")


@dataclass
class ValidationResult:
    """Result of a substitution validation check"""

    is_valid: bool
    error_message: str | None = None
    error_code: str | None = None


class SubstitutionRules:
    """
    Baseball substitution rules validation.

    Enforces official rules:
    - No re-entry (once removed, player cannot return)
    - Roster eligibility (must be on roster)
    - Active status (substitute must be inactive)
    - Game flow (timing and situational rules)
    """

    @staticmethod
    def validate_pinch_hitter(
        state: GameState,
        player_out: LineupPlayerState,
        player_in_card_id: int,
        roster: TeamLineupState,
    ) -> ValidationResult:
        """
        Validate pinch hitter substitution.

        Rules:
        1. Substitute must be in roster
        2. Substitute must be inactive (not already in game)
        3. Player being replaced must be current batter
        4. Substituted player cannot re-enter

        Args:
            state: Current game state
            player_out: Player being replaced (must be current batter)
            player_in_card_id: Card ID of incoming player
            roster: Team's complete roster

        Returns:
            ValidationResult with is_valid and optional error message
        """
        # Check player_out is current batter
        if player_out.lineup_id != state.current_batter.lineup_id:
            return ValidationResult(
                is_valid=False,
                error_message=f"Can only pinch hit for current batter. Current batter lineup_id: {state.current_batter.lineup_id}",
                error_code="NOT_CURRENT_BATTER",
            )

        # Check player_out is active
        if not player_out.is_active:
            return ValidationResult(
                is_valid=False,
                error_message="Cannot substitute for player who is already out of game",
                error_code="PLAYER_ALREADY_OUT",
            )

        # Check substitute is in roster
        player_in = roster.get_player_by_card_id(player_in_card_id)
        if not player_in:
            return ValidationResult(
                is_valid=False,
                error_message=f"Player with card_id {player_in_card_id} not found in roster",
                error_code="NOT_IN_ROSTER",
            )

        # Check substitute is not already active
        if player_in.is_active:
            return ValidationResult(
                is_valid=False,
                error_message=f"Player {player_in.card_id} is already in the game",
                error_code="ALREADY_ACTIVE",
            )

        # All checks passed
        logger.info(
            f"Pinch hitter validation passed: "
            f"{player_out.card_id} (lineup_id {player_out.lineup_id}) → "
            f"{player_in.card_id} (card_id {player_in_card_id})"
        )
        return ValidationResult(is_valid=True)

    @staticmethod
    def validate_defensive_replacement(
        state: GameState,
        player_out: LineupPlayerState,
        player_in_card_id: int,
        new_position: str,
        roster: TeamLineupState,
        allow_mid_inning: bool = False,
    ) -> ValidationResult:
        """
        Validate defensive replacement.

        Rules:
        1. Substitute must be in roster
        2. Substitute must be inactive
        3. Substituted player cannot re-enter
        4. Generally only between half-innings (unless allow_mid_inning=True for injury)
        5. New position must be valid baseball position

        Args:
            state: Current game state
            player_out: Player being replaced
            player_in_card_id: Card ID of incoming player
            new_position: Position for incoming player (usually same as player_out)
            roster: Team's complete roster
            allow_mid_inning: If True, allows mid-inning substitution (for injuries)

        Returns:
            ValidationResult with is_valid and optional error message
        """
        # Check player_out is active
        if not player_out.is_active:
            return ValidationResult(
                is_valid=False,
                error_message="Cannot substitute for player who is already out of game",
                error_code="PLAYER_ALREADY_OUT",
            )

        # Check timing (can substitute mid-play if injury, otherwise must wait for half-inning)
        # For MVP: We'll allow any time, but log if mid-inning
        if not allow_mid_inning and state.play_count > 0:
            # In real game, would check if we're between half-innings
            # For MVP, just log a warning
            logger.warning(
                f"Defensive replacement during active play (play {state.play_count}). "
                f"In production, verify this is between half-innings or an injury."
            )

        # Validate new position
        valid_positions = ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF", "DH"]
        if new_position not in valid_positions:
            return ValidationResult(
                is_valid=False,
                error_message=f"Invalid position: {new_position}. Must be one of {valid_positions}",
                error_code="INVALID_POSITION",
            )

        # Check substitute is in roster
        player_in = roster.get_player_by_card_id(player_in_card_id)
        if not player_in:
            return ValidationResult(
                is_valid=False,
                error_message=f"Player with card_id {player_in_card_id} not found in roster",
                error_code="NOT_IN_ROSTER",
            )

        # Check substitute is not already active
        if player_in.is_active:
            return ValidationResult(
                is_valid=False,
                error_message=f"Player {player_in.card_id} is already in the game",
                error_code="ALREADY_ACTIVE",
            )

        # All checks passed
        logger.info(
            f"Defensive replacement validation passed: "
            f"{player_out.card_id} (lineup_id {player_out.lineup_id}, {player_out.position}) → "
            f"{player_in.card_id} (card_id {player_in_card_id}, {new_position})"
        )
        return ValidationResult(is_valid=True)

    @staticmethod
    def validate_pitching_change(
        state: GameState,
        pitcher_out: LineupPlayerState,
        pitcher_in_card_id: int,
        roster: TeamLineupState,
        force_change: bool = False,
    ) -> ValidationResult:
        """
        Validate pitching change.

        Rules:
        1. Substitute must be in roster
        2. Substitute must be inactive
        3. Old pitcher must be active
        4. Old pitcher must have faced at least 1 batter (unless force_change=True for injury)
        5. Substituted pitcher cannot re-enter as pitcher

        Args:
            state: Current game state
            pitcher_out: Current pitcher being replaced
            pitcher_in_card_id: Card ID of incoming pitcher
            roster: Team's complete roster
            force_change: If True, allows immediate change (for injuries/emergencies)

        Returns:
            ValidationResult with is_valid and optional error message
        """
        # Check pitcher_out is active
        if not pitcher_out.is_active:
            return ValidationResult(
                is_valid=False,
                error_message="Cannot substitute for pitcher who is already out of game",
                error_code="PLAYER_ALREADY_OUT",
            )

        # Check pitcher_out is actually a pitcher
        if pitcher_out.position != "P":
            return ValidationResult(
                is_valid=False,
                error_message=f"Player being replaced is not a pitcher (position: {pitcher_out.position})",
                error_code="NOT_A_PITCHER",
            )

        # Check minimum batters faced (unless force_change)
        if not force_change and state.play_count == 0:
            return ValidationResult(
                is_valid=False,
                error_message="Pitcher must face at least 1 batter before being replaced",
                error_code="MIN_BATTERS_NOT_MET",
            )

        # Check substitute is in roster
        pitcher_in = roster.get_player_by_card_id(pitcher_in_card_id)
        if not pitcher_in:
            return ValidationResult(
                is_valid=False,
                error_message=f"Player with card_id {pitcher_in_card_id} not found in roster",
                error_code="NOT_IN_ROSTER",
            )

        # Check substitute is not already active
        if pitcher_in.is_active:
            return ValidationResult(
                is_valid=False,
                error_message=f"Player {pitcher_in.card_id} is already in the game",
                error_code="ALREADY_ACTIVE",
            )

        # Check new pitcher can play pitcher position (MVP: skip position eligibility check)
        # In post-MVP, would check if player has 'P' in their positions list

        # All checks passed
        logger.info(
            f"Pitching change validation passed: "
            f"{pitcher_out.card_id} (lineup_id {pitcher_out.lineup_id}) → "
            f"{pitcher_in.card_id} (card_id {pitcher_in_card_id})"
        )
        return ValidationResult(is_valid=True)

    @staticmethod
    def validate_double_switch(
        state: GameState,
        player_out_1: LineupPlayerState,
        player_in_1_card_id: int,
        new_position_1: str,
        new_batting_order_1: int,
        player_out_2: LineupPlayerState,
        player_in_2_card_id: int,
        new_position_2: str,
        new_batting_order_2: int,
        roster: TeamLineupState,
    ) -> ValidationResult:
        """
        Validate double switch (two simultaneous substitutions with batting order swap).

        Complex rule used in National League to optimize pitcher's spot in batting order.

        Rules:
        1. Both substitutes must be in roster
        2. Both substitutes must be inactive
        3. Both players being replaced must be active
        4. New positions must be valid
        5. New batting orders must be different from each other
        6. Generally done between half-innings

        Args:
            state: Current game state
            player_out_1: First player being replaced
            player_in_1_card_id: Card ID of first incoming player
            new_position_1: Position for first incoming player
            new_batting_order_1: Batting order for first incoming player
            player_out_2: Second player being replaced
            player_in_2_card_id: Card ID of second incoming player
            new_position_2: Position for second incoming player
            new_batting_order_2: Batting order for second incoming player
            roster: Team's complete roster

        Returns:
            ValidationResult with is_valid and optional error message
        """
        # Validate first substitution
        result_1 = SubstitutionRules.validate_defensive_replacement(
            state=state,
            player_out=player_out_1,
            player_in_card_id=player_in_1_card_id,
            new_position=new_position_1,
            roster=roster,
            allow_mid_inning=False,
        )
        if not result_1.is_valid:
            return ValidationResult(
                is_valid=False,
                error_message=f"First substitution invalid: {result_1.error_message}",
                error_code=f"FIRST_SUB_{result_1.error_code}",
            )

        # Validate second substitution
        result_2 = SubstitutionRules.validate_defensive_replacement(
            state=state,
            player_out=player_out_2,
            player_in_card_id=player_in_2_card_id,
            new_position=new_position_2,
            roster=roster,
            allow_mid_inning=False,
        )
        if not result_2.is_valid:
            return ValidationResult(
                is_valid=False,
                error_message=f"Second substitution invalid: {result_2.error_message}",
                error_code=f"SECOND_SUB_{result_2.error_code}",
            )

        # Validate batting orders
        if new_batting_order_1 not in range(1, 10) or new_batting_order_2 not in range(
            1, 10
        ):
            return ValidationResult(
                is_valid=False,
                error_message="Batting orders must be between 1 and 9",
                error_code="INVALID_BATTING_ORDER",
            )

        if new_batting_order_1 == new_batting_order_2:
            return ValidationResult(
                is_valid=False,
                error_message="Both players cannot have same batting order",
                error_code="DUPLICATE_BATTING_ORDER",
            )

        # All checks passed
        logger.info(
            f"Double switch validation passed: "
            f"({player_out_1.card_id} → {player_in_1_card_id} at {new_position_1}, order {new_batting_order_1}) & "
            f"({player_out_2.card_id} → {player_in_2_card_id} at {new_position_2}, order {new_batting_order_2})"
        )
        return ValidationResult(is_valid=True)
