"""
Unit tests for terminal client shared commands.
"""
import pytest
from uuid import uuid4
from unittest.mock import AsyncMock, MagicMock, patch

from terminal_client.commands import GameCommands
from app.models.game_models import GameState


@pytest.fixture
def game_commands():
    """Create GameCommands instance with mocked dependencies."""
    commands = GameCommands()
    commands.db_ops = AsyncMock()
    return commands


@pytest.mark.asyncio
async def test_create_new_game_success(game_commands):
    """Test successful game creation."""
    game_id = uuid4()

    with patch('terminal_client.commands.state_manager') as mock_sm:
        with patch('terminal_client.commands.game_engine') as mock_ge:
            with patch('terminal_client.commands.uuid4', return_value=game_id):
                with patch('terminal_client.commands.Config') as mock_config:
                    # Setup mocks
                    from app.models.game_models import LineupPlayerState
                    mock_batter = LineupPlayerState(
                        lineup_id=1,
                        card_id=100,
                        position='CF',
                        batting_order=1
                    )
                    mock_state = GameState(
                        game_id=game_id,
                        league_id='sba',
                        home_team_id=1,
                        away_team_id=2,
                        current_batter=mock_batter,
                        inning=1,
                        half='top'
                    )
                    mock_sm.create_game = AsyncMock(return_value=mock_state)
                    mock_ge.start_game = AsyncMock(return_value=mock_state)

                    # Execute
                    gid, success = await game_commands.create_new_game()

                    # Verify
                    assert success is True
                    assert gid == game_id
                    mock_sm.create_game.assert_called_once()
                    mock_ge.start_game.assert_called_once()
                    mock_config.set_current_game.assert_called_once_with(game_id)


@pytest.mark.asyncio
async def test_create_new_game_with_pd_league(game_commands):
    """Test game creation with PD league."""
    game_id = uuid4()

    with patch('terminal_client.commands.state_manager') as mock_sm:
        with patch('terminal_client.commands.game_engine') as mock_ge:
            with patch('terminal_client.commands.uuid4', return_value=game_id):
                with patch('terminal_client.commands.Config') as mock_config:
                    # Setup mocks
                    from app.models.game_models import LineupPlayerState
                    mock_batter = LineupPlayerState(
                        lineup_id=1,
                        card_id=200,
                        position='SS',
                        batting_order=1
                    )
                    mock_state = GameState(
                        game_id=game_id,
                        league_id='pd',
                        home_team_id=3,
                        away_team_id=5,
                        current_batter=mock_batter,
                        inning=1,
                        half='top'
                    )
                    mock_sm.create_game = AsyncMock(return_value=mock_state)
                    mock_ge.start_game = AsyncMock(return_value=mock_state)

                    # Execute
                    gid, success = await game_commands.create_new_game(
                        league='pd',
                        home_team=3,
                        away_team=5
                    )

                    # Verify
                    assert success is True
                    assert gid == game_id
                    # Check that PD-specific lineup creation was called
                    assert game_commands.db_ops.add_pd_lineup_card.call_count == 18  # 9 per team


@pytest.mark.asyncio
async def test_create_new_game_failure(game_commands):
    """Test game creation failure."""
    with patch('terminal_client.commands.state_manager') as mock_sm:
        with patch('terminal_client.commands.uuid4', return_value=uuid4()):
            # Setup mocks to fail
            mock_sm.create_game = AsyncMock(side_effect=Exception("Database error"))

            # Execute
            gid, success = await game_commands.create_new_game()

            # Verify
            assert success is False


@pytest.mark.asyncio
async def test_submit_defensive_decision_success(game_commands):
    """Test successful defensive decision submission."""
    game_id = uuid4()

    with patch('terminal_client.commands.game_engine') as mock_ge:
        mock_state = MagicMock()
        mock_ge.submit_defensive_decision = AsyncMock(return_value=mock_state)

        success = await game_commands.submit_defensive_decision(
            game_id=game_id,
            alignment='shifted_left',
            hold_runners=[1, 2]
        )

        assert success is True
        mock_ge.submit_defensive_decision.assert_called_once()


@pytest.mark.asyncio
async def test_submit_defensive_decision_failure(game_commands):
    """Test defensive decision submission failure."""
    game_id = uuid4()

    with patch('terminal_client.commands.game_engine') as mock_ge:
        mock_ge.submit_defensive_decision = AsyncMock(side_effect=Exception("Invalid decision"))

        success = await game_commands.submit_defensive_decision(
            game_id=game_id,
            alignment='invalid_alignment'
        )

        assert success is False


