import numpy as np
import pandas as pd
from datetime import datetime, timedelta
from typing import Dict, List, Tuple
from scipy import stats
from sklearn.mixture import GaussianMixture
import logging

logger = logging.getLogger(__name__)

class RegimeDetector:
    """
    Market regime detection using various statistical and ML methods
    """
    
    def __init__(self):
        self.regimes = ['bull', 'bear', 'sideways', 'high_volatility', 'low_volatility']
        
    def detect_current_regime(self, lookback_days: int = 60) -> Dict:
        """
        Detect current market regime using multiple indicators
        """
        # In real implementation, would fetch market data
        # For now, generate sample data
        market_data = self._generate_market_data(lookback_days)
        
        # Calculate various regime indicators
        trend_regime = self._detect_trend_regime(market_data)
        volatility_regime = self._detect_volatility_regime(market_data)
        momentum_regime = self._detect_momentum_regime(market_data)
        
        # Combine indicators for final regime
        regime, confidence = self._combine_regime_indicators(
            trend_regime, 
            volatility_regime, 
            momentum_regime
        )
        
        return {
            'regime': regime,
            'confidence': confidence,
            'indicators': {
                'trend': trend_regime,
                'volatility': volatility_regime,
                'momentum': momentum_regime,
                'market_breadth': self._calculate_market_breadth(market_data),
                'fear_greed_index': self._calculate_fear_greed_index(market_data)
            },
            'sub_regimes': {
                'trend_strength': self._calculate_trend_strength(market_data),
                'volatility_percentile': self._calculate_volatility_percentile(market_data),
                'correlation_regime': self._detect_correlation_regime(market_data)
            }
        }
    
    def _generate_market_data(self, days: int) -> pd.DataFrame:
        """Generate sample market data for testing"""
        dates = pd.date_range(end=datetime.now(), periods=days, freq='D')
        
        # Generate correlated returns for multiple assets
        n_assets = 10
        returns = np.random.multivariate_normal(
            mean=[0.0005] * n_assets,
            cov=np.eye(n_assets) * 0.0004 + np.ones((n_assets, n_assets)) * 0.0001,
            size=days
        )
        
        # Create price series
        prices = pd.DataFrame(
            (1 + returns).cumprod(axis=0) * 100,
            index=dates,
            columns=[f'Asset_{i}' for i in range(n_assets)]
        )
        
        # Add market index
        prices['Market'] = prices.mean(axis=1)
        
        # Add volatility index (like VIX)
        prices['Volatility'] = pd.Series(returns[:, 0]).rolling(20).std() * np.sqrt(252) * 100
        
        return prices
    
    def _detect_trend_regime(self, data: pd.DataFrame) -> Dict:
        """Detect trend regime using moving averages and linear regression"""
        market = data['Market']
        
        # Calculate moving averages
        ma_short = market.rolling(20).mean()
        ma_long = market.rolling(50).mean()
        
        # Trend strength
        current_price = market.iloc[-1]
        trend_score = (current_price - ma_long.iloc[-1]) / ma_long.iloc[-1]
        
        # Linear regression trend
        x = np.arange(len(market))
        slope, _, r_value, _, _ = stats.linregress(x, market.values)
        
        # Determine regime
        if trend_score > 0.05 and ma_short.iloc[-1] > ma_long.iloc[-1]:
            regime = 'bull'
        elif trend_score < -0.05 and ma_short.iloc[-1] < ma_long.iloc[-1]:
            regime = 'bear'
        else:
            regime = 'sideways'
        
        return {
            'regime': regime,
            'trend_score': trend_score,
            'slope': slope,
            'r_squared': r_value ** 2
        }
    
    def _detect_volatility_regime(self, data: pd.DataFrame) -> Dict:
        """Detect volatility regime using GARCH-like analysis"""
        returns = data['Market'].pct_change().dropna()
        
        # Calculate rolling volatility
        vol_short = returns.rolling(10).std() * np.sqrt(252)
        vol_long = returns.rolling(30).std() * np.sqrt(252)
        
        current_vol = vol_short.iloc[-1]
        vol_percentile = stats.percentileofscore(vol_long.dropna(), current_vol)
        
        # Volatility regime
        if vol_percentile > 75:
            regime = 'high_volatility'
        elif vol_percentile < 25:
            regime = 'low_volatility'
        else:
            regime = 'normal_volatility'
        
        # Volatility of volatility
        vol_of_vol = vol_short.rolling(20).std().iloc[-1]
        
        return {
            'regime': regime,
            'current_volatility': current_vol,
            'volatility_percentile': vol_percentile,
            'vol_of_vol': vol_of_vol
        }
    
    def _detect_momentum_regime(self, data: pd.DataFrame) -> Dict:
        """Detect momentum regime using RSI and rate of change"""
        market = data['Market']
        
        # Calculate RSI
        rsi = self._calculate_rsi(market, period=14)
        
        # Rate of change
        roc_short = (market.iloc[-1] / market.iloc[-5] - 1) * 100
        roc_long = (market.iloc[-1] / market.iloc[-20] - 1) * 100
        
        # Momentum regime
        if rsi > 70 and roc_short > 0:
            regime = 'overbought'
        elif rsi < 30 and roc_short < 0:
            regime = 'oversold'
        elif roc_short > 2 and roc_long > 5:
            regime = 'strong_momentum'
        elif roc_short < -2 and roc_long < -5:
            regime = 'weak_momentum'
        else:
            regime = 'neutral_momentum'
        
        return {
            'regime': regime,
            'rsi': rsi,
            'roc_short': roc_short,
            'roc_long': roc_long
        }
    
    def _detect_correlation_regime(self, data: pd.DataFrame) -> str:
        """Detect correlation regime among assets"""
        # Calculate rolling correlation
        asset_returns = data.iloc[:, :-2].pct_change().dropna()
        corr_matrix = asset_returns.rolling(30).corr()
        
        # Average pairwise correlation
        n_assets = len(asset_returns.columns)
        avg_corr = (corr_matrix.sum().sum() - n_assets) / (n_assets * (n_assets - 1))
        current_avg_corr = avg_corr.iloc[-1]
        
        if current_avg_corr > 0.7:
            return 'high_correlation'
        elif current_avg_corr < 0.3:
            return 'low_correlation'
        else:
            return 'normal_correlation'
    
    def _calculate_rsi(self, prices: pd.Series, period: int = 14) -> float:
        """Calculate RSI"""
        delta = prices.diff()
        gain = (delta.where(delta > 0, 0)).rolling(period).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(period).mean()
        
        rs = gain / loss
        rsi = 100 - (100 / (1 + rs))
        
        return rsi.iloc[-1]
    
    def _calculate_market_breadth(self, data: pd.DataFrame) -> float:
        """Calculate market breadth (advance/decline ratio)"""
        # Calculate daily returns for all assets
        returns = data.iloc[:, :-2].pct_change().iloc[-1]
        
        advancing = (returns > 0).sum()
        declining = (returns < 0).sum()
        
        return advancing / (advancing + declining) if (advancing + declining) > 0 else 0.5
    
    def _calculate_fear_greed_index(self, data: pd.DataFrame) -> float:
        """Simplified fear & greed index"""
        # Combine multiple indicators
        volatility = data['Volatility'].iloc[-1]
        momentum = self._detect_momentum_regime(data)['roc_short']
        breadth = self._calculate_market_breadth(data)
        
        # Normalize and combine
        vol_score = 1 - min(volatility / 40, 1)  # Lower vol = higher greed
        momentum_score = (momentum + 10) / 20  # Normalize to 0-1
        
        fear_greed = (vol_score + momentum_score + breadth) / 3
        
        return fear_greed * 100  # 0 = extreme fear, 100 = extreme greed
    
    def _calculate_trend_strength(self, data: pd.DataFrame) -> float:
        """Calculate trend strength using ADX-like indicator"""
        market = data['Market']
        
        # Calculate directional movement
        high = market.rolling(2).max()
        low = market.rolling(2).min()
        
        plus_dm = (high - high.shift(1)).where(lambda x: x > 0, 0)
        minus_dm = (low.shift(1) - low).where(lambda x: x > 0, 0)
        
        # Smooth and normalize
        period = 14
        plus_di = plus_dm.rolling(period).mean() / market.rolling(period).std()
        minus_di = minus_dm.rolling(period).mean() / market.rolling(period).std()
        
        # Calculate trend strength
        dx = abs(plus_di - minus_di) / (plus_di + minus_di)
        adx = dx.rolling(period).mean().iloc[-1]
        
        return min(adx * 100, 100) if not np.isnan(adx) else 50
    
    def _calculate_volatility_percentile(self, data: pd.DataFrame) -> float:
        """Calculate current volatility percentile"""
        volatility_regime = self._detect_volatility_regime(data)
        return volatility_regime['volatility_percentile']
    
    def _combine_regime_indicators(
        self, 
        trend: Dict, 
        volatility: Dict, 
        momentum: Dict
    ) -> Tuple[str, float]:
        """Combine multiple indicators to determine overall regime"""
        # Simple weighted combination
        regimes = []
        weights = []
        
        # Trend regime
        if trend['regime'] in ['bull', 'bear']:
            regimes.append(trend['regime'])
            weights.append(abs(trend['trend_score']) * 10)
        
        # Volatility regime
        if volatility['regime'] == 'high_volatility':
            regimes.append('high_volatility')
            weights.append(volatility['volatility_percentile'] / 100)
        
        # Choose dominant regime
        if not regimes:
            return 'sideways', 0.5
        
        # Weight by confidence
        dominant_idx = np.argmax(weights)
        regime = regimes[dominant_idx]
        confidence = min(weights[dominant_idx], 1.0)
        
        return regime, confidence