"""
Unit tests for SubstitutionRules validation logic.

Tests all validation rules for pinch hitter, defensive replacement,
pitching change, and double switch substitutions.
"""
import pytest
from uuid import uuid4

from app.core.substitution_rules import SubstitutionRules, ValidationResult
from app.models.game_models import GameState, LineupPlayerState, TeamLineupState


@pytest.fixture
def game_state():
    """Create test game state with minimal setup."""
    state = GameState(
        game_id=uuid4(),
        league_id='sba',
        home_team_id=1,
        away_team_id=2,
        current_batter=LineupPlayerState(
            lineup_id=10, card_id=101, position='CF', batting_order=1, is_active=True
        )
    )
    return state


@pytest.fixture
def roster():
    """Create test roster with active and bench players."""
    players = [
        # Active players
        LineupPlayerState(lineup_id=10, card_id=101, position='CF', batting_order=1, is_active=True),
        LineupPlayerState(lineup_id=11, card_id=102, position='SS', batting_order=2, is_active=True),
        LineupPlayerState(lineup_id=12, card_id=103, position='P', batting_order=9, is_active=True),
        LineupPlayerState(lineup_id=13, card_id=104, position='C', batting_order=3, is_active=True),
        LineupPlayerState(lineup_id=14, card_id=105, position='1B', batting_order=4, is_active=True),
        LineupPlayerState(lineup_id=15, card_id=106, position='2B', batting_order=5, is_active=True),
        LineupPlayerState(lineup_id=16, card_id=107, position='3B', batting_order=6, is_active=True),
        LineupPlayerState(lineup_id=17, card_id=108, position='LF', batting_order=7, is_active=True),
        LineupPlayerState(lineup_id=18, card_id=109, position='RF', batting_order=8, is_active=True),
        # Bench players
        LineupPlayerState(lineup_id=20, card_id=201, position='CF', batting_order=None, is_active=False),
        LineupPlayerState(lineup_id=21, card_id=202, position='SS', batting_order=None, is_active=False),
        LineupPlayerState(lineup_id=22, card_id=203, position='P', batting_order=None, is_active=False),
    ]
    return TeamLineupState(team_id=1, players=players)


