"""
Tests for ICT/SMC Detector
"""
import pytest
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

# Add parent directory to path
import sys
sys.path.insert(0, str(__file__).rsplit('/', 2)[0])

from src.models.ict_smc_detector import (
    ICTSMCDetector,
    ICTAnalysis,
    OrderBlock,
    FairValueGap,
    MarketBias
)


class TestICTSMCDetector:
    """Test suite for ICT/SMC Detector"""

    @pytest.fixture
    def sample_ohlcv_data(self):
        """Generate sample OHLCV data for testing"""
        np.random.seed(42)
        n_periods = 200
        
        # Generate trending price data
        base_price = 1.1000
        trend = np.cumsum(np.random.randn(n_periods) * 0.0005)
        
        dates = pd.date_range(end=datetime.now(), periods=n_periods, freq='1H')
        
        # Generate OHLCV
        data = []
        for i, date in enumerate(dates):
            price = base_price + trend[i]
            high = price + abs(np.random.randn() * 0.0010)
            low = price - abs(np.random.randn() * 0.0010)
            open_price = price + np.random.randn() * 0.0005
            close = price + np.random.randn() * 0.0005
            volume = np.random.randint(1000, 10000)
            
            data.append({
                'open': max(low, min(high, open_price)),
                'high': high,
                'low': low,
                'close': max(low, min(high, close)),
                'volume': volume
            })
        
        df = pd.DataFrame(data, index=dates)
        return df

    @pytest.fixture
    def detector(self):
        """Create detector instance"""
        return ICTSMCDetector(
            swing_lookback=10,
            ob_min_size=0.001,
            fvg_min_size=0.0005
        )

    def test_detector_initialization(self, detector):
        """Test detector initializes correctly"""
        assert detector.swing_lookback == 10
        assert detector.ob_min_size == 0.001
        assert detector.fvg_min_size == 0.0005

    def test_analyze_returns_ict_analysis(self, detector, sample_ohlcv_data):
        """Test analyze returns ICTAnalysis object"""
        result = detector.analyze(sample_ohlcv_data, "EURUSD", "1H")
        
        assert isinstance(result, ICTAnalysis)
        assert result.symbol == "EURUSD"
        assert result.timeframe == "1H"
        assert result.market_bias in [MarketBias.BULLISH, MarketBias.BEARISH, MarketBias.NEUTRAL]

    def test_analyze_with_insufficient_data(self, detector):
        """Test analyze handles insufficient data gracefully"""
        # Create minimal data
        df = pd.DataFrame({
            'open': [1.1, 1.2],
            'high': [1.15, 1.25],
            'low': [1.05, 1.15],
            'close': [1.12, 1.22],
            'volume': [1000, 1000]
        }, index=pd.date_range(end=datetime.now(), periods=2, freq='1H'))
        
        result = detector.analyze(df, "TEST", "1H")
        
        # Should return empty analysis
        assert result.market_bias == MarketBias.NEUTRAL
        assert result.score == 0

    def test_swing_points_detection(self, detector, sample_ohlcv_data):
        """Test swing high/low detection"""
        swing_highs, swing_lows = detector._find_swing_points(sample_ohlcv_data)
        
        # Should find some swing points
        assert len(swing_highs) > 0
        assert len(swing_lows) > 0
        
        # Each swing point should be a tuple of (index, price)
        for idx, price in swing_highs:
            assert isinstance(idx, int)
            assert isinstance(price, float)

    def test_order_blocks_detection(self, detector, sample_ohlcv_data):
        """Test order block detection"""
        swing_highs, swing_lows = detector._find_swing_points(sample_ohlcv_data)
        order_blocks = detector._find_order_blocks(sample_ohlcv_data, swing_highs, swing_lows)
        
        # May or may not find order blocks depending on data
        for ob in order_blocks:
            assert isinstance(ob, OrderBlock)
            assert ob.type in ['bullish', 'bearish']
            assert ob.high > ob.low
            assert 0 <= ob.strength <= 1

    def test_fair_value_gaps_detection(self, detector, sample_ohlcv_data):
        """Test FVG detection"""
        fvgs = detector._find_fair_value_gaps(sample_ohlcv_data)
        
        for fvg in fvgs:
            assert isinstance(fvg, FairValueGap)
            assert fvg.type in ['bullish', 'bearish']
            assert fvg.high > fvg.low
            assert fvg.size > 0

    def test_premium_discount_zones(self, detector, sample_ohlcv_data):
        """Test premium/discount zone calculation"""
        swing_highs, swing_lows = detector._find_swing_points(sample_ohlcv_data)
        premium, discount, equilibrium = detector._calculate_zones(
            sample_ohlcv_data, swing_highs, swing_lows
        )
        
        # Premium zone should be above equilibrium
        assert premium[0] >= equilibrium or premium[1] >= equilibrium
        
        # Discount zone should be below equilibrium
        assert discount[0] <= equilibrium or discount[1] <= equilibrium

    def test_trade_recommendation(self, detector, sample_ohlcv_data):
        """Test trade recommendation generation"""
        analysis = detector.analyze(sample_ohlcv_data, "EURUSD", "1H")
        recommendation = detector.get_trade_recommendation(analysis)
        
        assert 'action' in recommendation
        assert recommendation['action'] in ['BUY', 'SELL', 'HOLD']
        assert 'score' in recommendation

    def test_analysis_to_dict(self, detector, sample_ohlcv_data):
        """Test analysis serialization"""
        analysis = detector.analyze(sample_ohlcv_data, "EURUSD", "1H")
        result = analysis.to_dict()
        
        assert isinstance(result, dict)
        assert 'symbol' in result
        assert 'market_bias' in result
        assert 'order_blocks' in result
        assert 'fair_value_gaps' in result
        assert 'signals' in result
        assert 'score' in result

    def test_setup_score_range(self, detector, sample_ohlcv_data):
        """Test that setup score is in valid range"""
        analysis = detector.analyze(sample_ohlcv_data, "EURUSD", "1H")
        
        assert 0 <= analysis.score <= 100

    def test_bias_confidence_range(self, detector, sample_ohlcv_data):
        """Test that bias confidence is in valid range"""
        analysis = detector.analyze(sample_ohlcv_data, "EURUSD", "1H")
        
        assert 0 <= analysis.bias_confidence <= 1


