"""
Integration test for X-Check WebSocket flow.

Tests the complete flow from dice roll to X-Check result broadcast.

Author: Claude
Date: 2025-11-03
Phase: 3E-Final
"""
import pytest
import json
from uuid import uuid4
from unittest.mock import Mock, patch, AsyncMock
import pendulum

from app.websocket.handlers import register_handlers
from app.websocket.connection_manager import ConnectionManager
from app.core.state_manager import state_manager
from app.core.game_engine import game_engine
from app.models.game_models import GameState, LineupPlayerState
from app.models.player_models import PositionRating  # Import for forward reference resolution
from app.config import PlayOutcome

# Rebuild GameState model to resolve forward references
GameState.model_rebuild()


@pytest.mark.integration
class TestXCheckWebSocket:
    """Test X-Check integration with WebSocket handlers"""

    @pytest.fixture
    def mock_sio(self):
        """Mock Socket.io server"""
        sio = Mock()
        sio.emit = AsyncMock()
        return sio

    @pytest.fixture
    def mock_manager(self):
        """Mock ConnectionManager"""
        manager = Mock(spec=ConnectionManager)
        manager.user_sessions = {}
        manager.game_rooms = {}
        manager.emit_to_user = AsyncMock()
        manager.broadcast_to_game = AsyncMock()
        return manager

    @pytest.fixture
    def game_state(self):
        """Create test game state"""
        game_id = uuid4()

        # Create batter
        batter = LineupPlayerState(
            lineup_id=10,
            card_id=123,
            position="RF",
            batting_order=3
        )

        # Create pitcher
        pitcher = LineupPlayerState(
            lineup_id=20,
            card_id=456,
            position="P",
            batting_order=9
        )

        # Create catcher
        catcher = LineupPlayerState(
            lineup_id=21,
            card_id=789,
            position="C",
            batting_order=2
        )

        state = GameState(
            game_id=game_id,
            league_id="pd",  # PD league has position ratings
            home_team_id=1,
            away_team_id=2,
            current_batter=batter,
            current_pitcher=pitcher,
            current_catcher=catcher
        )

        # Clear bases
        state.on_first = None
        state.on_second = None
        state.on_third = None

        # Register state in state manager
        state_manager._states[game_id] = state

        return state

    async def test_submit_manual_xcheck_outcome(self, mock_sio, mock_manager, game_state):
        """
        Test submitting manual X-Check outcome via WebSocket.

        Flow:
        1. Create game with position ratings loaded
        2. Roll dice (stores pending_manual_roll)
        3. Submit X-Check outcome
        4. Verify play_resolved event includes x_check_details
        """
        game_id = game_state.game_id

        # Register handlers
        register_handlers(mock_sio, mock_manager)

        # Get the submit_manual_outcome handler
        # It's registered as the 5th event (after connect, disconnect, join_game, leave_game, heartbeat, roll_dice)
        handler_calls = [call for call in mock_sio.event.call_args_list]
        submit_handler = None
        for call in handler_calls:
            if len(call[0]) > 0 and hasattr(call[0][0], '__name__'):
                if call[0][0].__name__ == 'submit_manual_outcome':
                    submit_handler = call[0][0]
                    break

        assert submit_handler is not None, "submit_manual_outcome handler not found"

        # Mock pending roll (simulate roll_dice was called)
        from app.core.roll_types import AbRoll
        ab_roll = AbRoll(
            roll_id="test-roll-123",
            roll_type="AB",
            league_id="pd",
            game_id=game_id,
            d6_one=4,
            d6_two_a=3,
            d6_two_b=4,
            chaos_d20=12,
            resolution_d20=8,
            timestamp=pendulum.now('UTC')
        )
        game_state.pending_manual_roll = ab_roll
        state_manager.update_state(game_id, game_state)

        # Mock session
        sid = "test-session-123"
        mock_manager.user_sessions[sid] = "test-user"

        # Submit X-Check outcome
        data = {
            "game_id": str(game_id),
            "outcome": "x_check",
            "hit_location": "SS"
        }

        # Mock game engine resolve_manual_play to return X-Check result
        with patch('app.websocket.handlers.game_engine.resolve_manual_play') as mock_resolve:
            from app.models.game_models import XCheckResult
            from app.core.play_resolver import PlayResult

            # Create mock X-Check result
            xcheck_result = XCheckResult(
                position="SS",
                d20_roll=12,
                d6_roll=10,
                defender_range=4,
                defender_error_rating=12,
                defender_id=25,
                base_result="G2",
                converted_result="G2",
                error_result="NO",
                final_outcome=PlayOutcome.GROUNDBALL_B,
                hit_type="g2_no_error"
            )

            play_result = PlayResult(
                outcome=PlayOutcome.GROUNDBALL_B,
                outs_recorded=1,
                runs_scored=0,
                batter_result=None,
                runners_advanced=[],
                description="X-Check SS: G2 → G2 + NO = groundball_b",
                ab_roll=ab_roll,
                hit_location="SS",
                is_hit=False,
                is_out=True,
                x_check_details=xcheck_result
            )

            mock_resolve.return_value = play_result

            # Call handler
            await submit_handler(sid, data)

            # Verify outcome_accepted was emitted to user
            assert mock_manager.emit_to_user.called
            emit_calls = mock_manager.emit_to_user.call_args_list
            accepted_call = None
            for call in emit_calls:
                if call[0][1] == "outcome_accepted":
                    accepted_call = call
                    break
            assert accepted_call is not None, "outcome_accepted not emitted"

            # Verify play_resolved was broadcast with X-Check details
            assert mock_manager.broadcast_to_game.called
            broadcast_calls = mock_manager.broadcast_to_game.call_args_list
            play_resolved_call = None
            for call in broadcast_calls:
                if call[0][1] == "play_resolved":
                    play_resolved_call = call
                    break

            assert play_resolved_call is not None, "play_resolved not broadcast"

            # Extract broadcast data
            broadcast_data = play_resolved_call[0][2]

            # Verify standard play data
            assert broadcast_data["outcome"] == "groundball_b"
            assert broadcast_data["hit_location"] == "SS"
            assert broadcast_data["outs_recorded"] == 1
            assert broadcast_data["runs_scored"] == 0
            assert broadcast_data["is_out"] is True

            # Verify X-Check details are included
            assert "x_check_details" in broadcast_data, "x_check_details missing from broadcast"
            xcheck_data = broadcast_data["x_check_details"]

            # Verify X-Check structure
            assert xcheck_data["position"] == "SS"
            assert xcheck_data["d20_roll"] == 12
            assert xcheck_data["d6_roll"] == 10
            assert xcheck_data["defender_range"] == 4
            assert xcheck_data["defender_error_rating"] == 12
            assert xcheck_data["defender_id"] == 25
            assert xcheck_data["base_result"] == "G2"
            assert xcheck_data["converted_result"] == "G2"
            assert xcheck_data["error_result"] == "NO"
            assert xcheck_data["final_outcome"] == "groundball_b"
            assert xcheck_data["hit_type"] == "g2_no_error"

            # Verify optional SPD test fields
            assert xcheck_data["spd_test_roll"] is None
            assert xcheck_data["spd_test_target"] is None
            assert xcheck_data["spd_test_passed"] is None

    async def test_non_xcheck_play_has_no_xcheck_details(self, mock_sio, mock_manager, game_state):
        """
        Test that non-X-Check plays don't include x_check_details.
        """
        game_id = game_state.game_id

        # Register handlers
        register_handlers(mock_sio, mock_manager)

        # Get the submit_manual_outcome handler
        handler_calls = [call for call in mock_sio.event.call_args_list]
        submit_handler = None
        for call in handler_calls:
            if len(call[0]) > 0 and hasattr(call[0][0], '__name__'):
                if call[0][0].__name__ == 'submit_manual_outcome':
                    submit_handler = call[0][0]
                    break

        assert submit_handler is not None

        # Mock pending roll
        from app.core.roll_types import AbRoll
        ab_roll = AbRoll(
            roll_id="test-roll-124",
            roll_type="AB",
            league_id="pd",
            game_id=game_id,
            d6_one=5,
            d6_two_a=6,
            d6_two_b=6,
            chaos_d20=20,
            resolution_d20=20,
            timestamp=pendulum.now('UTC')
        )
        game_state.pending_manual_roll = ab_roll
        state_manager.update_state(game_id, game_state)

        # Mock session
        sid = "test-session-124"
        mock_manager.user_sessions[sid] = "test-user"

        # Submit strikeout (not X-Check)
        data = {
            "game_id": str(game_id),
            "outcome": "strikeout",
            "hit_location": None
        }

        # Mock game engine resolve_manual_play to return strikeout result
        with patch('app.websocket.handlers.game_engine.resolve_manual_play') as mock_resolve:
            from app.core.play_resolver import PlayResult

            play_result = PlayResult(
                outcome=PlayOutcome.STRIKEOUT,
                outs_recorded=1,
                runs_scored=0,
                batter_result=None,
                runners_advanced=[],
                description="Strikeout looking",
                ab_roll=ab_roll,
                hit_location=None,
                is_hit=False,
                is_out=True,
                x_check_details=None  # No X-Check for strikeout
            )

            mock_resolve.return_value = play_result

            # Call handler
            await submit_handler(sid, data)

            # Verify play_resolved was broadcast
            assert mock_manager.broadcast_to_game.called
            broadcast_calls = mock_manager.broadcast_to_game.call_args_list
            play_resolved_call = None
            for call in broadcast_calls:
                if call[0][1] == "play_resolved":
                    play_resolved_call = call
                    break

            assert play_resolved_call is not None

            # Extract broadcast data
            broadcast_data = play_resolved_call[0][2]

            # Verify X-Check details are NOT included for non-X-Check plays
            assert "x_check_details" not in broadcast_data, \
                "x_check_details should not be present for non-X-Check plays"
