"""
Integration Tests for GameEngine

Tests complete game flow including:
- Single at-bat execution
- Full half-inning (3 outs)
- Lineup validation
- Snapshot tracking
- Batting order cycling

These tests require database access (marked with @pytest.mark.integration).
"""
import pytest
from uuid import uuid4

from app.core.state_manager import state_manager
from app.core.game_engine import game_engine
from app.core.validators import ValidationError
from app.database.operations import DatabaseOperations
from app.models.game_models import DefensiveDecision, OffensiveDecision


@pytest.mark.integration
@pytest.mark.asyncio
class TestSingleAtBat:
    """Test single at-bat execution"""

    async def test_complete_at_bat_flow(self):
        """Test complete at-bat flow: create → start → decisions → resolve"""
        # Create game
        game_id = uuid4()
        db_ops = DatabaseOperations()

        await db_ops.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        # Create dummy lineups
        positions = ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF"]

        # Away team lineup
        for i in range(1, 10):
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=2,
                player_id=100 + i,
                position=positions[i-1],
                batting_order=i
            )

        # Home team lineup
        for i in range(1, 10):
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=1,
                player_id=200 + i,
                position=positions[i-1],
                batting_order=i
            )

        # Create in state manager
        state = await state_manager.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )
        assert state.status == "pending"

        # Start game
        state = await game_engine.start_game(game_id)
        assert state.status == "active"
        assert state.inning == 1
        assert state.half == "top"
        assert state.outs == 0

        # Submit defensive decision
        def_decision = DefensiveDecision(
            alignment="normal",
            infield_depth="normal",
            outfield_depth="normal"
        )
        state = await game_engine.submit_defensive_decision(game_id, def_decision)
        assert state.pending_decision == "offensive"

        # Submit offensive decision
        off_decision = OffensiveDecision(approach="normal")
        state = await game_engine.submit_offensive_decision(game_id, off_decision)
        assert state.pending_decision == "resolution"

        # Resolve play
        result = await game_engine.resolve_play(game_id)
        assert result is not None
        assert result.outcome is not None
        assert result.ab_roll is not None

        # Verify state updated
        final_state = await game_engine.get_game_state(game_id)
        assert final_state.play_count == 1
        assert final_state.last_play_result is not None


@pytest.mark.integration
@pytest.mark.asyncio
class TestFullInning:
    """Test complete half-inning execution"""

    async def test_full_half_inning_three_outs(self):
        """Test playing until 3 outs completes half-inning"""
        # Create and start game
        game_id = uuid4()
        db_ops = DatabaseOperations()

        await db_ops.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        # Create dummy lineups
        positions = ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF"]
        for i in range(1, 10):
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=2,
                player_id=100 + i,
                position=positions[i-1],
                batting_order=i
            )
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=1,
                player_id=200 + i,
                position=positions[i-1],
                batting_order=i
            )

        state = await state_manager.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )
        await game_engine.start_game(game_id)

        at_bat_count = 0
        initial_inning = 1
        initial_half = "top"

        # Play until 3 outs
        while True:
            state = await game_engine.get_game_state(game_id)

            # Check if inning changed
            if state.inning != initial_inning or state.half != initial_half:
                break

            # Safety check
            if at_bat_count > 50:
                pytest.fail("Safety limit reached - something wrong with inning advancement")

            # Submit decisions
            await game_engine.submit_defensive_decision(game_id, DefensiveDecision(alignment="normal"))
            await game_engine.submit_offensive_decision(game_id, OffensiveDecision(approach="normal"))

            # Resolve
            result = await game_engine.resolve_play(game_id)
            at_bat_count += 1

        # Verify inning advanced
        final_state = await game_engine.get_game_state(game_id)
        assert final_state.inning == 1
        assert final_state.half == "bottom"
        assert final_state.outs == 0  # Reset after inning change
        assert at_bat_count >= 3  # At least 3 at-bats for 3 outs


