"""
Pytest configuration for integration tests.

Provides shared fixtures for database testing with proper async session management.
Uses NullPool to avoid asyncpg connection reuse issues in tests.

Key Pattern: Session injection into DatabaseOperations
    - Each test gets a fresh session (function scope)
    - Session is injected into DatabaseOperations
    - All operations use the same session (no connection conflicts)
    - Session is rolled back after each test (isolation)

Reference: https://github.com/MagicStack/asyncpg/issues/863#issuecomment-1229220920
"""

import pytest
import pytest_asyncio
from uuid import uuid4

from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.pool import NullPool

from app.database.operations import DatabaseOperations
from app.config import get_settings


settings = get_settings()

# Create test-specific engine with NullPool to avoid connection reuse issues
# Each test gets a fresh connection - prevents "another operation is in progress" errors
test_engine = create_async_engine(
    settings.database_url,
    poolclass=NullPool,
    echo=False,
)

# Create test-specific session factory
TestAsyncSessionLocal = async_sessionmaker(
    test_engine,
    class_=AsyncSession,
    expire_on_commit=False,
    autocommit=False,
    autoflush=False,
)


@pytest_asyncio.fixture(scope="function")
async def db_session():
    """
    Provide an isolated database session for each test.

    Creates a new session for each test function.
    The session is NOT automatically committed - tests must call
    await session.commit() if they want changes persisted.

    After the test, the session is rolled back to ensure isolation.
    """
    async with TestAsyncSessionLocal() as session:
        yield session
        # Rollback any uncommitted changes after each test
        await session.rollback()


@pytest_asyncio.fixture(scope="function")
async def db_ops(db_session: AsyncSession):
    """
    Provide DatabaseOperations instance with injected session.

    This is the key fixture for integration tests:
    - All database operations use the same session
    - No connection conflicts between operations
    - Session is rolled back after test (via db_session fixture)

    Usage in tests:
        async def test_something(db_ops):
            game = await db_ops.create_game(...)
            # All operations use the same session
            await db_ops.add_lineup(...)
    """
    return DatabaseOperations(db_session)


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


@pytest.fixture
def sample_game_id():
    """Alias for unique_game_id for backward compatibility."""
    return uuid4()


@pytest_asyncio.fixture(scope="function")
async def sample_game(db_ops: DatabaseOperations, db_session: AsyncSession):
    """
    Create a sample game for testing.

    Returns the game_id of the created game.
    The game is created within the test session and will be
    rolled back after the test completes.
    """
    game_id = uuid4()
    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",
    )
    # Flush to make the game visible in this session
    await db_session.flush()
    return game_id


@pytest_asyncio.fixture(scope="function")
async def sample_pd_game(db_ops: DatabaseOperations, db_session: AsyncSession):
    """
    Create a sample PD league game for testing.

    Returns the game_id of the created game.
    """
    game_id = uuid4()
    await db_ops.create_game(
        game_id=game_id,
        league_id="pd",
        home_team_id=10,
        away_team_id=20,
        game_mode="friendly",
        visibility="public",
    )
    await db_session.flush()
    return game_id


@pytest_asyncio.fixture(scope="function")
async def game_with_lineup(db_ops: DatabaseOperations, db_session: AsyncSession):
    """
    Create a game with full lineups for both teams.

    Returns a dict with game_id and lineup entries.
    Useful for tests that need a fully set up game.
    """
    game_id = uuid4()

    # Create game
    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 home team lineup (9 players)
    home_lineup = []
    positions = ["P", "C", "1B", "2B", "3B", "SS", "LF", "CF", "RF"]
    for i, pos in enumerate(positions):
        lineup = await db_ops.add_sba_lineup_player(
            game_id=game_id,
            team_id=1,
            player_id=100 + i,
            position=pos,
            batting_order=None if pos == "P" else i,
            is_starter=True,
        )
        home_lineup.append(lineup)

    # Add away team lineup (9 players)
    away_lineup = []
    for i, pos in enumerate(positions):
        lineup = await db_ops.add_sba_lineup_player(
            game_id=game_id,
            team_id=2,
            player_id=200 + i,
            position=pos,
            batting_order=None if pos == "P" else i,
            is_starter=True,
        )
        away_lineup.append(lineup)

    await db_session.flush()

    return {
        "game_id": game_id,
        "home_lineup": home_lineup,
        "away_lineup": away_lineup,
    }
