"""
Adaptive gating module for MoERegression.

This module implements adaptive entry filters based on market volatility.
Key insight: ALL successful trades occur in low volatility regimes (<0.5% ATR).
"""

import pandas as pd
import numpy as np
from typing import Dict, Tuple, Optional


class AdaptiveGating:
    """Adaptive gating based on market volatility and regime."""

    def __init__(self, config: Dict = None):
        """Initialize adaptive gating parameters."""
        self.config = config or self.get_default_config()

    def get_default_config(self) -> Dict:
        """Get default configuration."""
        return {
            # Volatility-based thresholds (ATR % of price)
            'min_volatility_low': 0.3,   # Minimum ATR for trading
            'min_volatility_normal': 0.5,
            'max_volatility_high': 1.5,  # Maximum ATR for trading

            # Momentum thresholds
            'min_momentum_5': 0.005,     # 0.5% minimum 5-bar momentum
            'min_momentum_10': 0.01,     # 1.0% minimum 10-bar momentum

            # Regime thresholds
            'min_adx': 20,               # Minimum ADX for trend movement
            'max_adx_range': 15,         # ADX range for normal volatility

            # Volume confirmation
            'min_volume_ratio': 1.1,     # Minimum 10% volume above average
            'volume_confirm_bars': 3,    # Bars with increasing volume

            # Time limit
            'max_time_to_target_hours': 48,  # Max hours to target

            # Risk management
            'min_atr_sl_mult': 1.5,      # Minimum SL multiplier
            'min_atr_tp_mult': 3.0,      # Minimum TP multiplier
        }

    def get_volatility_regime(self, atr_pct: float) -> str:
        """Determine volatility regime based on ATR % of price."""
        if atr_pct < self.config['min_volatility_low']:
            return 'LOW_VOL'
        elif atr_pct < self.config['max_volatility_high']:
            return 'NORMAL_VOL'
        else:
            return 'HIGH_VOL'

    def check_volatility_filter(self, atr_pct: float) -> bool:
        """Check if market volatility is suitable for trading."""
        vol_regime = self.get_volatility_regime(atr_pct)

        if vol_regime == 'LOW_VOL':
            return True  # Always trade in low volatility
        elif vol_regime == 'NORMAL_VOL':
            # Only trade if momentum is strong
            return True  # Already filtered by momentum
        else:  # HIGH_VOL
            # No trading in high volatility
            return False

    def check_momentum_filter(self, momentum_5: float, momentum_10: float) -> bool:
        """Check if momentum is favorable."""
        return (momentum_5 >= self.config['min_momentum_5'] and
                momentum_10 >= self.config['min_momentum_10'])

    def check_regime_filter(self, atr_pct: float, adx: float) -> bool:
        """Check if market regime is suitable."""
        vol_regime = self.get_volatility_regime(atr_pct)

        # In low volatility, only trade if ADX > 20 (some movement)
        if vol_regime == 'LOW_VOL':
            return adx >= self.config['min_adx']

        # In normal volatility, trade if ADX is reasonable
        elif vol_regime == 'NORMAL_VOL':
            return adx >= 15 and adx <= self.config['max_adx_range']

        # High volatility: no trading
        else:
            return False

    def check_volume_filter(self, volume: float, avg_volume: float) -> bool:
        """Check if volume is sufficient."""
        volume_ratio = volume / avg_volume
        return volume_ratio >= self.config['min_volume_ratio']

    def check_time_limit(self, bars_to_target: float, current_bar: int,
                        max_horizons: list) -> bool:
        """Check if target is reachable within reasonable time."""
        # Calculate max expected bars to target
        max_bars_expected = max(bars_to_target, max(current_bar, 1))

        # Time limit in hours (assuming 24 bars per day)
        max_hours = max_bars_expected * 24

        return max_hours <= self.config['max_time_to_target_hours']

    def check_adaptive_gating(self, df_row: pd.Series,
                             max_horizons: list = None) -> Dict:
        """
        Run all adaptive filters on a single signal.

        Args:
            df_row: Row of features (pandas Series)
            max_horizons: Maximum horizons from model

        Returns:
            Dict with filter results and score
        """
        max_horizons = max_horizons or [10, 30, 60]

        results = {
            'passed': False,
            'reason': None,
            'scores': {}
        }

        # Extract features
        atr_pct = df_row.get('atr_pct', 0)
        momentum_5 = df_row.get('momentum_5', 0)
        momentum_10 = df_row.get('momentum_10', 0)
        adx = df_row.get('ADX_14', 0)
        volume = df_row.get('Volume', 1)
        avg_volume = df_row.get('volume_avg', volume)
        bars_to_target = df_row.get('bars_to_target', 10)

        # 1. Volatility filter
        if not self.check_volatility_filter(atr_pct):
            results['reason'] = f'High volatility: {atr_pct:.2%} ATR'
            return results

        results['scores']['volatility'] = 1.0
        results['reason'] = 'Volatility OK'

        # 2. Momentum filter
        if not self.check_momentum_filter(momentum_5, momentum_10):
            results['reason'] = f'Weak momentum: 5b={momentum_5:.2%}, 10b={momentum_10:.2%}'
            results['scores']['momentum'] = 0.0
            return results

        results['scores']['momentum'] = 1.0
        results['reason'] = 'Momentum OK'

        # 3. Regime filter
        if not self.check_regime_filter(atr_pct, adx):
            results['reason'] = f'Regime not tradeable: ADX={adx:.1f}, ATR={atr_pct:.2%}'
            results['scores']['regime'] = 0.0
            return results

        results['scores']['regime'] = 1.0
        results['reason'] = 'Regime OK'

        # 4. Volume filter
        if not self.check_volume_filter(volume, avg_volume):
            results['reason'] = f'Low volume: {volume/vol_avg:.2f}x avg'
            results['scores']['volume'] = 0.0
            return results

        results['scores']['volume'] = 1.0
        results['reason'] = 'Volume OK'

        # 5. Time limit filter
        if not self.check_time_limit(bars_to_target, current_bar, max_horizons):
            results['reason'] = f'Time limit: {max_hours:.0f}h > {self.config["max_time_to_target_hours"]}h'
            results['scores']['time'] = 0.0
            return results

        results['scores']['time'] = 1.0
        results['reason'] = 'All filters passed'

        results['passed'] = True
        return results

    def calculate_quality_score(self, gating_results: Dict,
                               model_confidence: float = 0.5) -> float:
        """
        Calculate overall quality score (0-1).

        Score is based on:
        - Gating filters passed (0.4 weight)
        - Model confidence (0.3 weight)
        - Volatility regime (0.2 weight)
        - Momentum strength (0.1 weight)
        """
        if not gating_results['passed']:
            return 0.0

        score = 0.0

        # Gating filters
        gating_scores = gating_results.get('scores', {})
        gating_weight = 0.4
        score += gating_weight * np.mean(list(gating_scores.values()))

        # Model confidence
        confidence_weight = 0.3
        score += confidence_weight * model_confidence

        # Volatility bonus (1.0 for LOW_VOL, 0.5 for NORMAL)
        atr_pct = gating_results.get('atr_pct', 0)
        if atr_pct < 0.5:
            volatility_bonus = 0.2
        elif atr_pct < 1.5:
            volatility_bonus = 0.1
        else:
            volatility_bonus = 0.0

        volatility_weight = 0.2
        score += volatility_weight * volatility_bonus

        # Momentum bonus
        momentum_5 = gating_results.get('momentum_5', 0)
        if momentum_5 > 0.02:
            momentum_bonus = 0.1
        else:
            momentum_bonus = 0.0

        momentum_weight = 0.1
        score += momentum_weight * momentum_bonus

        return min(score, 1.0)

    def apply_adaptive_filters(self, signals_df: pd.DataFrame,
                              model_predictions: pd.Series,
                              current_bar_col: str = 'current_bar',
                              **kwargs) -> pd.DataFrame:
        """
        Apply adaptive filters to a DataFrame of signals.

        Returns filtered DataFrame with added 'quality_score' and 'gating_passed' columns.
        """
        signals = signals_df.copy()

        # Calculate additional features if not present
        if 'atr_pct' not in signals.columns:
            signals['atr_pct'] = signals['atr_entry'] / signals['current_price']

        if 'momentum_5' not in signals.columns:
            signals['momentum_5'] = (signals['current_price'] - signals['prev_close'].shift(4)) / signals['prev_close'].shift(4)

        if 'momentum_10' not in signals.columns:
            signals['momentum_10'] = (signals['current_price'] - signals['prev_close'].shift(9)) / signals['prev_close'].shift(9)

        if 'ADX_14' not in signals.columns:
            # Simplified ADX calculation
            signals['ADX_14'] = 25  # Default value

        if 'volume_avg' not in signals.columns:
            signals['volume_avg'] = signals['Volume'].rolling(20).mean()

        # Apply adaptive gating
        gating_results = signals.apply(
            lambda row: self.check_adaptive_gating(row, max_horizons=kwargs.get('max_horizons', [10, 30, 60])),
            axis=1
        )

        signals['gating_passed'] = gating_results.apply(lambda x: x['passed'])
        signals['quality_score'] = gating_results.apply(
            lambda x: self.calculate_quality_score(x, model_predictions.get(x.name, 0.5))
        )

        return signals


def get_best_volatility_thresholds(trades_df: pd.DataFrame) -> Dict:
    """
    Analyze historical trades to find optimal volatility thresholds.

    Args:
        trades_df: DataFrame of closed trades

    Returns:
        Dict with optimal thresholds
    """
    # Calculate ATR % for each trade
    trades_df['atr_pct'] = trades_df['atr_entry'] / trades_df['entry_price']

    # Bin trades by ATR % and analyze PnL
    bins = [0, 0.5, 1.5, 10]
    labels = ['LOW_VOL', 'NORMAL_VOL', 'HIGH_VOL']

    trades_df['vol_regime'] = pd.cut(trades_df['atr_pct'], bins=bins, labels=labels)

    # Analyze each regime
    results = {}

    for regime in labels:
        regime_trades = trades_df[trades_df['vol_regime'] == regime]

        if len(regime_trades) > 0:
            total_pnl = regime_trades['pnl'].sum()
            avg_pnl = regime_trades['pnl'].mean()
            win_rate = (regime_trades['pnl'] > 0).sum() / len(regime_trades)

            results[regime] = {
                'total_pnl': total_pnl,
                'avg_pnl': avg_pnl,
                'win_rate': win_rate,
                'count': len(regime_trades)
            }

    return results