class TestStrategyEnsemble:
    """Test suite for Strategy Ensemble"""

    @pytest.fixture
    def sample_ohlcv_data(self):
        """Generate sample OHLCV data"""
        np.random.seed(42)
        n_periods = 300
        
        base_price = 1.1000
        trend = np.cumsum(np.random.randn(n_periods) * 0.0005)
        dates = pd.date_range(end=datetime.now(), periods=n_periods, freq='1H')
        
        data = []
        for i, date in enumerate(dates):
            price = base_price + trend[i]
            high = price + abs(np.random.randn() * 0.0010)
            low = price - abs(np.random.randn() * 0.0010)
            open_price = price + np.random.randn() * 0.0005
            close = price + np.random.randn() * 0.0005
            volume = np.random.randint(1000, 10000)
            
            data.append({
                'open': max(low, min(high, open_price)),
                'high': high,
                'low': low,
                'close': max(low, min(high, close)),
                'volume': volume
            })
        
        return pd.DataFrame(data, index=dates)

    def test_ensemble_import(self):
        """Test ensemble can be imported"""
        from src.models.strategy_ensemble import (
            StrategyEnsemble,
            EnsembleSignal,
            TradeAction,
            SignalStrength
        )
        
        assert StrategyEnsemble is not None
        assert EnsembleSignal is not None

    def test_ensemble_initialization(self):
        """Test ensemble initializes correctly"""
        from src.models.strategy_ensemble import StrategyEnsemble
        
        ensemble = StrategyEnsemble(
            amd_weight=0.25,
            ict_weight=0.35,
            min_confidence=0.6
        )
        
        assert ensemble.min_confidence == 0.6
        # Weights should be normalized
        total = sum(ensemble.weights.values())
        assert abs(total - 1.0) < 0.01

    def test_ensemble_analyze(self, sample_ohlcv_data):
        """Test ensemble analysis"""
        from src.models.strategy_ensemble import StrategyEnsemble, EnsembleSignal
        
        ensemble = StrategyEnsemble()
        signal = ensemble.analyze(sample_ohlcv_data, "EURUSD", "1H")
        
        assert isinstance(signal, EnsembleSignal)
        assert signal.symbol == "EURUSD"
        assert -1 <= signal.net_score <= 1
        assert 0 <= signal.confidence <= 1

    def test_quick_signal(self, sample_ohlcv_data):
        """Test quick signal generation"""
        from src.models.strategy_ensemble import StrategyEnsemble
        
        ensemble = StrategyEnsemble()
        signal = ensemble.get_quick_signal(sample_ohlcv_data, "EURUSD")
        
        assert isinstance(signal, dict)
        assert 'action' in signal
        assert 'confidence' in signal
        assert 'score' in signal


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
