"""
Pytest configuration and shared fixtures for BaoLife test suite.

This module provides global test fixtures including:
- Database connection pools and transactions
- Player fixtures at various life stages
- Game engine with mock dependencies
- Mock storage, output, and service objects
- WebSocket test utilities
- Time mocking utilities
"""
import pytest
import sys
import asyncio
from pathlib import Path
from datetime import datetime, timedelta
from typing import AsyncGenerator

# Add ws/ to Python path
sys.path.insert(0, str(Path(__file__).parent.parent))

# Configure pytest-asyncio
pytest_plugins = ('pytest_asyncio',)


# ============================================================================
# Test Mode Configuration
# ============================================================================

@pytest.fixture(scope="session", autouse=True)
def set_test_mode():
    """Enable test mode for all tests."""
    import os
    original = os.getenv('TEST_MODE')
    os.environ['TEST_MODE'] = 'true'
    os.environ['BAOLIFE_TEST_MODE'] = 'true'
    yield
    if original:
        os.environ['TEST_MODE'] = original
    else:
        os.environ.pop('TEST_MODE', None)
    os.environ.pop('BAOLIFE_TEST_MODE', None)


# ============================================================================
# Database Fixtures
# ============================================================================

@pytest.fixture(scope="session")
async def db_pool():
    """
    Create an async database connection pool for tests.

    This fixture is session-scoped and provides a shared connection pool
    for all tests. The pool is closed when the test session ends.

    Yields:
        aiomysql.Pool: Database connection pool
    """
    from database_async import initialize_pool, close_pool

    # Initialize test database pool
    pool = await initialize_pool(
        host='localhost',
        port=3306,
        user='root',
        password='',
        db='lifesim_test',
        minsize=1,
        maxsize=10
    )

    yield pool

    # Clean up
    await close_pool()


@pytest.fixture
async def db_transaction(db_pool):
    """
    Provide a database transaction that automatically rolls back.

    This fixture creates a new transaction for each test and rolls it back
    after the test completes, ensuring test isolation.

    Yields:
        aiomysql.Connection: Database connection with active transaction
    """
    async with db_pool.acquire() as conn:
        async with conn.cursor() as cursor:
            # Start transaction
            await cursor.execute("START TRANSACTION")

            yield conn

            # Rollback transaction (even if test passed)
            await cursor.execute("ROLLBACK")


# ============================================================================
# Player Fixtures
# ============================================================================

@pytest.fixture
def newborn_player():
    """
    Create a player with a newborn character (age 0).

    Use for testing:
    - Birth events
    - Early childhood mechanics
    - Initial character setup

    Returns:
        playerClass: Player with newborn character
    """
    from tests.utils.player_factory import create_minimal_player
    return create_minimal_player(age=0, name='Test Baby', sex='Male')


@pytest.fixture
def child_player():
    """
    Create a player with a child character (age 8).

    Use for testing:
    - Elementary school events
    - Childhood activities
    - Family relationships

    Returns:
        playerClass: Player with child character
    """
    from tests.utils.player_factory import create_minimal_player
    return create_minimal_player(age=8, name='Test Child', sex='Female', occupation='student')


@pytest.fixture
def teen_player():
    """
    Create a player with a teenage character (age 16).

    Use for testing:
    - High school events
    - Social dynamics
    - Dating mechanics
    - Part-time jobs

    Returns:
        playerClass: Player with teenager character
    """
    from tests.utils.player_factory import create_minimal_player
    return create_minimal_player(age=16, name='Test Teen', sex='Male', occupation='student', money=500)


@pytest.fixture
def adult_player():
    """
    Create a player with an adult character (age 30).

    Use for testing:
    - Career progression
    - Financial management
    - Adult relationships
    - Life milestones

    Returns:
        playerClass: Player with adult character
    """
    from tests.utils.player_factory import create_minimal_player
    return create_minimal_player(age=30, name='Test Adult', sex='Female', occupation='Software Developer', money=50000)


# ============================================================================
# Game Engine Fixtures
# ============================================================================

@pytest.fixture
def game_engine(mock_storage, mock_output, mock_conversation_service):
    """
    Create a GameEngine with mock dependencies.

    This fixture provides a fully-configured game engine that uses mock
    storage and output, allowing tests to run without database or WebSocket
    connections.

    Args:
        mock_storage: Mock storage backend
        mock_output: Mock output handler
        mock_conversation_service: Mock conversation service

    Returns:
        GameEngine: Configured game engine with mocks
    """
    from game_engine import GameEngine

    engine = GameEngine(
        storage=mock_storage,
        output=mock_output,
        conversation_service=mock_conversation_service
    )
    return engine


# ============================================================================
# Mock Object Fixtures
# ============================================================================