@pytest.mark.asyncio
async def test_submit_offensive_decision_success(game_commands):
    """Test successful offensive decision submission."""
    game_id = uuid4()

    with patch('terminal_client.commands.game_engine') as mock_ge:
        mock_state = MagicMock()
        mock_ge.submit_offensive_decision = AsyncMock(return_value=mock_state)

        success = await game_commands.submit_offensive_decision(
            game_id=game_id,
            action='hit_and_run',
            steal_attempts=[2]
        )

        assert success is True
        mock_ge.submit_offensive_decision.assert_called_once()


@pytest.mark.asyncio
async def test_submit_offensive_decision_failure(game_commands):
    """Test offensive decision submission failure."""
    game_id = uuid4()

    with patch('terminal_client.commands.game_engine') as mock_ge:
        mock_ge.submit_offensive_decision = AsyncMock(side_effect=Exception("Invalid decision"))

        success = await game_commands.submit_offensive_decision(
            game_id=game_id,
            action='invalid_action'
        )

        assert success is False


@pytest.mark.asyncio
async def test_resolve_play_success(game_commands):
    """Test successful play resolution."""
    game_id = uuid4()

    with patch('terminal_client.commands.game_engine') as mock_ge:
        with patch('terminal_client.commands.state_manager') as mock_sm:
            with patch('terminal_client.commands.display'):
                with patch('random.choice') as mock_random_choice:
                    # Setup mock result with proper attributes
                    mock_result = MagicMock()
                    mock_result.description = "Single to center field"
                    mock_result.outs_recorded = 0
                    mock_result.runs_scored = 1

                    # Setup mock state
                    mock_state = MagicMock()
                    mock_state.away_score = 1
                    mock_state.home_score = 0

                    # Mock state_manager to return a valid state (not None)
                    mock_sm.get_state = MagicMock(return_value=mock_state)

                    # Mock random.choice to return SINGLE_1
                    from app.config import PlayOutcome
                    mock_random_choice.return_value = PlayOutcome.SINGLE_1

                    mock_ge.resolve_play = AsyncMock(return_value=mock_result)
                    mock_ge.get_game_state = AsyncMock(return_value=mock_state)

                    success = await game_commands.resolve_play(game_id)

                    assert success is True
                    # Should be called with auto-generated outcome (SINGLE_1 from mocked random)
                    mock_ge.resolve_play.assert_called_once_with(game_id, PlayOutcome.SINGLE_1, None, None, None)
                    mock_ge.get_game_state.assert_called_once_with(game_id)


@pytest.mark.asyncio
async def test_resolve_play_game_not_found(game_commands):
    """Test play resolution when game is not found."""
    game_id = uuid4()

    with patch('terminal_client.commands.game_engine') as mock_ge:
        mock_result = MagicMock()
        mock_ge.resolve_play = AsyncMock(return_value=mock_result)
        mock_ge.get_game_state = AsyncMock(return_value=None)  # Game not found

        success = await game_commands.resolve_play(game_id)

        assert success is False


@pytest.mark.asyncio
async def test_resolve_play_failure(game_commands):
    """Test play resolution failure."""
    game_id = uuid4()

    with patch('terminal_client.commands.game_engine') as mock_ge:
        mock_ge.resolve_play = AsyncMock(side_effect=Exception("Resolution error"))

        success = await game_commands.resolve_play(game_id)

        assert success is False


@pytest.mark.asyncio
async def test_quick_play_rounds_success(game_commands):
    """Test successful quick play execution."""
    game_id = uuid4()

    with patch('terminal_client.commands.game_engine') as mock_ge:
        with patch('terminal_client.commands.asyncio.sleep', new_callable=AsyncMock):
            with patch('terminal_client.commands.display'):
                # Setup mocks
                mock_state = MagicMock()
                mock_state.status = "active"
                mock_state.away_score = 0
                mock_state.home_score = 0
                mock_state.inning = 1
                mock_state.half = "top"
                mock_state.outs = 0

                mock_result = MagicMock()
                mock_result.description = "Groundout"

                mock_ge.get_game_state = AsyncMock(return_value=mock_state)
                mock_ge.submit_defensive_decision = AsyncMock()
                mock_ge.submit_offensive_decision = AsyncMock()
                mock_ge.resolve_play = AsyncMock(return_value=mock_result)

                # Execute 3 plays
                plays_completed = await game_commands.quick_play_rounds(game_id, count=3)

                # Verify
                assert plays_completed == 3
                assert mock_ge.resolve_play.call_count == 3