@pytest.mark.integration
@pytest.mark.asyncio
class TestLineupValidation:
    """Test lineup validation at game start"""

    async def test_start_game_fails_with_no_lineups(self):
        """Test starting game with no lineups fails"""
        game_id = uuid4()
        db_ops = DatabaseOperations()

        await db_ops.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        state = await state_manager.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )

        # Should raise ValidationError
        with pytest.raises(ValidationError) as exc_info:
            await game_engine.start_game(game_id)

        assert "lineup incomplete" in str(exc_info.value).lower()

    async def test_start_game_fails_with_incomplete_lineup(self):
        """Test starting game with incomplete lineup (< 9 players) fails"""
        game_id = uuid4()
        db_ops = DatabaseOperations()

        await db_ops.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        # Add only 5 players per team
        for i in range(1, 6):
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=1,
                player_id=200 + i,
                position=["P", "C", "1B", "2B", "3B"][i-1],
                batting_order=i
            )
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=2,
                player_id=100 + i,
                position=["P", "C", "1B", "2B", "3B"][i-1],
                batting_order=i
            )

        state = await state_manager.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )

        # Should raise ValidationError
        with pytest.raises(ValidationError) as exc_info:
            await game_engine.start_game(game_id)

        assert "lineup incomplete" in str(exc_info.value).lower()
        assert "5 players" in str(exc_info.value)  # Shows actual count

    async def test_start_game_fails_with_missing_positions(self):
        """Test starting game with missing defensive positions fails"""
        game_id = uuid4()
        db_ops = DatabaseOperations()

        await db_ops.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        # Create 9 players but missing SS
        positions = ["P", "C", "1B", "2B", "3B", "LF", "CF", "RF", "DH"]  # Missing SS, has DH
        for i in range(1, 10):
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=1,
                player_id=200 + i,
                position=positions[i-1],
                batting_order=i
            )
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=2,
                player_id=100 + i,
                position=positions[i-1],
                batting_order=i
            )

        state = await state_manager.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )

        # Should raise ValidationError
        with pytest.raises(ValidationError) as exc_info:
            await game_engine.start_game(game_id)

        assert "missing active player at ss" in str(exc_info.value).lower()


@pytest.mark.integration
@pytest.mark.asyncio
class TestSnapshotTracking:
    """Test snapshot tracking in GameState"""

    async def test_snapshot_fields_populated(self):
        """Test that snapshot fields are populated in GameState"""
        # Create game with lineups
        game_id = uuid4()
        db_ops = DatabaseOperations()

        await db_ops.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        positions = ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF"]
        for i in range(1, 10):
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=2,
                player_id=100 + i,
                position=positions[i-1],
                batting_order=i
            )
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=1,
                player_id=200 + i,
                position=positions[i-1],
                batting_order=i
            )

        state = await state_manager.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )
        await game_engine.start_game(game_id)

        # Check snapshot fields after game start
        state = await game_engine.get_game_state(game_id)
        assert state.current_batter is not None
        assert state.current_pitcher is not None
        assert state.current_catcher is not None
        assert state.current_on_base_code == 0  # Empty bases

    async def test_on_base_code_calculation(self):
        """Test on_base_code matches runners"""
        # Create game and play until we have runners
        game_id = uuid4()
        db_ops = DatabaseOperations()

        await db_ops.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        positions = ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF"]
        for i in range(1, 10):
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=2,
                player_id=100 + i,
                position=positions[i-1],
                batting_order=i
            )
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=1,
                player_id=200 + i,
                position=positions[i-1],
                batting_order=i
            )

        state = await state_manager.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )
        await game_engine.start_game(game_id)

        # Play until we get runners on base
        for _ in range(20):
            state = await game_engine.get_game_state(game_id)

            await game_engine.submit_defensive_decision(game_id, DefensiveDecision())
            await game_engine.submit_offensive_decision(game_id, OffensiveDecision())
            await game_engine.resolve_play(game_id)

            state = await game_engine.get_game_state(game_id)
            all_runners = state.get_all_runners()
            if all_runners:
                # Verify on_base_code matches runners
                expected_code = 0
                for base, runner in all_runners:
                    if base == 1:
                        expected_code |= 1
                    elif base == 2:
                        expected_code |= 2
                    elif base == 3:
                        expected_code |= 4

                assert state.current_on_base_code == expected_code
                break


