"""Unit tests for CCXT pricing provider."""

import pytest
from unittest.mock import Mock, patch, MagicMock
from decimal import Decimal
from datetime import datetime

from src.data.providers.ccxt_provider import CCXTProvider


@pytest.fixture
def mock_ccxt_exchange():
    """Create a mock CCXT exchange."""
    exchange = Mock()
    exchange.markets = {
        'BTC/USDT': {},
        'ETH/USDT': {},
        'BTC/USD': {},
    }
    exchange.id = 'kraken'
    exchange.fetch_ticker = Mock(return_value={
        'bid': 50000.0,
        'ask': 50001.0,
        'last': 50000.5,
        'high': 51000.0,
        'low': 49000.0,
        'quoteVolume': 1000000.0,
        'timestamp': 1609459200000,
    })
    exchange.fetch_ohlcv = Mock(return_value=[
        [1609459200000, 50000, 51000, 49000, 50000, 1000],
    ])
    exchange.load_markets = Mock(return_value=exchange.markets)
    return exchange


@pytest.fixture
def provider():
    """Create a CCXT provider instance."""
    return CCXTProvider(exchange_name='kraken')


class TestCCXTProvider:
    """Tests for CCXTProvider."""
    
    def test_init(self, provider):
        """Test provider initialization."""
        assert provider.name == "CCXT Provider"
        assert not provider._connected
        assert provider.exchange is None
    
    def test_name_property(self, provider):
        """Test name property."""
        provider._selected_exchange_id = 'kraken'
        assert provider.name == "CCXT-Kraken"
    
    @patch('src.data.providers.ccxt_provider.ccxt')
    def test_connect_success(self, mock_ccxt, provider, mock_ccxt_exchange):
        """Test successful connection."""
        mock_ccxt.kraken = Mock(return_value=mock_ccxt_exchange)
        
        result = provider.connect()
        
        assert result is True
        assert provider._connected is True
        assert provider.exchange == mock_ccxt_exchange
        assert provider._selected_exchange_id == 'kraken'
    
    @patch('src.data.providers.ccxt_provider.ccxt')
    def test_connect_failure(self, mock_ccxt, provider):
        """Test connection failure."""
        mock_ccxt.kraken = Mock(side_effect=Exception("Connection failed"))
        
        result = provider.connect()
        
        assert result is False
        assert not provider._connected
    
    @patch('src.data.providers.ccxt_provider.ccxt')
    def test_get_ticker(self, mock_ccxt, provider, mock_ccxt_exchange):
        """Test getting ticker data."""
        mock_ccxt.kraken = Mock(return_value=mock_ccxt_exchange)
        provider.connect()
        
        ticker = provider.get_ticker('BTC/USDT')
        
        assert ticker['symbol'] == 'BTC/USDT'
        assert isinstance(ticker['bid'], Decimal)
        assert isinstance(ticker['last'], Decimal)
        assert ticker['last'] > 0
    
    @patch('src.data.providers.ccxt_provider.ccxt')
    def test_get_ohlcv(self, mock_ccxt, provider, mock_ccxt_exchange):
        """Test getting OHLCV data."""
        mock_ccxt.kraken = Mock(return_value=mock_ccxt_exchange)
        provider.connect()
        
        ohlcv = provider.get_ohlcv('BTC/USDT', '1h', limit=10)
        
        assert len(ohlcv) > 0
        assert len(ohlcv[0]) == 6  # timestamp, open, high, low, close, volume
    
    @patch('src.data.providers.ccxt_provider.ccxt')
    def test_subscribe_ticker(self, mock_ccxt, provider, mock_ccxt_exchange):
        """Test subscribing to ticker updates."""
        mock_ccxt.kraken = Mock(return_value=mock_ccxt_exchange)
        provider.connect()
        
        callback = Mock()
        result = provider.subscribe_ticker('BTC/USDT', callback)
        
        assert result is True
        assert 'ticker_BTC/USDT' in provider._subscribers
    
    def test_normalize_symbol(self, provider):
        """Test symbol normalization."""
        # Test with exchange
        with patch.object(provider, 'exchange') as mock_exchange:
            mock_exchange.markets = {'BTC/USDT': {}}
            normalized = provider.normalize_symbol('btc-usdt')
            assert normalized == 'BTC/USDT'
        
        # Test without exchange
        provider.exchange = None
        normalized = provider.normalize_symbol('btc-usdt')
        assert normalized == 'BTC/USDT'
    
    @patch('src.data.providers.ccxt_provider.ccxt')
    def test_disconnect(self, mock_ccxt, provider, mock_ccxt_exchange):
        """Test disconnection."""
        mock_ccxt.kraken = Mock(return_value=mock_ccxt_exchange)
        provider.connect()
        provider.subscribe_ticker('BTC/USDT', Mock())
        
        provider.disconnect()
        
        assert not provider._connected
        assert provider.exchange is None
        assert len(provider._subscribers) == 0
