"""
End-to-end integration tests for state persistence and recovery.

Tests the complete flow: StateManager → DatabaseOperations → PostgreSQL → Recovery

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

import pytest
from uuid import uuid4

from app.core.state_manager import StateManager
from app.models.game_models import TeamLineupState, LineupPlayerState
from app.database.session import init_db


# Mark all tests in this module as integration tests
pytestmark = pytest.mark.integration


@pytest.fixture(scope="module")
async def setup_database():
    """Set up test database schema"""
    await init_db()
    yield


@pytest.fixture
def sample_game_id():
    """Generate unique game ID for each test"""
    return uuid4()


class TestStateManagerPersistence:
    """Tests for StateManager integration with database"""

    @pytest.mark.asyncio
    async def test_create_game_and_persist(self, setup_database, sample_game_id):
        """Test creating game in StateManager and persisting to DB"""
        state_manager = StateManager()

        # Create in-memory state
        state = await state_manager.create_game(
            game_id=sample_game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )

        assert state.game_id == sample_game_id

        # Persist to database
        await state_manager.db_ops.create_game(
            game_id=sample_game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        # Verify in database
        db_game = await state_manager.db_ops.get_game(sample_game_id)
        assert db_game is not None
        assert db_game.id == sample_game_id

    @pytest.mark.asyncio
    async def test_create_persist_and_recover(self, setup_database, sample_game_id):
        """Test complete flow: create → persist → remove → recover"""
        state_manager = StateManager()

        # Step 1: Create game in memory
        await state_manager.create_game(
            game_id=sample_game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )

        # Step 2: Persist to database
        await state_manager.db_ops.create_game(
            game_id=sample_game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        # Step 3: Update game state
        state = state_manager.get_state(sample_game_id)
        state.inning = 5
        state.half = "bottom"
        state.home_score = 3
        state.away_score = 2
        state_manager.update_state(sample_game_id, state)

        # Persist update
        await state_manager.db_ops.update_game_state(
            game_id=sample_game_id,
            inning=5,
            half="bottom",
            home_score=3,
            away_score=2
        )

        # Step 4: Remove from memory
        state_manager.remove_game(sample_game_id)
        assert state_manager.get_state(sample_game_id) is None

        # Step 5: Recover from database
        recovered = await state_manager.recover_game(sample_game_id)

        assert recovered is not None
        assert recovered.game_id == sample_game_id
        assert recovered.inning == 5
        assert recovered.half == "bottom"
        assert recovered.home_score == 3
        assert recovered.away_score == 2

    @pytest.mark.asyncio
    async def test_recover_nonexistent_game(self, setup_database):
        """Test recovering nonexistent game returns None"""
        state_manager = StateManager()
        fake_id = uuid4()

        recovered = await state_manager.recover_game(fake_id)

        assert recovered is None

    @pytest.mark.asyncio
    async def test_lineup_persistence(self, setup_database, sample_game_id):
        """Test lineup persistence and state"""
        state_manager = StateManager()

        # Create game
        await state_manager.create_game(
            game_id=sample_game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )

        # Persist game to DB
        await state_manager.db_ops.create_game(
            game_id=sample_game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        # Create lineup in StateManager
        lineup = TeamLineupState(
            team_id=1,
            players=[
                LineupPlayerState(lineup_id=1, card_id=101, position="CF", batting_order=1),
                LineupPlayerState(lineup_id=2, card_id=102, position="SS", batting_order=2),
                LineupPlayerState(lineup_id=3, card_id=103, position="1B", batting_order=3),
            ]
        )
        state_manager.set_lineup(sample_game_id, team_id=1, lineup=lineup)

        # Persist lineup entries to DB (SBA uses player_id)
        for player in lineup.players:
            await state_manager.db_ops.add_sba_lineup_player(
                game_id=sample_game_id,
                team_id=1,
                player_id=player.card_id,  # Note: card_id here is used as player_id for SBA
                position=player.position,
                batting_order=player.batting_order
            )

        # Retrieve from DB
        db_lineup = await state_manager.db_ops.get_active_lineup(sample_game_id, team_id=1)

        assert len(db_lineup) == 3
        assert db_lineup[0].player_id == 101
        assert db_lineup[1].player_id == 102
        assert db_lineup[2].player_id == 103


class TestCompleteGameFlow:
    """Tests simulating a complete game flow"""

    @pytest.mark.asyncio
    async def test_game_with_plays(self, setup_database, sample_game_id):
        """Test game with plays - complete flow"""
        state_manager = StateManager()

        # Create and persist game
        await state_manager.create_game(
            game_id=sample_game_id,
            league_id="sba",
            home_team_id=1,
            away_team_id=2
        )

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

        # Add some plays
        for i in range(3):
            await state_manager.db_ops.save_play({
                "game_id": sample_game_id,
                "play_number": i + 1,
                "inning": 1,
                "half": "top",
                "outs_before": i,
                "batting_order": i + 1,
                "result_description": f"Play {i+1}",
                "pa": 1,
                "ab": 1
            })

        # Update game state
        state = state_manager.get_state(sample_game_id)
        state.play_count = 3
        state_manager.update_state(sample_game_id, state)

        # Persist state update
        await state_manager.db_ops.update_game_state(
            game_id=sample_game_id,
            inning=1,
            half="bottom",
            home_score=0,
            away_score=0
        )

        # Remove from memory and recover
        state_manager.remove_game(sample_game_id)
        recovered = await state_manager.recover_game(sample_game_id)

        assert recovered is not None
        assert recovered.play_count == 3  # Should reflect the plays

    @pytest.mark.asyncio
    async def test_multiple_games_independence(self, setup_database):
        """Test that multiple games are independent"""
        state_manager = StateManager()

        game1 = uuid4()
        game2 = uuid4()

        # Create two games
        await state_manager.create_game(game1, "sba", 1, 2)
        await state_manager.create_game(game2, "pd", 3, 4)

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

        await state_manager.db_ops.create_game(
            game_id=game2,
            league_id="pd",
            home_team_id=3,
            away_team_id=4,
            game_mode="ranked",
            visibility="public"
        )

        # Update game1
        state1 = state_manager.get_state(game1)
        state1.home_score = 5
        state_manager.update_state(game1, state1)

        await state_manager.db_ops.update_game_state(
            game_id=game1,
            inning=1,
            half="top",
            home_score=5,
            away_score=0
        )

        # Remove both from memory
        state_manager.remove_game(game1)
        state_manager.remove_game(game2)

        # Recover both
        recovered1 = await state_manager.recover_game(game1)
        recovered2 = await state_manager.recover_game(game2)

        # Verify independence
        assert recovered1.home_score == 5
        assert recovered2.home_score == 0
        assert recovered1.league_id == "sba"
        assert recovered2.league_id == "pd"


class TestStateManagerStatistics:
    """Tests for StateManager statistics with persistence"""

    @pytest.mark.asyncio
    async def test_stats_after_eviction_and_recovery(self, setup_database, sample_game_id):
        """Test stats are accurate after eviction and recovery"""
        state_manager = StateManager()

        # Create game
        await state_manager.create_game(sample_game_id, "sba", 1, 2)

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

        # Check stats
        stats = state_manager.get_stats()
        assert stats["active_games"] == 1

        # Evict
        state_manager.remove_game(sample_game_id)
        stats = state_manager.get_stats()
        assert stats["active_games"] == 0

        # Recover
        await state_manager.recover_game(sample_game_id)
        stats = state_manager.get_stats()
        assert stats["active_games"] == 1
