"""Market condition analyzer for intelligent autopilot.

Analyzes real-time market conditions and extracts features for ML model.
"""

from decimal import Decimal
from typing import Dict, Any, Optional
from enum import Enum
from dataclasses import dataclass
import pandas as pd
import numpy as np

from src.core.logger import get_logger
from src.data.indicators import get_indicators

logger = get_logger(__name__)


class MarketRegime(str, Enum):
    """Market regime classification."""
    TRENDING_UP = "trending_up"
    TRENDING_DOWN = "trending_down"
    RANGING = "ranging"
    HIGH_VOLATILITY = "high_volatility"
    LOW_VOLATILITY = "low_volatility"
    BREAKOUT = "breakout"
    REVERSAL = "reversal"
    UNKNOWN = "unknown"


@dataclass
class MarketConditions:
    """Market conditions snapshot."""
    symbol: str
    timeframe: str
    regime: MarketRegime
    features: Dict[str, float]
    timestamp: pd.Timestamp
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for storage."""
        return {
            'symbol': self.symbol,
            'timeframe': self.timeframe,
            'regime': self.regime.value,
            'features': self.features,
            'timestamp': self.timestamp.isoformat()
        }


class MarketAnalyzer:
    """Analyzes market conditions and extracts features for ML."""
    
    def __init__(self):
        """Initialize market analyzer."""
        self.logger = get_logger(__name__)
        self.indicators = get_indicators()
    
    def analyze_current_conditions(
        self,
        symbol: str,
        timeframe: str,
        ohlcv_data: pd.DataFrame
    ) -> MarketConditions:
        """Analyze current market conditions.
        
        Args:
            symbol: Trading symbol
            timeframe: Timeframe
            ohlcv_data: OHLCV DataFrame with columns: open, high, low, close, volume
            
        Returns:
            MarketConditions object
        """
        if len(ohlcv_data) < 50:
            self.logger.warning(f"Insufficient data for analysis: {len(ohlcv_data)} candles")
            return MarketConditions(
                symbol=symbol,
                timeframe=timeframe,
                regime=MarketRegime.UNKNOWN,
                features={},
                timestamp=pd.Timestamp.now()
            )
        
        # Extract features
        features = self.extract_features(ohlcv_data)
        
        # Classify market regime
        regime = self.classify_market_regime(features, ohlcv_data)
        
        return MarketConditions(
            symbol=symbol,
            timeframe=timeframe,
            regime=regime,
            features=features,
            timestamp=pd.Timestamp.now()
        )
    
    def extract_features(self, df: pd.DataFrame) -> Dict[str, float]:
        """Extract comprehensive market features.
        
        Args:
            df: OHLCV DataFrame
            
        Returns:
            Dictionary of feature names to values
        """
        features = {}
        
        try:
            close = df['close']
            high = df['high']
            low = df['low']
            open_price = df['open']
            volume = df['volume']
            
            # Trend Features
            features['sma_20'] = float(close.rolling(20).mean().iloc[-1])
            features['sma_50'] = float(close.rolling(50).mean().iloc[-1]) if len(close) >= 50 else features['sma_20']
            features['ema_12'] = float(self.indicators.ema(close, period=12).iloc[-1])
            features['ema_26'] = float(self.indicators.ema(close, period=26).iloc[-1]) if len(close) >= 26 else features['ema_12']
            
            # Price position relative to MAs
            current_price = float(close.iloc[-1])
            features['price_vs_sma20'] = (current_price - features['sma_20']) / features['sma_20'] if features['sma_20'] > 0 else 0.0
            features['price_vs_sma50'] = (current_price - features['sma_50']) / features['sma_50'] if features['sma_50'] > 0 else 0.0
            features['sma20_vs_sma50'] = (features['sma_20'] - features['sma_50']) / features['sma_50'] if features['sma_50'] > 0 else 0.0
            
            # Trend strength (ADX)
            if len(df) >= 14:
                adx = self.indicators.adx(high, low, close, period=14)
                features['adx'] = float(adx.iloc[-1]) if not pd.isna(adx.iloc[-1]) else 0.0
            else:
                features['adx'] = 0.0
            
            # Momentum Features
            if len(close) >= 14:
                rsi = self.indicators.rsi(close, period=14)
                features['rsi'] = float(rsi.iloc[-1]) if not pd.isna(rsi.iloc[-1]) else 50.0
            else:
                features['rsi'] = 50.0
            
            # MACD
            if len(close) >= 26:
                macd_result = self.indicators.macd(close, fast=12, slow=26, signal=9)
                features['macd'] = float(macd_result['macd'].iloc[-1]) if not pd.isna(macd_result['macd'].iloc[-1]) else 0.0
                features['macd_signal'] = float(macd_result['signal'].iloc[-1]) if not pd.isna(macd_result['signal'].iloc[-1]) else 0.0
                features['macd_histogram'] = float(macd_result['histogram'].iloc[-1]) if not pd.isna(macd_result['histogram'].iloc[-1]) else 0.0
            else:
                features['macd'] = 0.0
                features['macd_signal'] = 0.0
                features['macd_histogram'] = 0.0
            
            # Volatility Features
            if len(df) >= 14:
                atr = self.indicators.atr(high, low, close, period=14)
                features['atr'] = float(atr.iloc[-1]) if not pd.isna(atr.iloc[-1]) else 0.0
                features['atr_percent'] = (features['atr'] / current_price) * 100 if current_price > 0 else 0.0
            else:
                features['atr'] = 0.0
                features['atr_percent'] = 0.0
            
            # Bollinger Bands
            if len(close) >= 20:
                bb = self.indicators.bollinger_bands(close, period=20, std_dev=2)
                features['bb_upper'] = float(bb['upper'].iloc[-1]) if not pd.isna(bb['upper'].iloc[-1]) else current_price
                features['bb_lower'] = float(bb['lower'].iloc[-1]) if not pd.isna(bb['lower'].iloc[-1]) else current_price
                features['bb_middle'] = float(bb['middle'].iloc[-1]) if not pd.isna(bb['middle'].iloc[-1]) else current_price
                features['bb_width'] = (features['bb_upper'] - features['bb_lower']) / features['bb_middle'] if features['bb_middle'] > 0 else 0.0
                features['bb_position'] = (current_price - features['bb_lower']) / (features['bb_upper'] - features['bb_lower']) if (features['bb_upper'] - features['bb_lower']) > 0 else 0.5
            else:
                features['bb_upper'] = current_price
                features['bb_lower'] = current_price
                features['bb_middle'] = current_price
                features['bb_width'] = 0.0
                features['bb_position'] = 0.5
            
            # Volume Features
            if len(volume) >= 20:
                volume_ma = volume.rolling(20).mean()
                features['volume_ratio'] = float(volume.iloc[-1] / volume_ma.iloc[-1]) if volume_ma.iloc[-1] > 0 else 1.0
                features['volume_trend'] = float((volume.iloc[-5:].mean() - volume.iloc[-20:-5].mean()) / volume.iloc[-20:-5].mean()) if volume.iloc[-20:-5].mean() > 0 else 0.0
            else:
                features['volume_ratio'] = 1.0
                features['volume_trend'] = 0.0
            
            # OBV (On-Balance Volume)
            if len(close) >= 10:
                obv = self.indicators.obv(close, volume)
                features['obv_trend'] = float((obv.iloc[-1] - obv.iloc[-10]) / abs(obv.iloc[-10])) if obv.iloc[-10] != 0 else 0.0
            else:
                features['obv_trend'] = 0.0
            
            # Price Action Features
            features['price_change_1'] = float((close.iloc[-1] - close.iloc[-2]) / close.iloc[-2]) if len(close) >= 2 and close.iloc[-2] > 0 else 0.0
            features['price_change_5'] = float((close.iloc[-1] - close.iloc[-6]) / close.iloc[-6]) if len(close) >= 6 and close.iloc[-6] > 0 else 0.0
            features['price_change_20'] = float((close.iloc[-1] - close.iloc[-21]) / close.iloc[-21]) if len(close) >= 21 and close.iloc[-21] > 0 else 0.0
            
            # High-Low Range
            features['high_low_range'] = float((high.iloc[-1] - low.iloc[-1]) / current_price) if current_price > 0 else 0.0
            features['high_low_range_5'] = float((high.iloc[-5:].max() - low.iloc[-5:].min()) / current_price) if current_price > 0 else 0.0
            
            # Candlestick Patterns (simplified)
            if len(df) >= 3:
                features['is_bullish_candle'] = 1.0 if close.iloc[-1] > open_price.iloc[-1] else 0.0
                features['candle_body_size'] = float(abs(close.iloc[-1] - open_price.iloc[-1]) / current_price) if current_price > 0 else 0.0
                features['candle_wick_upper'] = float((high.iloc[-1] - max(close.iloc[-1], open_price.iloc[-1])) / current_price) if current_price > 0 else 0.0
                features['candle_wick_lower'] = float((min(close.iloc[-1], open_price.iloc[-1]) - low.iloc[-1]) / current_price) if current_price > 0 else 0.0
            else:
                features['is_bullish_candle'] = 0.5
                features['candle_body_size'] = 0.0
                features['candle_wick_upper'] = 0.0
                features['candle_wick_lower'] = 0.0
            
            # Support/Resistance Proximity (simplified - using recent highs/lows)
            if len(df) >= 20:
                recent_high = float(high.iloc[-20:].max())
                recent_low = float(low.iloc[-20:].min())
                features['distance_to_resistance'] = (recent_high - current_price) / current_price if current_price > 0 else 0.0
                features['distance_to_support'] = (current_price - recent_low) / current_price if current_price > 0 else 0.0
            else:
                features['distance_to_resistance'] = 0.0
                features['distance_to_support'] = 0.0
            
            # ==========================================
            # NEW FEATURES FOR IMPROVED ML ACCURACY
            # ==========================================
            
            # Volatility Percentile - current ATR vs 30-day rolling ATR
            if len(df) >= 30:
                atr_series = self.indicators.atr(high, low, close, period=14)
                atr_30_mean = float(atr_series.iloc[-30:].mean()) if len(atr_series) >= 30 else features.get('atr', 0)
                features['volatility_percentile'] = features.get('atr', 0) / atr_30_mean if atr_30_mean > 0 else 1.0
            else:
                features['volatility_percentile'] = 1.0
            
            # Momentum Delta - 3-period vs 10-period price change (acceleration)
            if len(close) >= 11:
                momentum_3 = float((close.iloc[-1] - close.iloc[-4]) / close.iloc[-4]) if close.iloc[-4] > 0 else 0.0
                momentum_10 = float((close.iloc[-1] - close.iloc[-11]) / close.iloc[-11]) if close.iloc[-11] > 0 else 0.0
                features['momentum_3'] = momentum_3
                features['momentum_10'] = momentum_10
                features['momentum_delta'] = momentum_3 - momentum_10  # Positive = accelerating
            else:
                features['momentum_3'] = 0.0
                features['momentum_10'] = 0.0
                features['momentum_delta'] = 0.0
            
            # Trend Strength Change - ADX rate of change
            if len(df) >= 20:
                adx_series = self.indicators.adx(high, low, close, period=14)
                if len(adx_series) >= 5:
                    adx_current = float(adx_series.iloc[-1]) if not pd.isna(adx_series.iloc[-1]) else 0.0
                    adx_prev = float(adx_series.iloc[-5]) if not pd.isna(adx_series.iloc[-5]) else adx_current
                    features['adx_change'] = adx_current - adx_prev  # Positive = strengthening trend
                else:
                    features['adx_change'] = 0.0
            else:
                features['adx_change'] = 0.0
            
            # Volume Climax Detection - extreme volume with reversal candle
            if len(df) >= 20:
                volume_std = float(volume.iloc[-20:].std())
                volume_mean = float(volume.iloc[-20:].mean())
                current_volume = float(volume.iloc[-1])
                z_score = (current_volume - volume_mean) / volume_std if volume_std > 0 else 0.0
                is_reversal = (close.iloc[-1] < open_price.iloc[-1]) != (close.iloc[-2] < open_price.iloc[-2])
                features['volume_z_score'] = z_score
                features['volume_climax'] = 1.0 if (z_score > 2.0 and is_reversal) else 0.0
            else:
                features['volume_z_score'] = 0.0
                features['volume_climax'] = 0.0
            
            # RSI Extremes - overbought/oversold signals
            rsi_value = features.get('rsi', 50.0)
            features['rsi_oversold'] = 1.0 if rsi_value < 30 else 0.0
            features['rsi_overbought'] = 1.0 if rsi_value > 70 else 0.0
            features['rsi_extreme'] = 1.0 if (rsi_value < 25 or rsi_value > 75) else 0.0
            
            # Price Position in Range - where is price in N-day range
            if len(df) >= 20:
                range_high = float(high.iloc[-20:].max())
                range_low = float(low.iloc[-20:].min())
                range_size = range_high - range_low
                features['price_in_range'] = (current_price - range_low) / range_size if range_size > 0 else 0.5
            else:
                features['price_in_range'] = 0.5
            
            # Trend Alignment - short term vs medium term
            sma_10 = float(close.rolling(10).mean().iloc[-1]) if len(close) >= 10 else current_price
            features['sma_10'] = sma_10
            features['trend_aligned'] = 1.0 if (sma_10 > features.get('sma_20', sma_10) > features.get('sma_50', sma_10)) or \
                                              (sma_10 < features.get('sma_20', sma_10) < features.get('sma_50', sma_10)) else 0.0
            
            # Consecutive Candles - streak of bullish/bearish candles
            if len(df) >= 5:
                bullish_streak = 0
                bearish_streak = 0
                for i in range(1, 6):
                    if close.iloc[-i] > open_price.iloc[-i]:
                        if bearish_streak == 0:
                            bullish_streak += 1
                        else:
                            break
                    else:
                        if bullish_streak == 0:
                            bearish_streak += 1
                        else:
                            break
                features['bullish_streak'] = float(bullish_streak)
                features['bearish_streak'] = float(bearish_streak)
            else:
                features['bullish_streak'] = 0.0
                features['bearish_streak'] = 0.0
            
            # MACD Crossover proximity
            macd_val = features.get('macd', 0.0)
            macd_sig = features.get('macd_signal', 0.0)
            if macd_sig != 0:
                features['macd_signal_ratio'] = macd_val / abs(macd_sig) if macd_sig != 0 else 0.0
            else:
                features['macd_signal_ratio'] = 0.0
            
            # Bollinger Band squeeze detection
            bb_width = features.get('bb_width', 0.0)
            if len(df) >= 30:
                bb_history = []
                for i in range(30):
                    if len(close) > i + 20:
                        bb_temp = self.indicators.bollinger_bands(close.iloc[:-i-1] if i > 0 else close, period=20, std_dev=2)
                        if 'upper' in bb_temp and 'lower' in bb_temp:
                            width = (float(bb_temp['upper'].iloc[-1]) - float(bb_temp['lower'].iloc[-1])) / float(bb_temp['middle'].iloc[-1]) if not pd.isna(bb_temp['middle'].iloc[-1]) and bb_temp['middle'].iloc[-1] > 0 else 0
                            bb_history.append(width)
                if bb_history:
                    avg_width = np.mean(bb_history)
                    features['bb_squeeze'] = 1.0 if bb_width < avg_width * 0.7 else 0.0
                else:
                    features['bb_squeeze'] = 0.0
            else:
                features['bb_squeeze'] = 0.0
            
            # ==========================================
            # SENTIMENT FEATURES
            # ==========================================
            
            # Fear & Greed Index (0-100, 0=extreme fear, 100=extreme greed)
            # Fetch from API or use cached value
            fear_greed = self._get_fear_greed_index()
            features['fear_greed_index'] = fear_greed
            # Normalize to -1 to 1 range (fear negative, greed positive)
            features['fear_greed_normalized'] = (fear_greed - 50) / 50
            # Binary indicators
            features['is_extreme_fear'] = 1.0 if fear_greed < 25 else 0.0
            features['is_extreme_greed'] = 1.0 if fear_greed > 75 else 0.0
            
        except Exception as e:
            self.logger.error(f"Error extracting features: {e}")
            # Return empty features on error
            return {}
        
        return features
    
    def _get_fear_greed_index(self) -> float:
        """Fetch the current Fear & Greed Index.
        
        Uses alternative.me API for Bitcoin Fear & Greed Index.
        Falls back to neutral (50) if unavailable.
        
        Returns:
            Fear & Greed value 0-100
        """
        try:
            import requests
            
            # Check cache
            cache_key = '_fear_greed_cache'
            cache_time_key = '_fear_greed_cache_time'
            
            if hasattr(self, cache_key) and hasattr(self, cache_time_key):
                # Cache for 1 hour
                cache_age = (pd.Timestamp.now() - getattr(self, cache_time_key)).total_seconds()
                if cache_age < 3600:
                    return getattr(self, cache_key)
            
            # Fetch from API
            response = requests.get(
                "https://api.alternative.me/fng/?limit=1",
                timeout=5
            )
            if response.status_code == 200:
                data = response.json()
                if data.get('data') and len(data['data']) > 0:
                    value = float(data['data'][0].get('value', 50))
                    # Cache the value
                    setattr(self, cache_key, value)
                    setattr(self, cache_time_key, pd.Timestamp.now())
                    return value
        except Exception as e:
            self.logger.debug(f"Could not fetch Fear & Greed Index: {e}")
        
        # Return neutral if unavailable
        return 50.0
    
    def classify_market_regime(
        self,
        features: Dict[str, float],
        df: pd.DataFrame
    ) -> MarketRegime:
        """Classify market regime based on features.
        
        Args:
            features: Extracted features
            df: OHLCV DataFrame
            
        Returns:
            MarketRegime classification
        """
        if not features:
            return MarketRegime.UNKNOWN
        
        try:
            close = df['close']
            current_price = float(close.iloc[-1])
            
            # Check for trending conditions
            adx = features.get('adx', 0.0)
            price_vs_sma20 = features.get('price_vs_sma20', 0.0)
            sma20_vs_sma50 = features.get('sma20_vs_sma50', 0.0)
            
            # Strong uptrend
            if adx > 25 and price_vs_sma20 > 0.01 and sma20_vs_sma50 > 0.01:
                return MarketRegime.TRENDING_UP
            
            # Strong downtrend
            if adx > 25 and price_vs_sma20 < -0.01 and sma20_vs_sma50 < -0.01:
                return MarketRegime.TRENDING_DOWN
            
            # Check volatility
            atr_percent = features.get('atr_percent', 0.0)
            bb_width = features.get('bb_width', 0.0)
            
            if atr_percent > 3.0 or bb_width > 0.05:
                return MarketRegime.HIGH_VOLATILITY
            
            if atr_percent < 1.0 and bb_width < 0.02:
                return MarketRegime.LOW_VOLATILITY
            
            # Check for ranging (low ADX, price oscillating)
            if adx < 20:
                # Check if price is oscillating around MAs
                price_change_20 = features.get('price_change_20', 0.0)
                if abs(price_change_20) < 0.05:  # Less than 5% change over 20 periods
                    return MarketRegime.RANGING
            
            # Check for breakout
            bb_position = features.get('bb_position', 0.5)
            volume_ratio = features.get('volume_ratio', 1.0)
            if (bb_position > 0.95 or bb_position < 0.05) and volume_ratio > 1.5:
                return MarketRegime.BREAKOUT
            
            # Check for reversal signals
            rsi = features.get('rsi', 50.0)
            macd_histogram = features.get('macd_histogram', 0.0)
            if (rsi > 70 and macd_histogram < 0) or (rsi < 30 and macd_histogram > 0):
                return MarketRegime.REVERSAL
            
            # Default to ranging if unclear
            return MarketRegime.RANGING
            
        except Exception as e:
            self.logger.error(f"Error classifying market regime: {e}")
            return MarketRegime.UNKNOWN


# Global instance
_market_analyzer: Optional[MarketAnalyzer] = None


def get_market_analyzer() -> MarketAnalyzer:
    """Get global market analyzer instance."""
    global _market_analyzer
    if _market_analyzer is None:
        _market_analyzer = MarketAnalyzer()
    return _market_analyzer