@pytest.mark.integration
@pytest.mark.asyncio
class TestBattingOrderCycling:
    """Test batting order cycles independently per team"""

    async def test_independent_batting_order_indices(self):
        """Test that each team tracks batting order independently"""
        # Create game
        game_id = uuid4()
        db_ops = DatabaseOperations()

        await db_ops.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        positions = ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF"]
        for i in range(1, 10):
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=2,
                player_id=100 + i,
                position=positions[i-1],
                batting_order=i
            )
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=1,
                player_id=200 + i,
                position=positions[i-1],
                batting_order=i
            )

        state = await state_manager.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )
        state = await game_engine.start_game(game_id)

        # Check initial indices
        assert state.away_team_batter_idx == 1  # Advanced from 0 during start_game
        assert state.home_team_batter_idx == 0  # Not advanced yet (top of 1st)

        # After first play, away_team should advance to 2
        await game_engine.submit_defensive_decision(game_id, DefensiveDecision())
        await game_engine.submit_offensive_decision(game_id, OffensiveDecision())
        await game_engine.resolve_play(game_id)

        state = await game_engine.get_game_state(game_id)
        # After first play, away idx advances again (now at 2)
        # Home idx still at 0 (bottom hasn't started)
        assert state.away_team_batter_idx == 2
        assert state.home_team_batter_idx == 0

    async def test_batting_order_wraps_at_nine(self):
        """Test batting order wraps from 8 back to 0"""
        # Create game
        game_id = uuid4()
        db_ops = DatabaseOperations()

        await db_ops.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        positions = ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF"]
        for i in range(1, 10):
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=2,
                player_id=100 + i,
                position=positions[i-1],
                batting_order=i
            )
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=1,
                player_id=200 + i,
                position=positions[i-1],
                batting_order=i
            )

        state = await state_manager.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )

        # Manually set away_team_batter_idx to 8
        state.away_team_batter_idx = 8
        state_manager.update_state(game_id, state)

        await game_engine.start_game(game_id)

        # After start_game, idx should wrap from 8 to 0 (8+1 % 9 = 0)
        state = await game_engine.get_game_state(game_id)
        assert state.away_team_batter_idx == 0  # Wrapped around


@pytest.mark.integration
@pytest.mark.asyncio
class TestGameCompletion:
    """Test game completion conditions"""

    async def test_game_completes_after_9_innings(self):
        """Test game status changes to completed when game ends"""
        # This is a longer test - we'll fast-forward to end
        game_id = uuid4()
        db_ops = DatabaseOperations()

        await db_ops.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        positions = ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF"]
        for i in range(1, 10):
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=2,
                player_id=100 + i,
                position=positions[i-1],
                batting_order=i
            )
            await db_ops.add_sba_lineup_player(
                game_id=game_id,
                team_id=1,
                player_id=200 + i,
                position=positions[i-1],
                batting_order=i
            )

        state = await state_manager.create_game(
            game_id=game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )
        await game_engine.start_game(game_id)

        # Fast-forward: Manually set to bottom 9th with home team ahead
        state = await game_engine.get_game_state(game_id)
        state.inning = 9
        state.half = "bottom"
        state.home_score = 5
        state.away_score = 2
        state.outs = 2  # 2 outs
        state_manager.update_state(game_id, state)

        # Play one more out (should end game)
        await game_engine.submit_defensive_decision(game_id, DefensiveDecision())
        await game_engine.submit_offensive_decision(game_id, OffensiveDecision())
        await game_engine.resolve_play(game_id)

        # Game should be completed
        final_state = await game_engine.get_game_state(game_id)
        assert final_state.status == "completed"