@pytest.fixture
def mock_storage():
    """
    Provide a mock storage backend.

    This fixture returns an in-memory storage implementation that doesn't
    require a database connection. All data is stored in memory and cleared
    between tests.

    Returns:
        InMemoryStorage: In-memory storage implementation
    """
    from tests.mocks.storage_mock import InMemoryStorage
    storage = InMemoryStorage()
    yield storage
    storage.clear_all()


@pytest.fixture
def mock_output():
    """
    Provide a mock output handler.

    This fixture returns a mock output implementation that collects all
    sent messages instead of sending them over WebSocket.

    Returns:
        CollectorOutput: Mock output handler
    """
    from tests.mocks.output_mock import CollectorOutput
    output = CollectorOutput()
    yield output
    output.clear()


@pytest.fixture
def mock_conversation_service():
    """
    Provide a mock conversation service.

    This fixture returns a mock conversation service that doesn't make
    real API calls to OpenAI.

    Returns:
        MockConversationService: Mock conversation service
    """
    from tests.mocks.services_mock import MockConversationService
    return MockConversationService()


# ============================================================================
# WebSocket Fixtures
# ============================================================================

@pytest.fixture
async def websocket_client():
    """
    Provide a WebSocket test client.

    This fixture creates a test client for WebSocket connections,
    allowing tests to simulate WebSocket interactions.

    Yields:
        WebSocketTestClient: Test client for WebSocket testing
    """
    # TODO: Implement WebSocket test client
    # This would require setting up a test WebSocket server
    # and returning a client that can connect to it
    pass


@pytest.fixture
async def websocket_server():
    """
    Provide a running WebSocket server for tests.

    This fixture starts a WebSocket server in test mode and tears it
    down after the test completes.

    Yields:
        WebSocketServer: Running test server
    """
    # TODO: Implement WebSocket test server
    # This would start the actual WebSocket server with test configuration
    pass


# ============================================================================
# Time Mocking Fixtures
# ============================================================================

@pytest.fixture
def frozen_time():
    """
    Provide time mocking utilities.

    This fixture returns utilities for freezing and controlling time
    in tests, allowing deterministic testing of time-based logic.

    Returns:
        TimeMock: Time mocking utilities
    """
    from tests.mocks.time_mock import TimeMock
    return TimeMock()


@pytest.fixture
def fixed_datetime():
    """
    Freeze time to a specific datetime for the test.

    Use this fixture when you need a consistent datetime throughout
    a test. Time will be set to 2024-01-01 00:00:00.

    Yields:
        datetime: Fixed datetime object
    """
    from unittest.mock import patch
    fixed_dt = datetime(2024, 1, 1, 0, 0, 0)

    with patch('datetime.datetime') as mock_datetime:
        mock_datetime.now.return_value = fixed_dt
        mock_datetime.utcnow.return_value = fixed_dt
        yield fixed_dt


# ============================================================================
# Utility Fixtures
# ============================================================================

@pytest.fixture
def event_loop():
    """
    Create an event loop for async tests.

    This fixture provides a fresh event loop for each test,
    ensuring isolation between async tests.

    Yields:
        asyncio.AbstractEventLoop: Event loop for the test
    """
    loop = asyncio.new_event_loop()
    yield loop
    loop.close()


@pytest.fixture(autouse=True)
def reset_random_seed():
    """
    Reset random seed before each test.

    This ensures that tests using random values are reproducible.
    """
    import random
    random.seed(42)


@pytest.fixture(autouse=True)
def clear_caches():
    """
    Clear any caches before each test.

    This ensures tests don't interfere with each other through
    shared cache state.
    """
    # Add cache clearing logic here if needed
    yield
    # Clean up after test


# ============================================================================
# Hook: pytest collection
# ============================================================================

def pytest_collection_modifyitems(config, items):
    """
    Modify test collection to add markers automatically.

    This hook automatically adds markers based on test file location:
    - tests/unit/ → unit marker
    - tests/integration/ → integration marker
    - tests/e2e/ → e2e marker
    """
    for item in items:
        # Add marker based on test file path
        if "unit" in str(item.fspath):
            item.add_marker(pytest.mark.unit)
        elif "integration" in str(item.fspath):
            item.add_marker(pytest.mark.integration)
        elif "e2e" in str(item.fspath):
            item.add_marker(pytest.mark.e2e)

        # Add db marker if test uses database fixtures
        if any(fixture in item.fixturenames for fixture in ['db_pool', 'db_transaction']):
            item.add_marker(pytest.mark.db)

        # Add async marker for async tests
        if asyncio.iscoroutinefunction(item.function):
            item.add_marker(pytest.mark.asyncio)
