"""
Integration tests for DatabaseOperations.

Tests actual database operations using the test database.
These tests are slower than unit tests but verify real DB interactions.

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

import pytest
from uuid import uuid4

from app.database.operations import DatabaseOperations
from app.database.session import init_db, engine


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


@pytest.fixture(scope="function")
async def setup_database():
    """
    Set up test database schema.

    Runs once per test function (noop if tables exist).
    """
    # Create all tables (will skip if they exist)
    await init_db()
    yield
    # Teardown if needed (tables persist between test runs)


@pytest.fixture
async def db_ops():
    """Create DatabaseOperations instance for each test"""
    return DatabaseOperations()


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


class TestDatabaseOperationsGame:
    """Tests for game CRUD operations"""

    @pytest.mark.asyncio
    async def test_create_game(self, setup_database, db_ops, sample_game_id):
        """Test creating a game in database"""
        game = await 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"
        )

        assert game.id == sample_game_id
        assert game.league_id == "sba"
        assert game.status == "pending"
        assert game.home_team_id == 1
        assert game.away_team_id == 2

    @pytest.mark.asyncio
    async def test_create_game_with_ai(self, setup_database, db_ops, sample_game_id):
        """Test creating a game with AI opponent"""
        game = await db_ops.create_game(
            game_id=sample_game_id,
            league_id="pd",
            home_team_id=10,
            away_team_id=20,
            game_mode="practice",
            visibility="private",
            home_team_is_ai=False,
            away_team_is_ai=True,
            ai_difficulty="balanced"
        )

        assert game.away_team_is_ai is True
        assert game.home_team_is_ai is False
        assert game.ai_difficulty == "balanced"

    @pytest.mark.asyncio
    async def test_get_game(self, setup_database, db_ops, sample_game_id):
        """Test retrieving a game from database"""
        # Create game
        await 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"
        )

        # Retrieve game
        retrieved = await db_ops.get_game(sample_game_id)

        assert retrieved is not None
        assert retrieved.id == sample_game_id
        assert retrieved.league_id == "sba"

    @pytest.mark.asyncio
    async def test_get_game_nonexistent(self, setup_database, db_ops):
        """Test retrieving nonexistent game returns None"""
        fake_id = uuid4()
        game = await db_ops.get_game(fake_id)

        assert game is None

    @pytest.mark.asyncio
    async def test_update_game_state(self, setup_database, db_ops, sample_game_id):
        """Test updating game state"""
        # Create game
        await 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"
        )

        # Update state
        await db_ops.update_game_state(
            game_id=sample_game_id,
            inning=5,
            half="bottom",
            home_score=3,
            away_score=2,
            status="active"
        )

        # Verify update
        game = await db_ops.get_game(sample_game_id)
        assert game.current_inning == 5
        assert game.current_half == "bottom"
        assert game.home_score == 3
        assert game.away_score == 2
        assert game.status == "active"

    @pytest.mark.asyncio
    async def test_update_game_state_nonexistent_raises_error(self, setup_database, db_ops):
        """Test updating nonexistent game raises error"""
        fake_id = uuid4()

        with pytest.raises(ValueError, match="not found"):
            await db_ops.update_game_state(
                game_id=fake_id,
                inning=1,
                half="top",
                home_score=0,
                away_score=0
            )


class TestDatabaseOperationsLineup:
    """Tests for lineup operations"""

    @pytest.mark.asyncio
    async def test_add_sba_lineup_player(self, setup_database, db_ops, sample_game_id):
        """Test adding SBA player to lineup"""
        # Create game first
        await 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 SBA player to lineup
        lineup = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=1,
            player_id=101,
            position="CF",
            batting_order=1,
            is_starter=True
        )

        assert lineup.game_id == sample_game_id
        assert lineup.team_id == 1
        assert lineup.player_id == 101
        assert lineup.card_id is None
        assert lineup.position == "CF"
        assert lineup.batting_order == 1
        assert lineup.is_active is True

    @pytest.mark.asyncio
    async def test_add_pd_lineup_card(self, setup_database, db_ops, sample_game_id):
        """Test adding PD card to lineup"""
        await db_ops.create_game(
            game_id=sample_game_id,
            league_id="pd",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        lineup = await db_ops.add_pd_lineup_card(
            game_id=sample_game_id,
            team_id=1,
            card_id=200,
            position="P",
            batting_order=None,  # Pitcher, no batting order (AL rules)
            is_starter=True
        )

        assert lineup.position == "P"
        assert lineup.batting_order is None
        assert lineup.card_id == 200
        assert lineup.player_id is None

    @pytest.mark.asyncio
    async def test_get_active_lineup(self, setup_database, db_ops, sample_game_id):
        """Test retrieving active lineup for a team"""
        await 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 multiple SBA players to lineup
        await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=1,
            player_id=103,
            position="1B",
            batting_order=3
        )
        await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=1,
            player_id=101,
            position="CF",
            batting_order=1
        )
        await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=1,
            player_id=102,
            position="SS",
            batting_order=2
        )

        # Retrieve lineup
        lineup = await db_ops.get_active_lineup(sample_game_id, team_id=1)

        assert len(lineup) == 3
        # Should be sorted by batting order
        assert lineup[0].batting_order == 1
        assert lineup[1].batting_order == 2
        assert lineup[2].batting_order == 3

    @pytest.mark.asyncio
    async def test_get_active_lineup_empty(self, setup_database, db_ops, sample_game_id):
        """Test retrieving lineup for team with no entries"""
        await 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"
        )

        lineup = await db_ops.get_active_lineup(sample_game_id, team_id=1)

        assert lineup == []


class TestDatabaseOperationsPlays:
    """Tests for play operations"""

    @pytest.mark.asyncio
    async def test_save_play(self, setup_database, db_ops, sample_game_id):
        """Test saving a play"""
        await 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"
        )

        play_data = {
            "game_id": sample_game_id,
            "play_number": 1,
            "inning": 1,
            "half": "top",
            "outs_before": 0,
            "batting_order": 1,
            "result_description": "Single to left field",
            "pa": 1,
            "ab": 1,
            "hit": 1
        }

        play = await db_ops.save_play(play_data)

        assert play.game_id == sample_game_id
        assert play.play_number == 1
        assert play.result_description == "Single to left field"

    @pytest.mark.asyncio
    async def test_get_plays(self, setup_database, db_ops, sample_game_id):
        """Test retrieving plays for a game"""
        await 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"
        )

        # Save multiple plays
        for i in range(3):
            await 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
            })

        # Retrieve plays
        plays = await db_ops.get_plays(sample_game_id)

        assert len(plays) == 3
        # Should be ordered by play_number
        assert plays[0].play_number == 1
        assert plays[1].play_number == 2
        assert plays[2].play_number == 3

    @pytest.mark.asyncio
    async def test_get_plays_empty(self, setup_database, db_ops, sample_game_id):
        """Test retrieving plays for game with no plays"""
        await 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"
        )

        plays = await db_ops.get_plays(sample_game_id)

        assert plays == []


class TestDatabaseOperationsRecovery:
    """Tests for game state recovery"""

    @pytest.mark.asyncio
    async def test_load_game_state_complete(self, setup_database, db_ops, sample_game_id):
        """Test loading complete game state"""
        # Create game
        await 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 lineups
        await db_ops.create_lineup_entry(
            game_id=sample_game_id,
            team_id=1,
            card_id=101,
            position="CF",
            batting_order=1
        )

        # Add play
        await db_ops.save_play({
            "game_id": sample_game_id,
            "play_number": 1,
            "inning": 1,
            "half": "top",
            "outs_before": 0,
            "batting_order": 1,
            "result_description": "Single",
            "pa": 1
        })

        # Update game state
        await db_ops.update_game_state(
            game_id=sample_game_id,
            inning=2,
            half="bottom",
            home_score=1,
            away_score=0
        )

        # Load complete state
        state = await db_ops.load_game_state(sample_game_id)

        assert state is not None
        assert state["game"]["id"] == sample_game_id
        assert state["game"]["current_inning"] == 2
        assert state["game"]["current_half"] == "bottom"
        assert len(state["lineups"]) == 1
        assert len(state["plays"]) == 1

    @pytest.mark.asyncio
    async def test_load_game_state_nonexistent(self, setup_database, db_ops):
        """Test loading nonexistent game returns None"""
        fake_id = uuid4()
        state = await db_ops.load_game_state(fake_id)

        assert state is None


class TestDatabaseOperationsGameSession:
    """Tests for game session operations"""

    @pytest.mark.asyncio
    async def test_create_game_session(self, setup_database, db_ops, sample_game_id):
        """Test creating a game session"""
        await 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"
        )

        session = await db_ops.create_game_session(sample_game_id)

        assert session.game_id == sample_game_id
        assert session.state_snapshot is None  # Initially null

    @pytest.mark.asyncio
    async def test_update_session_snapshot(self, setup_database, db_ops, sample_game_id):
        """Test updating session snapshot"""
        await 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"
        )
        await db_ops.create_game_session(sample_game_id)

        snapshot = {
            "inning": 3,
            "outs": 2,
            "runners": [1, 3]
        }

        await db_ops.update_session_snapshot(sample_game_id, snapshot)

        # Note: Would need to query session to verify, but this tests no errors

    @pytest.mark.asyncio
    async def test_update_session_snapshot_nonexistent_raises_error(self, setup_database, db_ops):
        """Test updating nonexistent session raises error"""
        fake_id = uuid4()

        with pytest.raises(ValueError, match="not found"):
            await db_ops.update_session_snapshot(fake_id, {})


class TestDatabaseOperationsRoster:
    """Tests for roster link operations"""

    @pytest.mark.asyncio
    async def test_add_pd_roster_card(self, setup_database, db_ops, sample_game_id):
        """Test adding a PD card to roster"""
        # Create game first
        await db_ops.create_game(
            game_id=sample_game_id,
            league_id="pd",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        # Add roster card
        roster_data = await db_ops.add_pd_roster_card(
            game_id=sample_game_id,
            card_id=123,
            team_id=1
        )

        assert roster_data.id is not None
        assert roster_data.game_id == sample_game_id
        assert roster_data.card_id == 123
        assert roster_data.team_id == 1

    @pytest.mark.asyncio
    async def test_add_sba_roster_player(self, setup_database, db_ops, sample_game_id):
        """Test adding an SBA player to roster"""
        # Create game first
        await db_ops.create_game(
            game_id=sample_game_id,
            league_id="sba",
            home_team_id=10,
            away_team_id=20,
            game_mode="friendly",
            visibility="public"
        )

        # Add roster player
        roster_data = await db_ops.add_sba_roster_player(
            game_id=sample_game_id,
            player_id=456,
            team_id=10
        )

        assert roster_data.id is not None
        assert roster_data.game_id == sample_game_id
        assert roster_data.player_id == 456
        assert roster_data.team_id == 10

    @pytest.mark.asyncio
    async def test_add_duplicate_pd_card_raises_error(self, setup_database, db_ops, sample_game_id):
        """Test adding duplicate PD card to roster fails"""
        # Create game and add card
        await db_ops.create_game(
            game_id=sample_game_id,
            league_id="pd",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )
        await db_ops.add_pd_roster_card(
            game_id=sample_game_id,
            card_id=123,
            team_id=1
        )

        # Try to add same card again - should fail
        with pytest.raises(ValueError, match="Could not add card to roster"):
            await db_ops.add_pd_roster_card(
                game_id=sample_game_id,
                card_id=123,
                team_id=1
            )

    @pytest.mark.asyncio
    async def test_add_duplicate_sba_player_raises_error(self, setup_database, db_ops, sample_game_id):
        """Test adding duplicate SBA player to roster fails"""
        # Create game and add player
        await db_ops.create_game(
            game_id=sample_game_id,
            league_id="sba",
            home_team_id=10,
            away_team_id=20,
            game_mode="friendly",
            visibility="public"
        )
        await db_ops.add_sba_roster_player(
            game_id=sample_game_id,
            player_id=456,
            team_id=10
        )

        # Try to add same player again - should fail
        with pytest.raises(ValueError, match="Could not add player to roster"):
            await db_ops.add_sba_roster_player(
                game_id=sample_game_id,
                player_id=456,
                team_id=10
            )

    @pytest.mark.asyncio
    async def test_get_pd_roster_all_teams(self, setup_database, db_ops, sample_game_id):
        """Test getting all PD cards for a game"""
        # Create game and add cards for both teams
        await db_ops.create_game(
            game_id=sample_game_id,
            league_id="pd",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        await db_ops.add_pd_roster_card(sample_game_id, 101, 1)
        await db_ops.add_pd_roster_card(sample_game_id, 102, 1)
        await db_ops.add_pd_roster_card(sample_game_id, 201, 2)

        # Get all roster entries
        roster = await db_ops.get_pd_roster(sample_game_id)

        assert len(roster) == 3
        card_ids = {r.card_id for r in roster}
        assert card_ids == {101, 102, 201}

    @pytest.mark.asyncio
    async def test_get_pd_roster_filtered_by_team(self, setup_database, db_ops, sample_game_id):
        """Test getting PD cards filtered by team"""
        # Create game and add cards for both teams
        await db_ops.create_game(
            game_id=sample_game_id,
            league_id="pd",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        await db_ops.add_pd_roster_card(sample_game_id, 101, 1)
        await db_ops.add_pd_roster_card(sample_game_id, 102, 1)
        await db_ops.add_pd_roster_card(sample_game_id, 201, 2)

        # Get team 1 roster
        team1_roster = await db_ops.get_pd_roster(sample_game_id, team_id=1)

        assert len(team1_roster) == 2
        card_ids = {r.card_id for r in team1_roster}
        assert card_ids == {101, 102}

    @pytest.mark.asyncio
    async def test_get_sba_roster_all_teams(self, setup_database, db_ops, sample_game_id):
        """Test getting all SBA players for a game"""
        # Create game and add players for both teams
        await db_ops.create_game(
            game_id=sample_game_id,
            league_id="sba",
            home_team_id=10,
            away_team_id=20,
            game_mode="friendly",
            visibility="public"
        )

        await db_ops.add_sba_roster_player(sample_game_id, 401, 10)
        await db_ops.add_sba_roster_player(sample_game_id, 402, 10)
        await db_ops.add_sba_roster_player(sample_game_id, 501, 20)

        # Get all roster entries
        roster = await db_ops.get_sba_roster(sample_game_id)

        assert len(roster) == 3
        player_ids = {r.player_id for r in roster}
        assert player_ids == {401, 402, 501}

    @pytest.mark.asyncio
    async def test_get_sba_roster_filtered_by_team(self, setup_database, db_ops, sample_game_id):
        """Test getting SBA players filtered by team"""
        # Create game and add players for both teams
        await db_ops.create_game(
            game_id=sample_game_id,
            league_id="sba",
            home_team_id=10,
            away_team_id=20,
            game_mode="friendly",
            visibility="public"
        )

        await db_ops.add_sba_roster_player(sample_game_id, 401, 10)
        await db_ops.add_sba_roster_player(sample_game_id, 402, 10)
        await db_ops.add_sba_roster_player(sample_game_id, 501, 20)

        # Get team 10 roster
        team10_roster = await db_ops.get_sba_roster(sample_game_id, team_id=10)

        assert len(team10_roster) == 2
        player_ids = {r.player_id for r in team10_roster}
        assert player_ids == {401, 402}

    @pytest.mark.asyncio
    async def test_remove_roster_entry(self, setup_database, db_ops, sample_game_id):
        """Test removing a roster entry"""
        # Create game and add card
        await db_ops.create_game(
            game_id=sample_game_id,
            league_id="pd",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )
        roster_data = await db_ops.add_pd_roster_card(
            game_id=sample_game_id,
            card_id=123,
            team_id=1
        )

        # Remove it
        await db_ops.remove_roster_entry(roster_data.id)

        # Verify it's gone
        roster = await db_ops.get_pd_roster(sample_game_id)
        assert len(roster) == 0

    @pytest.mark.asyncio
    async def test_remove_nonexistent_roster_entry_raises_error(self, setup_database, db_ops):
        """Test removing nonexistent roster entry fails"""
        fake_id = 999999

        with pytest.raises(ValueError, match="not found"):
            await db_ops.remove_roster_entry(fake_id)

    @pytest.mark.asyncio
    async def test_get_empty_pd_roster(self, setup_database, db_ops, sample_game_id):
        """Test getting PD roster for game with no cards"""
        # Create game but don't add any cards
        await db_ops.create_game(
            game_id=sample_game_id,
            league_id="pd",
            home_team_id=1,
            away_team_id=2,
            game_mode="friendly",
            visibility="public"
        )

        roster = await db_ops.get_pd_roster(sample_game_id)
        assert len(roster) == 0

    @pytest.mark.asyncio
    async def test_get_empty_sba_roster(self, setup_database, db_ops, sample_game_id):
        """Test getting SBA roster for game with no players"""
        # Create game but don't add any players
        await db_ops.create_game(
            game_id=sample_game_id,
            league_id="sba",
            home_team_id=10,
            away_team_id=20,
            game_mode="friendly",
            visibility="public"
        )

        roster = await db_ops.get_sba_roster(sample_game_id)
        assert len(roster) == 0


class TestDatabaseOperationsRollback:
    """Tests for database rollback operations (delete_plays_after, etc.)"""

    @pytest.mark.asyncio
    async def test_delete_plays_after(self, setup_database, db_ops, sample_game_id):
        """Test deleting plays after a specific play number"""
        # Create game
        await 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 entries for batter, pitcher, and catcher
        batter = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=1,
            player_id=100,
            position="CF",
            batting_order=1,
            is_starter=True
        )
        pitcher = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=2,
            player_id=200,
            position="P",
            batting_order=None,
            is_starter=True
        )
        catcher = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=2,
            player_id=201,
            position="C",
            batting_order=1,
            is_starter=True
        )

        # Create 5 plays
        for play_num in range(1, 6):
            await db_ops.save_play({
                'game_id': sample_game_id,
                'play_number': play_num,
                'inning': 1,
                'half': 'top',
                'outs_before': 0,
                'batter_id': batter.id,
                'pitcher_id': pitcher.id,
                'catcher_id': catcher.id,
                'dice_roll': f'10+{play_num}',
                'result_description': f'Play {play_num}',
                'pa': 1,
                'complete': True
            })

        # Delete plays after play 3
        deleted_count = await db_ops.delete_plays_after(sample_game_id, 3)

        assert deleted_count == 2  # Plays 4 and 5 deleted

        # Verify only plays 1-3 remain
        remaining_plays = await db_ops.get_plays(sample_game_id)
        assert len(remaining_plays) == 3
        assert all(p.play_number <= 3 for p in remaining_plays)

    @pytest.mark.asyncio
    async def test_delete_plays_after_with_no_plays_to_delete(self, setup_database, db_ops, sample_game_id):
        """Test deleting plays when none exist after the threshold"""
        # Create game
        await 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 for play
        batter = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=1,
            player_id=100,
            position="CF",
            batting_order=1,
            is_starter=True
        )
        pitcher = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=2,
            player_id=200,
            position="P",
            batting_order=None,
            is_starter=True
        )
        catcher = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=2,
            player_id=201,
            position="C",
            batting_order=1,
            is_starter=True
        )

        # Create 3 plays
        for play_num in range(1, 4):
            await db_ops.save_play({
                'game_id': sample_game_id,
                'play_number': play_num,
                'inning': 1,
                'half': 'top',
                'outs_before': 0,
                'batter_id': batter.id,
                'pitcher_id': pitcher.id,
                'catcher_id': catcher.id,
                'dice_roll': f'10+{play_num}',
                'result_description': f'Play {play_num}',
                'pa': 1,
                'complete': True
            })

        # Delete plays after play 10 (none exist)
        deleted_count = await db_ops.delete_plays_after(sample_game_id, 10)

        assert deleted_count == 0

        # Verify all 3 plays remain
        remaining_plays = await db_ops.get_plays(sample_game_id)
        assert len(remaining_plays) == 3

    @pytest.mark.asyncio
    async def test_delete_substitutions_after(self, setup_database, db_ops, sample_game_id):
        """Test deleting substitutions after a specific play number"""
        # Create game
        await 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 starter
        starter = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=1,
            player_id=100,
            position="CF",
            batting_order=1,
            is_starter=True
        )

        # Create substitutions - need to manually set substitution fields
        sub1 = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=1,
            player_id=101,
            position="CF",
            batting_order=1,
            is_starter=False
        )
        sub2 = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=1,
            player_id=102,
            position="CF",
            batting_order=1,
            is_starter=False
        )
        sub3 = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=1,
            player_id=103,
            position="CF",
            batting_order=1,
            is_starter=False
        )

        # Manually set substitution fields using SQLAlchemy
        from app.database.session import AsyncSessionLocal
        from app.models.db_models import Lineup
        from sqlalchemy import select, update

        async with AsyncSessionLocal() as session:
            # Update starter - mark as inactive
            await session.execute(
                update(Lineup)
                .where(Lineup.id == starter.id)
                .values(is_active=False, after_play=None)
            )

            # Update sub1 - substituted at play 5
            await session.execute(
                update(Lineup)
                .where(Lineup.id == sub1.id)
                .values(is_active=False, entered_inning=3, after_play=5, replacing_id=starter.id)
            )

            # Update sub2 - substituted at play 10
            await session.execute(
                update(Lineup)
                .where(Lineup.id == sub2.id)
                .values(is_active=False, entered_inning=5, after_play=10, replacing_id=sub1.id)
            )

            # Update sub3 - substituted at play 15
            await session.execute(
                update(Lineup)
                .where(Lineup.id == sub3.id)
                .values(is_active=True, entered_inning=7, after_play=15, replacing_id=sub2.id)
            )

            await session.commit()

        # Delete substitutions after play 10 (>= 10, so deletes sub2 and sub3)
        deleted_count = await db_ops.delete_substitutions_after(sample_game_id, 10)

        assert deleted_count == 2  # sub2 (after play 10) and sub3 (after play 15) deleted

        # Verify lineup state - need to get ALL lineup entries, not just active
        from app.database.session import AsyncSessionLocal
        from app.models.db_models import Lineup
        from sqlalchemy import select

        async with AsyncSessionLocal() as session:
            result = await session.execute(
                select(Lineup)
                .where(
                    Lineup.game_id == sample_game_id,
                    Lineup.team_id == 1
                )
            )
            all_lineup = list(result.scalars().all())

        # Should have starter + 1 sub (sub1 only)
        assert len([p for p in all_lineup if p.after_play is not None]) == 1
        # The remaining sub should be sub1 (after_play=5)
        remaining_sub = [p for p in all_lineup if p.after_play is not None][0]
        assert remaining_sub.after_play == 5

    @pytest.mark.asyncio
    async def test_complete_rollback_scenario(self, setup_database, db_ops, sample_game_id):
        """Test complete rollback scenario: plays + substitutions"""
        # Create game
        await 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
        batter = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=1,
            player_id=100,
            position="CF",
            batting_order=1,
            is_starter=True
        )
        pitcher = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=2,
            player_id=200,
            position="P",
            batting_order=None,
            is_starter=True
        )
        catcher = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=2,
            player_id=201,
            position="C",
            batting_order=1,
            is_starter=True
        )

        # Create 10 plays
        for play_num in range(1, 11):
            await db_ops.save_play({
                'game_id': sample_game_id,
                'play_number': play_num,
                'inning': (play_num - 1) // 3 + 1,
                'half': 'top' if play_num % 2 == 1 else 'bot',
                'outs_before': 0,
                'batter_id': batter.id,
                'pitcher_id': pitcher.id,
                'catcher_id': catcher.id,
                'dice_roll': f'10+{play_num}',
                'result_description': f'Play {play_num}',
                'pa': 1,
                'complete': True
            })

        # Create substitution at play 7
        sub = await db_ops.add_sba_lineup_player(
            game_id=sample_game_id,
            team_id=1,
            player_id=101,
            position="CF",
            batting_order=1,
            is_starter=False
        )

        # Manually set substitution fields
        from app.database.session import AsyncSessionLocal
        from app.models.db_models import Lineup
        from sqlalchemy import update

        async with AsyncSessionLocal() as session:
            await session.execute(
                update(Lineup)
                .where(Lineup.id == sub.id)
                .values(is_active=True, entered_inning=3, after_play=7, replacing_id=batter.id)
            )
            await session.commit()

        # Rollback to play 5 (delete everything after play 5)
        rollback_point = 5

        plays_deleted = await db_ops.delete_plays_after(sample_game_id, rollback_point)
        subs_deleted = await db_ops.delete_substitutions_after(sample_game_id, rollback_point)

        # Verify deletions
        assert plays_deleted == 5  # Plays 6-10 deleted
        assert subs_deleted == 1   # Substitution at play 7 deleted

        # Verify remaining data
        remaining_plays = await db_ops.get_plays(sample_game_id)
        assert len(remaining_plays) == 5
        assert max(p.play_number for p in remaining_plays) == 5
