"""
Rule Validators - Validate game actions and state transitions.

Ensures all game actions follow baseball rules and state is valid.

Author: Claude
Date: 2025-10-24
"""

import logging

from app.models.game_models import DefensiveDecision, GameState, OffensiveDecision

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


class ValidationError(Exception):
    """Raised when validation fails"""

    pass


class GameValidator:
    """Validates game actions and state"""

    @staticmethod
    def validate_game_active(state: GameState) -> None:
        """Ensure game is in active state"""
        if state.status != "active":
            raise ValidationError(f"Game is not active (status: {state.status})")

    @staticmethod
    def validate_outs(outs: int) -> None:
        """Ensure outs are valid"""
        if outs < 0 or outs > 2:
            raise ValidationError(f"Invalid outs: {outs} (must be 0-2)")

    @staticmethod
    def validate_inning(inning: int, half: str) -> None:
        """Ensure inning is valid"""
        if inning < 1:
            raise ValidationError(f"Invalid inning: {inning}")
        if half not in ["top", "bottom"]:
            raise ValidationError(f"Invalid half: {half}")

    @staticmethod
    def validate_defensive_decision(
        decision: DefensiveDecision, state: GameState
    ) -> None:
        """
        Validate defensive team decision against current game state.

        Args:
            decision: Defensive decision to validate
            state: Current game state

        Raises:
            ValidationError: If decision is invalid for current situation
        """
        # # Validate alignment (already validated by Pydantic, but double-check)
        # valid_alignments = ["normal", "shifted_left", "shifted_right", "extreme_shift"]
        # if decision.alignment not in valid_alignments:
        #     raise ValidationError(f"Invalid alignment: {decision.alignment}")

        # Validate depths (already validated by Pydantic, but double-check)
        valid_infield_depths = ["infield_in", "normal", "corners_in"]
        if decision.infield_depth not in valid_infield_depths:
            raise ValidationError(f"Invalid infield depth: {decision.infield_depth}")

        valid_outfield_depths = ["normal", "shallow"]
        if decision.outfield_depth not in valid_outfield_depths:
            raise ValidationError(f"Invalid outfield depth: {decision.outfield_depth}")

        # Validate hold runners - can't hold empty bases
        occupied_bases = state.bases_occupied()
        for base in decision.hold_runners:
            if base not in [1, 2, 3]:
                raise ValidationError(
                    f"Invalid hold runner base: {base} (must be 1, 2, or 3)"
                )
            if base not in occupied_bases:
                raise ValidationError(
                    f"Cannot hold runner on base {base} - no runner present"
                )

        # Validate corners_in/infield_in depth requirements (requires runner on third)
        if decision.infield_depth in ["corners_in", "infield_in"]:
            if not state.is_runner_on_third():
                raise ValidationError(
                    f"Cannot play {decision.infield_depth} without a runner on third"
                )

        # Validate shallow outfield requires walk-off scenario
        if decision.outfield_depth == "shallow":
            # Walk-off conditions:
            # 1. Home team batting (bottom of inning)
            # 2. Bottom of final inning or later
            # 3. Tied or trailing
            # 4. Runner on base
            is_home_batting = state.half == "bottom"
            is_late_inning = state.inning >= state.regulation_innings

            if is_home_batting:
                is_close_game = state.home_score <= state.away_score
            else:
                is_close_game = state.away_score <= state.home_score

            has_runners = len(occupied_bases) > 0

            if not (
                is_home_batting and is_late_inning and is_close_game and has_runners
            ):
                raise ValidationError(
                    f"Shallow outfield only allowed in walk-off situations "
                    f"(home team batting, bottom {state.regulation_innings}th+ inning, tied/trailing, runner on base)"
                )

        logger.debug("Defensive decision validated")

    @staticmethod
    def validate_offensive_decision(
        decision: OffensiveDecision, state: GameState
    ) -> None:
        """
        Validate offensive team decision against current game state.

        Args:
            decision: Offensive decision to validate
            state: Current game state

        Raises:
            ValidationError: If decision is invalid for current situation

        Session 2 Update (2025-01-14): Added validation for action field.
        """
        # Validate action field (already validated by Pydantic, but enforce situational rules)
        occupied_bases = state.bases_occupied()

        # Validate steal action - requires steal_attempts to be specified
        if decision.action == "steal":
            if not decision.steal_attempts:
                raise ValidationError(
                    "Steal action requires steal_attempts to specify which bases to steal"
                )

        # Validate squeeze_bunt - requires R3, not with 2 outs
        if decision.action == "squeeze_bunt":
            if not state.is_runner_on_third():
                raise ValidationError("Squeeze bunt requires a runner on third base")
            if state.outs >= 2:
                raise ValidationError("Squeeze bunt cannot be used with 2 outs")

        # Validate check_jump - requires runner on base (lead runner only OR both if 1st+3rd)
        if decision.action == "check_jump":
            if len(occupied_bases) == 0:
                raise ValidationError("Check jump requires at least one runner on base")
            # Lead runner validation: can't check jump at 2nd if R3 exists
            if state.is_runner_on_second() and state.is_runner_on_third():
                raise ValidationError(
                    "Check jump not allowed for trail runner (R2) when R3 is on base"
                )

        # Validate sac_bunt - cannot be used with 2 outs
        if decision.action == "sac_bunt":
            if state.outs >= 2:
                raise ValidationError("Sacrifice bunt cannot be used with 2 outs")

        # Validate hit_and_run action - requires runner on base
        if decision.action == "hit_and_run":
            if len(occupied_bases) == 0:
                raise ValidationError(
                    "Hit and run action requires at least one runner on base"
                )

        # Validate steal attempts (when provided)
        for base in decision.steal_attempts:
            # Validate steal base is valid (2, 3, or 4 for home)
            if base not in [2, 3, 4]:
                raise ValidationError(
                    f"Invalid steal attempt to base {base} (must be 2, 3, or 4)"
                )

            # Must have runner on base-1 to steal base
            stealing_from = base - 1
            if stealing_from not in occupied_bases:
                raise ValidationError(
                    f"Cannot steal base {base} - no runner on base {stealing_from}"
                )

        logger.debug("Offensive decision validated")

    @staticmethod
    def validate_defensive_lineup_positions(lineup: list) -> None:
        """
        Validate defensive lineup has exactly 1 active player per position.

        Args:
            lineup: List of LineupPlayerState objects

        Raises:
            ValidationError: If any position is missing or duplicated
        """
        required_positions = ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF"]

        # Count active players per position
        position_counts: dict[str, int] = {}
        for player in lineup:
            if player.is_active:
                pos = player.position
                position_counts[pos] = position_counts.get(pos, 0) + 1

        # Check each required position has exactly 1 active player
        errors = []
        for pos in required_positions:
            count = position_counts.get(pos, 0)
            if count == 0:
                errors.append(f"Missing active player at {pos}")
            elif count > 1:
                errors.append(f"Multiple active players at {pos} ({count} players)")

        if errors:
            raise ValidationError(f"Invalid defensive lineup: {'; '.join(errors)}")

        logger.debug("Defensive lineup positions validated")

    @staticmethod
    def can_continue_inning(state: GameState) -> bool:
        """Check if inning can continue using state's outs_per_inning"""
        return state.outs < state.outs_per_inning

    @staticmethod
    def is_game_over(state: GameState) -> bool:
        """Check if game is complete using state's regulation_innings"""
        reg = state.regulation_innings

        # Game over after regulation innings if score not tied
        if state.inning >= reg and state.half == "bottom":
            if state.home_score != state.away_score:
                return True
            # Home team wins if ahead in bottom of final inning
            if state.home_score > state.away_score:
                return True
        # Also check if we're in extras and bottom team is ahead
        if state.inning > reg and state.half == "bottom":
            if state.home_score > state.away_score:
                return True
        return False


# Singleton instance
game_validator = GameValidator()
