"""Strategy grouping for improved ML accuracy.

Groups 14 individual strategies into 5 logical groups to reduce classification
complexity and improve model accuracy.
"""

from typing import Dict, List, Optional, Tuple
from enum import Enum

from src.core.logger import get_logger

logger = get_logger(__name__)


class StrategyGroup(str, Enum):
    """Strategy group classifications."""
    TREND_FOLLOWING = "trend_following"
    MEAN_REVERSION = "mean_reversion"
    MOMENTUM = "momentum"
    MARKET_MAKING = "market_making"
    SENTIMENT_BASED = "sentiment_based"


# Map each strategy to its group
STRATEGY_TO_GROUP: Dict[str, StrategyGroup] = {
    # Trend Following: Follow established trends
    "moving_average": StrategyGroup.TREND_FOLLOWING,
    "macd": StrategyGroup.TREND_FOLLOWING,
    "confirmed": StrategyGroup.TREND_FOLLOWING,
    
    # Mean Reversion: Bet on price returning to mean
    "rsi": StrategyGroup.MEAN_REVERSION,
    "bollinger_mean_reversion": StrategyGroup.MEAN_REVERSION,
    "grid": StrategyGroup.MEAN_REVERSION,
    "divergence": StrategyGroup.MEAN_REVERSION,
    
    # Momentum: Capture fast price moves
    "momentum": StrategyGroup.MOMENTUM,
    "volatility_breakout": StrategyGroup.MOMENTUM,
    
    # Market Making: Profit from bid-ask spread
    "market_making": StrategyGroup.MARKET_MAKING,
    "dca": StrategyGroup.MARKET_MAKING,
    
    # Sentiment Based: Use external signals
    "sentiment": StrategyGroup.SENTIMENT_BASED,
    "pairs_trading": StrategyGroup.SENTIMENT_BASED,
    "consensus": StrategyGroup.SENTIMENT_BASED,
}

# Reverse mapping: group -> list of strategies
GROUP_TO_STRATEGIES: Dict[StrategyGroup, List[str]] = {}
for strategy, group in STRATEGY_TO_GROUP.items():
    if group not in GROUP_TO_STRATEGIES:
        GROUP_TO_STRATEGIES[group] = []
    GROUP_TO_STRATEGIES[group].append(strategy)


def get_strategy_group(strategy_name: str) -> Optional[StrategyGroup]:
    """Get the group a strategy belongs to.
    
    Args:
        strategy_name: Name of the strategy (case-insensitive)
        
    Returns:
        StrategyGroup or None if strategy not found
    """
    return STRATEGY_TO_GROUP.get(strategy_name.lower())


def get_strategies_in_group(group: StrategyGroup) -> List[str]:
    """Get all strategies belonging to a group.
    
    Args:
        group: Strategy group
        
    Returns:
        List of strategy names in the group
    """
    return GROUP_TO_STRATEGIES.get(group, [])


def get_all_groups() -> List[StrategyGroup]:
    """Get all available strategy groups.
    
    Returns:
        List of all strategy groups
    """
    return list(StrategyGroup)


def get_best_strategy_in_group(
    group: StrategyGroup,
    market_features: Dict[str, float],
    available_strategies: Optional[List[str]] = None
) -> Tuple[str, float]:
    """Select the best individual strategy within a group based on market conditions.
    
    Uses rule-based heuristics to pick the optimal strategy when the ML model
    has predicted a group.
    
    Args:
        group: The predicted strategy group
        market_features: Current market condition features
        available_strategies: Optional list of available strategies (filters choices)
        
    Returns:
        Tuple of (best_strategy_name, confidence_score)
    """
    strategies = get_strategies_in_group(group)
    
    # Filter by availability if provided
    if available_strategies:
        strategies = [s for s in strategies if s in available_strategies]
    
    if not strategies:
        # Fallback if no strategies available in group
        logger.warning(f"No strategies available in group {group}")
        return ("rsi", 0.5)  # Safe default
    
    # If only one option, return it
    if len(strategies) == 1:
        return (strategies[0], 0.7)
    
    # Rule-based selection within group based on market features
    rsi = market_features.get("rsi", 50.0)
    adx = market_features.get("adx", 25.0)
    volatility = market_features.get("atr_percent", 2.0)
    volume_ratio = market_features.get("volume_ratio", 1.0)
    
    if group == StrategyGroup.TREND_FOLLOWING:
        # Strong trends: use confirmed, moderate: moving_average, diverging: macd
        if adx > 30:
            return ("confirmed", 0.75) if "confirmed" in strategies else ("moving_average", 0.7)
        elif adx > 20:
            return ("moving_average", 0.7) if "moving_average" in strategies else ("macd", 0.65)
        else:
            return ("macd", 0.6) if "macd" in strategies else (strategies[0], 0.55)
    
    elif group == StrategyGroup.MEAN_REVERSION:
        # Extreme RSI: rsi strategy, tight range: bollinger, low vol: grid
        if rsi < 30 or rsi > 70:
            return ("rsi", 0.75) if "rsi" in strategies else ("bollinger_mean_reversion", 0.7)
        elif volatility < 1.5:
            return ("grid", 0.7) if "grid" in strategies else ("bollinger_mean_reversion", 0.65)
        else:
            return ("bollinger_mean_reversion", 0.65) if "bollinger_mean_reversion" in strategies else (strategies[0], 0.6)
    
    elif group == StrategyGroup.MOMENTUM:
        # High volume spike: volatility_breakout, otherwise momentum
        if volume_ratio > 1.5:
            return ("volatility_breakout", 0.75) if "volatility_breakout" in strategies else ("momentum", 0.7)
        else:
            return ("momentum", 0.7) if "momentum" in strategies else (strategies[0], 0.65)
    
    elif group == StrategyGroup.MARKET_MAKING:
        # Low volatility: market_making, otherwise dca
        if volatility < 2.0:
            return ("market_making", 0.7) if "market_making" in strategies else ("dca", 0.65)
        else:
            return ("dca", 0.7) if "dca" in strategies else (strategies[0], 0.6)
    
    elif group == StrategyGroup.SENTIMENT_BASED:
        # Default to sentiment, fall back to consensus
        if "sentiment" in strategies:
            return ("sentiment", 0.65)
        elif "consensus" in strategies:
            return ("consensus", 0.6)
        else:
            return (strategies[0], 0.55)
    
    # Fallback
    return (strategies[0], 0.5)


def convert_strategy_to_group_label(strategy_name: str) -> str:
    """Convert a strategy name to its group label for ML training.
    
    Args:
        strategy_name: Individual strategy name
        
    Returns:
        Group label string (e.g., "trend_following")
    """
    group = get_strategy_group(strategy_name)
    if group:
        return group.value
    else:
        logger.warning(f"Strategy '{strategy_name}' not in any group, using as-is")
        return strategy_name