@pytest.mark.asyncio
async def test_quick_play_rounds_game_ends(game_commands):
    """Test quick play when game ends mid-execution."""
    game_id = uuid4()

    with patch('terminal_client.commands.game_engine') as mock_ge:
        with patch('terminal_client.commands.asyncio.sleep', new_callable=AsyncMock):
            with patch('terminal_client.commands.display'):
                # Setup mocks - game becomes completed after 2 plays
                mock_state_active_1 = MagicMock()
                mock_state_active_1.status = "active"
                mock_state_active_1.away_score = 0
                mock_state_active_1.home_score = 0
                mock_state_active_1.inning = 1
                mock_state_active_1.half = "top"
                mock_state_active_1.outs = 0

                mock_state_active_2 = MagicMock()
                mock_state_active_2.status = "active"
                mock_state_active_2.away_score = 1
                mock_state_active_2.home_score = 0
                mock_state_active_2.inning = 1
                mock_state_active_2.half = "top"
                mock_state_active_2.outs = 0

                mock_state_completed = MagicMock()
                mock_state_completed.status = "completed"

                mock_result = MagicMock()
                mock_result.description = "Game winning hit"

                # Setup get_game_state to return:
                # 1. active (before play 1)
                # 2. active (after play 1)
                # 3. active (before play 2)
                # 4. active (after play 2)
                # 5. completed (before play 3 - should stop)
                # 6. completed (final state query)
                mock_ge.get_game_state = AsyncMock(
                    side_effect=[
                        mock_state_active_1,  # Before play 1
                        mock_state_active_2,  # After play 1
                        mock_state_active_2,  # Before play 2
                        mock_state_active_2,  # After play 2
                        mock_state_completed,  # Before play 3 - stops here
                        mock_state_completed   # Final state query
                    ]
                )
                mock_ge.submit_defensive_decision = AsyncMock()
                mock_ge.submit_offensive_decision = AsyncMock()
                mock_ge.resolve_play = AsyncMock(return_value=mock_result)

                # Execute 5 plays but should stop at 2
                plays_completed = await game_commands.quick_play_rounds(game_id, count=5)

                # Verify - should only complete 2 plays
                assert plays_completed == 2
                assert mock_ge.resolve_play.call_count == 2


@pytest.mark.asyncio
async def test_show_game_status_success(game_commands):
    """Test showing game status successfully."""
    game_id = uuid4()

    with patch('terminal_client.commands.game_engine') as mock_ge:
        mock_state = MagicMock()
        mock_ge.get_game_state = AsyncMock(return_value=mock_state)

        success = await game_commands.show_game_status(game_id)

        assert success is True
        mock_ge.get_game_state.assert_called_once_with(game_id)


@pytest.mark.asyncio
async def test_show_game_status_not_found(game_commands):
    """Test showing game status when game not found."""
    game_id = uuid4()

    with patch('terminal_client.commands.game_engine') as mock_ge:
        mock_ge.get_game_state = AsyncMock(return_value=None)

        success = await game_commands.show_game_status(game_id)

        assert success is False


@pytest.mark.asyncio
async def test_show_box_score_success(game_commands):
    """Test showing box score successfully."""
    game_id = uuid4()

    mock_box_score = {
        'game_stats': {'home_runs': 5, 'away_runs': 3},
        'batting_stats': [],
        'pitching_stats': []
    }

    # Mock the box_score_service singleton instance method
    with patch.object(type(__import__('app.services', fromlist=['box_score_service']).box_score_service), 'get_box_score', new_callable=AsyncMock) as mock_get_box_score:
        with patch('terminal_client.commands.display'):
            mock_get_box_score.return_value = mock_box_score

            success = await game_commands.show_box_score(game_id)

            assert success is True
            mock_get_box_score.assert_called_once_with(game_id)


@pytest.mark.asyncio
async def test_show_box_score_not_found(game_commands):
    """Test showing box score when game not found."""
    game_id = uuid4()

    # Mock the box_score_service singleton instance method
    with patch.object(type(__import__('app.services', fromlist=['box_score_service']).box_score_service), 'get_box_score', new_callable=AsyncMock) as mock_get_box_score:
        mock_get_box_score.return_value = None

        success = await game_commands.show_box_score(game_id)

        assert success is False