class TestPinchHitterValidation:
    """Test pinch hitter validation rules."""

    def test_pinch_hitter_success(self, game_state, roster):
        """Test successful pinch hitter validation."""
        player_out = roster.get_player_by_lineup_id(10)

        result = SubstitutionRules.validate_pinch_hitter(
            state=game_state,
            player_out=player_out,
            player_in_card_id=201,
            roster=roster
        )

        assert result.is_valid
        assert result.error_message is None
        assert result.error_code is None

    def test_pinch_hitter_not_current_batter(self, game_state, roster):
        """Test pinch hit fails when not current batter."""
        player_out = roster.get_player_by_lineup_id(11)  # SS, not current batter

        result = SubstitutionRules.validate_pinch_hitter(
            state=game_state,
            player_out=player_out,
            player_in_card_id=201,
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "NOT_CURRENT_BATTER"
        assert "current batter" in result.error_message.lower()

    def test_pinch_hitter_player_already_out(self, game_state, roster):
        """Test pinch hit fails when player already out of game."""
        player_out = roster.get_player_by_lineup_id(10)
        player_out.is_active = False  # Mark as already removed

        result = SubstitutionRules.validate_pinch_hitter(
            state=game_state,
            player_out=player_out,
            player_in_card_id=201,
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "PLAYER_ALREADY_OUT"
        assert "already out" in result.error_message.lower()

    def test_pinch_hitter_substitute_not_in_roster(self, game_state, roster):
        """Test pinch hit fails when substitute not in roster."""
        player_out = roster.get_player_by_lineup_id(10)

        result = SubstitutionRules.validate_pinch_hitter(
            state=game_state,
            player_out=player_out,
            player_in_card_id=999,  # Not in roster
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "NOT_IN_ROSTER"
        assert "not found in roster" in result.error_message.lower()

    def test_pinch_hitter_substitute_already_active(self, game_state, roster):
        """Test pinch hit fails when substitute already in game."""
        player_out = roster.get_player_by_lineup_id(10)

        result = SubstitutionRules.validate_pinch_hitter(
            state=game_state,
            player_out=player_out,
            player_in_card_id=102,  # SS - already active
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "ALREADY_ACTIVE"
        assert "already in the game" in result.error_message.lower()

    def test_pinch_hitter_for_bench_player(self, game_state, roster):
        """Test pinch hit fails for inactive player."""
        # Set current batter to a bench player (shouldn't happen but test edge case)
        bench_player = roster.get_player_by_lineup_id(20)
        game_state.current_batter = bench_player

        result = SubstitutionRules.validate_pinch_hitter(
            state=game_state,
            player_out=bench_player,
            player_in_card_id=201,
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "PLAYER_ALREADY_OUT"


class TestDefensiveReplacementValidation:
    """Test defensive replacement validation rules."""

    def test_defensive_replacement_success(self, game_state, roster):
        """Test successful defensive replacement validation."""
        player_out = roster.get_player_by_lineup_id(11)  # SS

        result = SubstitutionRules.validate_defensive_replacement(
            state=game_state,
            player_out=player_out,
            player_in_card_id=202,
            new_position='SS',
            roster=roster
        )

        assert result.is_valid
        assert result.error_message is None

    def test_defensive_replacement_change_position(self, game_state, roster):
        """Test defensive replacement with position change."""
        player_out = roster.get_player_by_lineup_id(11)  # SS

        result = SubstitutionRules.validate_defensive_replacement(
            state=game_state,
            player_out=player_out,
            player_in_card_id=202,
            new_position='2B',  # Different position
            roster=roster
        )

        assert result.is_valid

    def test_defensive_replacement_player_already_out(self, game_state, roster):
        """Test defensive replacement fails when player already out."""
        player_out = roster.get_player_by_lineup_id(11)
        player_out.is_active = False

        result = SubstitutionRules.validate_defensive_replacement(
            state=game_state,
            player_out=player_out,
            player_in_card_id=202,
            new_position='SS',
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "PLAYER_ALREADY_OUT"

    def test_defensive_replacement_substitute_not_in_roster(self, game_state, roster):
        """Test defensive replacement fails when substitute not in roster."""
        player_out = roster.get_player_by_lineup_id(11)

        result = SubstitutionRules.validate_defensive_replacement(
            state=game_state,
            player_out=player_out,
            player_in_card_id=999,
            new_position='SS',
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "NOT_IN_ROSTER"

    def test_defensive_replacement_substitute_already_active(self, game_state, roster):
        """Test defensive replacement fails when substitute already active."""
        player_out = roster.get_player_by_lineup_id(11)

        result = SubstitutionRules.validate_defensive_replacement(
            state=game_state,
            player_out=player_out,
            player_in_card_id=102,  # Already active SS
            new_position='SS',
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "ALREADY_ACTIVE"

    def test_defensive_replacement_invalid_position(self, game_state, roster):
        """Test defensive replacement fails with invalid position."""
        player_out = roster.get_player_by_lineup_id(11)

        result = SubstitutionRules.validate_defensive_replacement(
            state=game_state,
            player_out=player_out,
            player_in_card_id=202,
            new_position='XX',  # Invalid position
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "INVALID_POSITION"

    def test_defensive_replacement_all_valid_positions(self, game_state, roster):
        """Test all valid baseball positions accepted."""
        player_out = roster.get_player_by_lineup_id(11)
        valid_positions = ['P', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF', 'DH']

        for position in valid_positions:
            result = SubstitutionRules.validate_defensive_replacement(
                state=game_state,
                player_out=player_out,
                player_in_card_id=202,
                new_position=position,
                roster=roster
            )
            assert result.is_valid, f"Position {position} should be valid"

    def test_defensive_replacement_mid_inning_warning(self, game_state, roster, caplog):
        """Test mid-inning replacement logs warning."""
        import logging
        caplog.set_level(logging.WARNING)

        game_state.play_count = 5  # Mid-inning
        player_out = roster.get_player_by_lineup_id(11)

        result = SubstitutionRules.validate_defensive_replacement(
            state=game_state,
            player_out=player_out,
            player_in_card_id=202,
            new_position='SS',
            roster=roster,
            allow_mid_inning=False
        )

        # Should still be valid but log warning
        assert result.is_valid
        assert any("Defensive replacement during active play" in record.message for record in caplog.records)

    def test_defensive_replacement_mid_inning_allowed(self, game_state, roster):
        """Test mid-inning replacement with allow_mid_inning=True."""
        game_state.play_count = 5
        player_out = roster.get_player_by_lineup_id(11)

        result = SubstitutionRules.validate_defensive_replacement(
            state=game_state,
            player_out=player_out,
            player_in_card_id=202,
            new_position='SS',
            roster=roster,
            allow_mid_inning=True
        )

        assert result.is_valid


class TestPitchingChangeValidation:
    """Test pitching change validation rules."""

    def test_pitching_change_success(self, game_state, roster):
        """Test successful pitching change validation."""
        game_state.play_count = 5  # Pitcher faced at least 1 batter
        pitcher_out = roster.get_player_by_lineup_id(12)  # P

        result = SubstitutionRules.validate_pitching_change(
            state=game_state,
            pitcher_out=pitcher_out,
            pitcher_in_card_id=203,
            roster=roster
        )

        assert result.is_valid
        assert result.error_message is None

    def test_pitching_change_player_already_out(self, game_state, roster):
        """Test pitching change fails when pitcher already out."""
        pitcher_out = roster.get_player_by_lineup_id(12)
        pitcher_out.is_active = False

        result = SubstitutionRules.validate_pitching_change(
            state=game_state,
            pitcher_out=pitcher_out,
            pitcher_in_card_id=203,
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "PLAYER_ALREADY_OUT"

    def test_pitching_change_not_a_pitcher(self, game_state, roster):
        """Test pitching change fails when player not a pitcher."""
        non_pitcher = roster.get_player_by_lineup_id(11)  # SS

        result = SubstitutionRules.validate_pitching_change(
            state=game_state,
            pitcher_out=non_pitcher,
            pitcher_in_card_id=203,
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "NOT_A_PITCHER"

    def test_pitching_change_min_batters_not_met(self, game_state, roster):
        """Test pitching change fails when min batters not faced."""
        game_state.play_count = 0  # No batters faced yet
        pitcher_out = roster.get_player_by_lineup_id(12)

        result = SubstitutionRules.validate_pitching_change(
            state=game_state,
            pitcher_out=pitcher_out,
            pitcher_in_card_id=203,
            roster=roster,
            force_change=False
        )

        assert not result.is_valid
        assert result.error_code == "MIN_BATTERS_NOT_MET"

    def test_pitching_change_force_change(self, game_state, roster):
        """Test force_change bypasses min batters requirement."""
        game_state.play_count = 0  # No batters faced
        pitcher_out = roster.get_player_by_lineup_id(12)

        result = SubstitutionRules.validate_pitching_change(
            state=game_state,
            pitcher_out=pitcher_out,
            pitcher_in_card_id=203,
            roster=roster,
            force_change=True  # Injury/emergency
        )

        assert result.is_valid

    def test_pitching_change_substitute_not_in_roster(self, game_state, roster):
        """Test pitching change fails when substitute not in roster."""
        game_state.play_count = 5
        pitcher_out = roster.get_player_by_lineup_id(12)

        result = SubstitutionRules.validate_pitching_change(
            state=game_state,
            pitcher_out=pitcher_out,
            pitcher_in_card_id=999,
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "NOT_IN_ROSTER"

    def test_pitching_change_substitute_already_active(self, game_state, roster):
        """Test pitching change fails when substitute already active."""
        game_state.play_count = 5
        pitcher_out = roster.get_player_by_lineup_id(12)

        result = SubstitutionRules.validate_pitching_change(
            state=game_state,
            pitcher_out=pitcher_out,
            pitcher_in_card_id=103,  # Already active pitcher
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "ALREADY_ACTIVE"


class TestDoubleSwitchValidation:
    """Test double switch validation rules."""

    def test_double_switch_success(self, game_state, roster):
        """Test successful double switch validation."""
        player_out_1 = roster.get_player_by_lineup_id(11)  # SS, batting 2
        player_out_2 = roster.get_player_by_lineup_id(12)  # P, batting 9

        result = SubstitutionRules.validate_double_switch(
            state=game_state,
            player_out_1=player_out_1,
            player_in_1_card_id=202,
            new_position_1='SS',
            new_batting_order_1=9,  # Swap with pitcher
            player_out_2=player_out_2,
            player_in_2_card_id=203,
            new_position_2='P',
            new_batting_order_2=2,  # Swap with SS
            roster=roster
        )

        assert result.is_valid

    def test_double_switch_first_sub_invalid(self, game_state, roster):
        """Test double switch fails when first substitution invalid."""
        player_out_1 = roster.get_player_by_lineup_id(11)
        player_out_1.is_active = False  # Make first sub invalid
        player_out_2 = roster.get_player_by_lineup_id(12)

        result = SubstitutionRules.validate_double_switch(
            state=game_state,
            player_out_1=player_out_1,
            player_in_1_card_id=202,
            new_position_1='SS',
            new_batting_order_1=9,
            player_out_2=player_out_2,
            player_in_2_card_id=203,
            new_position_2='P',
            new_batting_order_2=2,
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code.startswith("FIRST_SUB_")

    def test_double_switch_second_sub_invalid(self, game_state, roster):
        """Test double switch fails when second substitution invalid."""
        player_out_1 = roster.get_player_by_lineup_id(11)
        player_out_2 = roster.get_player_by_lineup_id(12)
        player_out_2.is_active = False  # Make second sub invalid

        result = SubstitutionRules.validate_double_switch(
            state=game_state,
            player_out_1=player_out_1,
            player_in_1_card_id=202,
            new_position_1='SS',
            new_batting_order_1=9,
            player_out_2=player_out_2,
            player_in_2_card_id=203,
            new_position_2='P',
            new_batting_order_2=2,
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code.startswith("SECOND_SUB_")

    def test_double_switch_invalid_batting_order_range(self, game_state, roster):
        """Test double switch fails with invalid batting order."""
        player_out_1 = roster.get_player_by_lineup_id(11)
        player_out_2 = roster.get_player_by_lineup_id(12)

        result = SubstitutionRules.validate_double_switch(
            state=game_state,
            player_out_1=player_out_1,
            player_in_1_card_id=202,
            new_position_1='SS',
            new_batting_order_1=10,  # Invalid - must be 1-9
            player_out_2=player_out_2,
            player_in_2_card_id=203,
            new_position_2='P',
            new_batting_order_2=2,
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "INVALID_BATTING_ORDER"

    def test_double_switch_duplicate_batting_order(self, game_state, roster):
        """Test double switch fails with duplicate batting order."""
        player_out_1 = roster.get_player_by_lineup_id(11)
        player_out_2 = roster.get_player_by_lineup_id(12)

        result = SubstitutionRules.validate_double_switch(
            state=game_state,
            player_out_1=player_out_1,
            player_in_1_card_id=202,
            new_position_1='SS',
            new_batting_order_1=5,
            player_out_2=player_out_2,
            player_in_2_card_id=203,
            new_position_2='P',
            new_batting_order_2=5,  # Same as first - invalid
            roster=roster
        )

        assert not result.is_valid
        assert result.error_code == "DUPLICATE_BATTING_ORDER"

    def test_double_switch_all_valid_batting_orders(self, game_state, roster):
        """Test all valid batting order combinations accepted."""
        player_out_1 = roster.get_player_by_lineup_id(11)
        player_out_2 = roster.get_player_by_lineup_id(12)

        # Test all valid batting order swaps (1-9)
        for order_1 in range(1, 10):
            for order_2 in range(1, 10):
                if order_1 == order_2:
                    continue  # Skip duplicates

                result = SubstitutionRules.validate_double_switch(
                    state=game_state,
                    player_out_1=player_out_1,
                    player_in_1_card_id=202,
                    new_position_1='SS',
                    new_batting_order_1=order_1,
                    player_out_2=player_out_2,
                    player_in_2_card_id=203,
                    new_position_2='P',
                    new_batting_order_2=order_2,
                    roster=roster
                )

                assert result.is_valid, f"Batting orders {order_1} and {order_2} should be valid"


class TestValidationResultDataclass:
    """Test ValidationResult dataclass functionality."""

    def test_valid_result_creation(self):
        """Test creating valid ValidationResult."""
        result = ValidationResult(is_valid=True)

        assert result.is_valid
        assert result.error_message is None
        assert result.error_code is None

    def test_invalid_result_creation(self):
        """Test creating invalid ValidationResult with error."""
        result = ValidationResult(
            is_valid=False,
            error_message="Test error",
            error_code="TEST_ERROR"
        )

        assert not result.is_valid
        assert result.error_message == "Test error"
        assert result.error_code == "TEST_ERROR"

    def test_result_with_only_message(self):
        """Test ValidationResult with message but no code."""
        result = ValidationResult(
            is_valid=False,
            error_message="Test error"
        )

        assert not result.is_valid
        assert result.error_message == "Test error"
        assert result.error_code is None
