"""Pytest configuration and fixtures."""

import pytest
import asyncio
from unittest.mock import Mock, AsyncMock, PropertyMock
from decimal import Decimal
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from src.core.database import Base, Database, get_database

from sqlalchemy.pool import StaticPool

@pytest.fixture(scope="session")
def event_loop():
    """Create an instance of the default event loop for each test case."""
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()

@pytest.fixture(scope="session")
async def db_engine():
    """Create async database engine."""
    engine = create_async_engine(
        "sqlite+aiosqlite:///:memory:", 
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
        echo=False
    )
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield engine
    await engine.dispose()

@pytest.fixture
async def db_session(db_engine):
    """Create async database session."""
    async_session = async_sessionmaker(bind=db_engine, expire_on_commit=False)
    async with async_session() as session:
        yield session

@pytest.fixture(autouse=True)
def override_get_database(db_engine, monkeypatch):
    """Override get_database to use test engine."""
    test_db = Database()
    # We mock the internal attributes to return our test engine/session
    test_db.engine = db_engine
    test_db.SessionLocal = async_sessionmaker(bind=db_engine, class_=AsyncSession, expire_on_commit=False)
    
    # Patch the global get_database
    monkeypatch.setattr("src.core.database._db_instance", test_db)
    return test_db

@pytest.fixture
def mock_exchange_adapter():
    """Mock exchange adapter."""
    from src.exchanges.base import BaseExchangeAdapter
    adapter = AsyncMock(spec=BaseExchangeAdapter)
    adapter.get_ticker.return_value = {'last': Decimal("50000")}
    adapter.place_order.return_value = {'id': 'test_order_123', 'status': 'open'}
    adapter.get_balance.return_value = {'USD': Decimal("10000")}
    # Helper methods should be sync mocks
    # Note: If extract_fee... is not part of BaseExchangeAdapter spec, we have to attach it manually
    # But checking base.py, it likely IS or isn't.
    # Safe to attach it manually even with spec if we traverse __dict__ or simply assign.
    # However, standard mock might block unknown attribs.
    # Actually BaseExchangeAdapter is abstract.
    
    # Let's inspect BaseExchangeAdapter structure if needed. 
    # For now, let's assume usage of spec is the right direction. 
    # But if extract_fee... is NOT in BaseExchangeAdapter, we might need to mock a Concrete class like Coinbase
    pass
    
    # Better approach: Just Delete get_fee_structure from the mock to ensure AttributeError
    adapter = AsyncMock()
    del adapter.get_fee_structure 
    # Wait, AsyncMock creates attrs on access. del might not work if not existing.
    # We can se side_effect to raise AttributeError
    
    adapter.get_ticker.return_value = {'last': Decimal("50000")}
    adapter.place_order.return_value = {'id': 'test_order_123', 'status': 'open'}
    adapter.get_balance.return_value = {'USD': Decimal("10000")}
    adapter.extract_fee_from_order_response = Mock(return_value=Decimal("1.0"))
    adapter.name = "coinbase" # FeeCalculator accesses .name
    type(adapter).get_fee_structure = PropertyMock(side_effect=AttributeError) 
    # Accessing methods on AsyncMock...
    
    return adapter
