"""
AMD (Accumulation, Manipulation, Distribution) Phase Detector
Identifies market phases for strategic trading
Migrated from TradingAgent for OrbiQuant IA Platform
"""

import pandas as pd
import numpy as np
from typing import Dict, List, Optional, Tuple, Any
from dataclasses import dataclass
from datetime import datetime, timedelta
from loguru import logger
from scipy import stats


@dataclass
class AMDPhase:
    """AMD phase detection result"""
    phase: str  # 'accumulation', 'manipulation', 'distribution'
    confidence: float
    start_time: datetime
    end_time: Optional[datetime]
    characteristics: Dict[str, float]
    signals: List[str]
    strength: float  # 0-1 phase strength

    def to_dict(self) -> Dict[str, Any]:
        return {
            'phase': self.phase,
            'confidence': self.confidence,
            'start_time': self.start_time.isoformat() if self.start_time else None,
            'end_time': self.end_time.isoformat() if self.end_time else None,
            'characteristics': self.characteristics,
            'signals': self.signals,
            'strength': self.strength
        }


class AMDDetector:
    """
    Detects Accumulation, Manipulation, and Distribution phases
    Based on Smart Money Concepts (SMC)
    """

    def __init__(self, lookback_periods: int = 100):
        """
        Initialize AMD detector

        Args:
            lookback_periods: Number of periods to analyze
        """
        self.lookback_periods = lookback_periods
        self.phase_history = []
        self.current_phase = None

        # Phase thresholds
        self.thresholds = {
            'volume_spike': 2.0,  # Volume above 2x average
            'range_compression': 0.7,  # Range below 70% of average
            'trend_strength': 0.6,  # ADX above 60
            'liquidity_grab': 0.02,  # 2% beyond key level
            'order_block_size': 0.015  # 1.5% minimum block size
        }

    def detect_phase(self, df: pd.DataFrame) -> AMDPhase:
        """
        Detect current market phase

        Args:
            df: OHLCV DataFrame

        Returns:
            AMDPhase object with detection results
        """
        if len(df) < self.lookback_periods:
            return AMDPhase(
                phase='unknown',
                confidence=0,
                start_time=df.index[-1],
                end_time=None,
                characteristics={},
                signals=[],
                strength=0
            )

        # Calculate phase indicators
        indicators = self._calculate_indicators(df)

        # Detect each phase probability
        accumulation_score = self._detect_accumulation(df, indicators)
        manipulation_score = self._detect_manipulation(df, indicators)
        distribution_score = self._detect_distribution(df, indicators)

        # Determine dominant phase
        scores = {
            'accumulation': accumulation_score,
            'manipulation': manipulation_score,
            'distribution': distribution_score
        }

        phase = max(scores, key=scores.get)
        confidence = scores[phase]

        # Get phase characteristics
        characteristics = self._get_phase_characteristics(phase, df, indicators)
        signals = self._get_phase_signals(phase, df, indicators)

        # Calculate phase strength
        strength = self._calculate_phase_strength(phase, indicators)

        return AMDPhase(
            phase=phase,
            confidence=confidence,
            start_time=df.index[-self.lookback_periods],
            end_time=df.index[-1],
            characteristics=characteristics,
            signals=signals,
            strength=strength
        )

    def _calculate_indicators(self, df: pd.DataFrame) -> Dict[str, pd.Series]:
        """Calculate technical indicators for phase detection"""
        indicators = {}

        # Volume analysis
        indicators['volume_ma'] = df['volume'].rolling(20).mean()
        indicators['volume_ratio'] = df['volume'] / indicators['volume_ma']
        indicators['volume_trend'] = df['volume'].rolling(10).mean() - df['volume'].rolling(30).mean()

        # Price action
        indicators['range'] = df['high'] - df['low']
        indicators['range_ma'] = indicators['range'].rolling(20).mean()
        indicators['range_ratio'] = indicators['range'] / indicators['range_ma']

        # Volatility
        indicators['atr'] = self._calculate_atr(df, 14)
        indicators['atr_ratio'] = indicators['atr'] / indicators['atr'].rolling(50).mean()

        # Trend
        indicators['trend'] = df['close'].rolling(20).mean()
        indicators['trend_slope'] = indicators['trend'].diff(5) / 5

        # Order flow
        indicators['buying_pressure'] = (df['close'] - df['low']) / (df['high'] - df['low'])
        indicators['selling_pressure'] = (df['high'] - df['close']) / (df['high'] - df['low'])

        # Market structure
        indicators['higher_highs'] = (df['high'] > df['high'].shift(1)).astype(int).rolling(10).sum()
        indicators['lower_lows'] = (df['low'] < df['low'].shift(1)).astype(int).rolling(10).sum()

        # Liquidity levels
        indicators['swing_high'] = df['high'].rolling(20).max()
        indicators['swing_low'] = df['low'].rolling(20).min()

        # Order blocks
        indicators['order_blocks'] = self._identify_order_blocks(df)

        # Fair value gaps
        indicators['fvg'] = self._identify_fair_value_gaps(df)

        return indicators

    def _calculate_atr(self, df: pd.DataFrame, period: int = 14) -> pd.Series:
        """Calculate Average True Range"""
        high_low = df['high'] - df['low']
        high_close = np.abs(df['high'] - df['close'].shift())
        low_close = np.abs(df['low'] - df['close'].shift())

        true_range = pd.concat([high_low, high_close, low_close], axis=1).max(axis=1)
        return true_range.rolling(period).mean()

    def _identify_order_blocks(self, df: pd.DataFrame) -> pd.Series:
        """Identify order blocks (institutional buying/selling zones)"""
        order_blocks = pd.Series(0, index=df.index)

        for i in range(2, len(df)):
            # Bullish order block: Strong move up after consolidation
            if (df['close'].iloc[i] > df['high'].iloc[i-1] and
                df['volume'].iloc[i] > df['volume'].iloc[i-1:i+1].mean() * 1.5):
                order_blocks.iloc[i] = 1

            # Bearish order block: Strong move down after consolidation
            elif (df['close'].iloc[i] < df['low'].iloc[i-1] and
                  df['volume'].iloc[i] > df['volume'].iloc[i-1:i+1].mean() * 1.5):
                order_blocks.iloc[i] = -1

        return order_blocks

    def _identify_fair_value_gaps(self, df: pd.DataFrame) -> pd.Series:
        """Identify fair value gaps (price inefficiencies)"""
        fvg = pd.Series(0, index=df.index)

        for i in range(2, len(df)):
            # Bullish FVG
            if df['low'].iloc[i] > df['high'].iloc[i-2]:
                gap_size = df['low'].iloc[i] - df['high'].iloc[i-2]
                fvg.iloc[i] = gap_size / df['close'].iloc[i]

            # Bearish FVG
            elif df['high'].iloc[i] < df['low'].iloc[i-2]:
                gap_size = df['low'].iloc[i-2] - df['high'].iloc[i]
                fvg.iloc[i] = -gap_size / df['close'].iloc[i]

        return fvg

    def _detect_accumulation(self, df: pd.DataFrame, indicators: Dict[str, pd.Series]) -> float:
        """
        Detect accumulation phase characteristics
        - Low volatility, range compression
        - Increasing volume on up moves
        - Smart money accumulating positions
        """
        score = 0.0
        weights = {
            'range_compression': 0.25,
            'volume_pattern': 0.25,
            'price_stability': 0.20,
            'order_blocks': 0.15,
            'buying_pressure': 0.15
        }

        # Range compression
        recent_range = indicators['range_ratio'].iloc[-20:].mean()
        if recent_range < self.thresholds['range_compression']:
            score += weights['range_compression']

        # Volume pattern (increasing on up moves)
        price_change = df['close'].pct_change()
        volume_correlation = price_change.iloc[-30:].corr(indicators['volume_ratio'].iloc[-30:])
        if volume_correlation > 0.3:
            score += weights['volume_pattern'] * min(1, volume_correlation / 0.5)

        # Price stability (low volatility)
        volatility = indicators['atr_ratio'].iloc[-20:].mean()
        if volatility < 1.0:
            score += weights['price_stability'] * (1 - volatility)

        # Order blocks (institutional accumulation)
        bullish_blocks = (indicators['order_blocks'].iloc[-30:] > 0).sum()
        if bullish_blocks > 5:
            score += weights['order_blocks'] * min(1, bullish_blocks / 10)

        # Buying pressure
        buying_pressure = indicators['buying_pressure'].iloc[-20:].mean()
        if buying_pressure > 0.55:
            score += weights['buying_pressure'] * min(1, (buying_pressure - 0.5) / 0.3)

        return min(1.0, score)

    def _detect_manipulation(self, df: pd.DataFrame, indicators: Dict[str, pd.Series]) -> float:
        """
        Detect manipulation phase characteristics
        - False breakouts and liquidity grabs
        - Whipsaw price action
        - Stop loss hunting
        """
        score = 0.0
        weights = {
            'liquidity_grabs': 0.30,
            'whipsaws': 0.25,
            'false_breakouts': 0.25,
            'volume_anomalies': 0.20
        }

        # Liquidity grabs (price spikes beyond key levels)
        swing_high = indicators['swing_high'].iloc[-30:]
        swing_low = indicators['swing_low'].iloc[-30:]
        high_grabs = ((df['high'].iloc[-30:] > swing_high * 1.01) &
                      (df['close'].iloc[-30:] < swing_high)).sum()
        low_grabs = ((df['low'].iloc[-30:] < swing_low * 0.99) &
                     (df['close'].iloc[-30:] > swing_low)).sum()

        total_grabs = high_grabs + low_grabs
        if total_grabs > 3:
            score += weights['liquidity_grabs'] * min(1, total_grabs / 6)

        # Whipsaws (rapid reversals)
        price_changes = df['close'].pct_change()
        reversals = ((price_changes > 0.01) & (price_changes.shift(-1) < -0.01)).sum()
        if reversals > 5:
            score += weights['whipsaws'] * min(1, reversals / 10)

        # False breakouts
        false_breaks = 0
        for i in range(-30, -2):
            if df['high'].iloc[i] > df['high'].iloc[i-5:i].max() * 1.01:
                if df['close'].iloc[i+1] < df['close'].iloc[i]:
                    false_breaks += 1

        if false_breaks > 2:
            score += weights['false_breakouts'] * min(1, false_breaks / 5)

        # Volume anomalies
        volume_spikes = (indicators['volume_ratio'].iloc[-30:] > 2.0).sum()
        if volume_spikes > 3:
            score += weights['volume_anomalies'] * min(1, volume_spikes / 6)

        return min(1.0, score)

    def _detect_distribution(self, df: pd.DataFrame, indicators: Dict[str, pd.Series]) -> float:
        """
        Detect distribution phase characteristics
        - High volume on down moves
        - Lower highs pattern
        - Smart money distributing positions
        """
        score = 0.0
        weights = {
            'volume_pattern': 0.25,
            'price_weakness': 0.25,
            'lower_highs': 0.20,
            'order_blocks': 0.15,
            'selling_pressure': 0.15
        }

        # Volume pattern (increasing on down moves)
        price_change = df['close'].pct_change()
        volume_correlation = price_change.iloc[-30:].corr(indicators['volume_ratio'].iloc[-30:])
        if volume_correlation < -0.3:
            score += weights['volume_pattern'] * min(1, abs(volume_correlation) / 0.5)

        # Price weakness
        trend_slope = indicators['trend_slope'].iloc[-20:].mean()
        if trend_slope < 0:
            score += weights['price_weakness'] * min(1, abs(trend_slope) / 0.01)

        # Lower highs pattern
        lower_highs = indicators['higher_highs'].iloc[-20:].mean()
        if lower_highs < 5:
            score += weights['lower_highs'] * (1 - lower_highs / 10)

        # Bearish order blocks
        bearish_blocks = (indicators['order_blocks'].iloc[-30:] < 0).sum()
        if bearish_blocks > 5:
            score += weights['order_blocks'] * min(1, bearish_blocks / 10)

        # Selling pressure
        selling_pressure = indicators['selling_pressure'].iloc[-20:].mean()
        if selling_pressure > 0.55:
            score += weights['selling_pressure'] * min(1, (selling_pressure - 0.5) / 0.3)

        return min(1.0, score)

    def _get_phase_characteristics(
        self,
        phase: str,
        df: pd.DataFrame,
        indicators: Dict[str, pd.Series]
    ) -> Dict[str, float]:
        """Get specific characteristics for detected phase"""
        chars = {}

        if phase == 'accumulation':
            chars['range_compression'] = float(indicators['range_ratio'].iloc[-20:].mean())
            chars['buying_pressure'] = float(indicators['buying_pressure'].iloc[-20:].mean())
            chars['volume_trend'] = float(indicators['volume_trend'].iloc[-20:].mean())
            chars['price_stability'] = float(1 - indicators['atr_ratio'].iloc[-20:].mean())

        elif phase == 'manipulation':
            chars['liquidity_grab_count'] = float(self._count_liquidity_grabs(df, indicators))
            chars['whipsaw_intensity'] = float(self._calculate_whipsaw_intensity(df))
            chars['false_breakout_ratio'] = float(self._calculate_false_breakout_ratio(df))
            chars['volatility_spike'] = float(indicators['atr_ratio'].iloc[-10:].max())

        elif phase == 'distribution':
            chars['selling_pressure'] = float(indicators['selling_pressure'].iloc[-20:].mean())
            chars['volume_divergence'] = float(self._calculate_volume_divergence(df, indicators))
            chars['trend_weakness'] = float(abs(indicators['trend_slope'].iloc[-20:].mean()))
            chars['distribution_days'] = float(self._count_distribution_days(df, indicators))

        return chars

    def _get_phase_signals(
        self,
        phase: str,
        df: pd.DataFrame,
        indicators: Dict[str, pd.Series]
    ) -> List[str]:
        """Get trading signals for detected phase"""
        signals = []

        if phase == 'accumulation':
            # Look for breakout signals
            if df['close'].iloc[-1] > indicators['swing_high'].iloc[-2]:
                signals.append('breakout_imminent')
            if indicators['volume_ratio'].iloc[-1] > 1.5:
                signals.append('volume_confirmation')
            if indicators['order_blocks'].iloc[-5:].sum() > 2:
                signals.append('institutional_buying')

        elif phase == 'manipulation':
            # Look for reversal signals
            if self._is_liquidity_grab(df.iloc[-3:], indicators):
                signals.append('liquidity_grab_detected')
            if self._is_false_breakout(df.iloc[-5:]):
                signals.append('false_breakout_reversal')
            signals.append('avoid_breakout_trades')

        elif phase == 'distribution':
            # Look for short signals
            if df['close'].iloc[-1] < indicators['swing_low'].iloc[-2]:
                signals.append('breakdown_imminent')
            if indicators['volume_ratio'].iloc[-1] > 1.5 and df['close'].iloc[-1] < df['open'].iloc[-1]:
                signals.append('high_volume_selling')
            if indicators['order_blocks'].iloc[-5:].sum() < -2:
                signals.append('institutional_selling')

        return signals

    def _calculate_phase_strength(self, phase: str, indicators: Dict[str, pd.Series]) -> float:
        """Calculate the strength of the detected phase"""
        try:
            if phase == 'accumulation':
                # Strong accumulation: tight range, increasing volume, bullish order flow
                range_score = 1 - min(1, indicators['range_ratio'].iloc[-10:].mean())
                volume_score = min(1, abs(indicators['volume_trend'].iloc[-10:].mean()) / (indicators['volume_ma'].iloc[-1] + 1e-8))
                flow_score = indicators['buying_pressure'].iloc[-10:].mean()
                return float((range_score + volume_score + flow_score) / 3)

            elif phase == 'manipulation':
                # Strong manipulation: high volatility, volume spikes
                volatility_score = min(1, indicators['atr_ratio'].iloc[-10:].mean() - 1) if indicators['atr_ratio'].iloc[-10:].mean() > 1 else 0
                volume_spike_score = (indicators['volume_ratio'].iloc[-10:] > 2).mean()
                whipsaw_score = 0.5  # Default moderate score
                return float((volatility_score + whipsaw_score + volume_spike_score) / 3)

            elif phase == 'distribution':
                # Strong distribution: increasing selling, declining prices, bearish structure
                selling_score = indicators['selling_pressure'].iloc[-10:].mean()
                trend_score = 1 - min(1, (indicators['trend_slope'].iloc[-10:].mean() + 0.01) / 0.02)
                structure_score = 1 - (indicators['higher_highs'].iloc[-10:].mean() / 10)
                return float((selling_score + trend_score + structure_score) / 3)
        except:
            # Return default strength if calculation fails
            return 0.5

        return 0.0

    def _count_liquidity_grabs(self, df: pd.DataFrame, indicators: Dict[str, pd.Series]) -> float:
        """Count number of liquidity grabs"""
        count = 0
        for i in range(-20, -1):
            if self._is_liquidity_grab(df.iloc[i-2:i+1], indicators):
                count += 1
        return count

    def _is_liquidity_grab(self, window: pd.DataFrame, indicators: Dict[str, pd.Series]) -> bool:
        """Check if current window shows a liquidity grab"""
        if len(window) < 3:
            return False

        # Check for sweep of highs/lows followed by reversal
        if window['high'].iloc[1] > window['high'].iloc[0] * 1.005:
            if window['close'].iloc[2] < window['close'].iloc[1]:
                return True

        if window['low'].iloc[1] < window['low'].iloc[0] * 0.995:
            if window['close'].iloc[2] > window['close'].iloc[1]:
                return True

        return False

    def _is_false_breakout(self, window: pd.DataFrame) -> bool:
        """Check if window contains a false breakout"""
        if len(window) < 5:
            return False

        # Breakout followed by immediate reversal
        high_break = window['high'].iloc[2] > window['high'].iloc[:2].max() * 1.005
        low_break = window['low'].iloc[2] < window['low'].iloc[:2].min() * 0.995

        if high_break and window['close'].iloc[-1] < window['close'].iloc[2]:
            return True
        if low_break and window['close'].iloc[-1] > window['close'].iloc[2]:
            return True

        return False

    def _calculate_whipsaw_intensity(self, df: pd.DataFrame) -> float:
        """Calculate intensity of whipsaw movements"""
        if len(df) < 10:
            return 0.0

        price_changes = df['close'].pct_change() if 'close' in df.columns else pd.Series([0])
        direction_changes = (price_changes > 0).astype(int).diff().abs().sum()
        return min(1.0, direction_changes / (len(df) * 0.5))

    def _calculate_false_breakout_ratio(self, df: pd.DataFrame) -> float:
        """Calculate ratio of false breakouts"""
        false_breaks = 0
        total_breaks = 0

        for i in range(5, len(df) - 2):
            # Check for breakouts
            if df['high'].iloc[i] > df['high'].iloc[i-5:i].max() * 1.005:
                total_breaks += 1
                if df['close'].iloc[i+2] < df['close'].iloc[i]:
                    false_breaks += 1

        return false_breaks / max(1, total_breaks)

    def _calculate_volume_divergence(self, df: pd.DataFrame, indicators: Dict[str, pd.Series]) -> float:
        """Calculate volume/price divergence"""
        price_trend = df['close'].iloc[-20:].pct_change().mean()
        volume_trend = indicators['volume_ma'].iloc[-20:].pct_change().mean()

        # Divergence when price up but volume down (or vice versa)
        if price_trend > 0 and volume_trend < 0:
            return abs(price_trend - volume_trend)
        elif price_trend < 0 and volume_trend > 0:
            return abs(price_trend - volume_trend)

        return 0.0

    def _count_distribution_days(self, df: pd.DataFrame, indicators: Dict[str, pd.Series]) -> int:
        """Count distribution days (high volume down days)"""
        count = 0
        for i in range(-20, -1):
            if (df['close'].iloc[i] < df['open'].iloc[i] and
                indicators['volume_ratio'].iloc[i] > 1.2):
                count += 1
        return count

    def get_trading_bias(self, phase: AMDPhase) -> Dict[str, Any]:
        """
        Get trading bias based on detected phase

        Returns:
            Dictionary with trading recommendations
        """
        bias = {
            'phase': phase.phase,
            'direction': 'neutral',
            'confidence': phase.confidence,
            'position_size': 0.5,
            'risk_level': 'medium',
            'strategies': []
        }

        if phase.phase == 'accumulation' and phase.confidence > 0.6:
            bias['direction'] = 'long'
            bias['position_size'] = min(1.0, phase.confidence)
            bias['risk_level'] = 'low'
            bias['strategies'] = [
                'buy_dips',
                'accumulate_position',
                'wait_for_breakout'
            ]

        elif phase.phase == 'manipulation' and phase.confidence > 0.6:
            bias['direction'] = 'neutral'
            bias['position_size'] = 0.3
            bias['risk_level'] = 'high'
            bias['strategies'] = [
                'fade_breakouts',
                'trade_ranges',
                'tight_stops'
            ]

        elif phase.phase == 'distribution' and phase.confidence > 0.6:
            bias['direction'] = 'short'
            bias['position_size'] = min(1.0, phase.confidence)
            bias['risk_level'] = 'medium'
            bias['strategies'] = [
                'sell_rallies',
                'reduce_longs',
                'wait_for_breakdown'
            ]

        return bias
