"""
Strategy factory for creating strategy instances with their parameters.

Centralizes strategy creation and parameter configuration.
"""
from dataclasses import dataclass, field
from typing import Any

import numpy as np

from strategies.base import BaseStrategy


@dataclass
class StrategyConfig:
    """
    Configuration for a strategy including default and grid parameters.
    
    Attributes:
        strategy_class: The strategy class to instantiate
        default_params: Parameters for single backtest runs
        grid_params: Parameters for grid search optimization
    """
    strategy_class: type[BaseStrategy]
    default_params: dict[str, Any] = field(default_factory=dict)
    grid_params: dict[str, Any] = field(default_factory=dict)


def _build_registry() -> dict[str, StrategyConfig]:
    """
    Build the strategy registry lazily to avoid circular imports.
    
    Returns:
        Dictionary mapping strategy names to their configurations
    """
    # Import here to avoid circular imports
    from strategies.examples import MaCrossStrategy, RsiStrategy
    from strategies.supertrend import MetaSupertrendStrategy
    from strategies.regime_strategy import RegimeReversionStrategy
    from strategies.multi_pair import MultiPairDivergenceStrategy, MultiPairConfig
    
    return {
        "rsi": StrategyConfig(
            strategy_class=RsiStrategy,
            default_params={
                'period': 14, 
                'rsi_lower': 30, 
                'rsi_upper': 70
            },
            grid_params={
                'period': np.arange(10, 25, 2),
                'rsi_lower': [20, 30, 40],
                'rsi_upper': [60, 70, 80]
            }
        ),
        "macross": StrategyConfig(
            strategy_class=MaCrossStrategy,
            default_params={
                'fast_window': 10, 
                'slow_window': 20
            },
            grid_params={
                'fast_window': np.arange(5, 20, 5),
                'slow_window': np.arange(20, 60, 10)
            }
        ),
        "meta_st": StrategyConfig(
            strategy_class=MetaSupertrendStrategy,
            default_params={
                'period1': 12, 'multiplier1': 3.0,
                'period2': 10, 'multiplier2': 1.0,
                'period3': 11, 'multiplier3': 2.0
            },
            grid_params={
                'multiplier1': [2.0, 3.0, 4.0],
                'period1': [10, 12, 14],
                'period2': 11, 'multiplier2': 2.0,
                'period3': 12, 'multiplier3': 1.0
            }
        ),
        "regime": StrategyConfig(
            strategy_class=RegimeReversionStrategy,
            default_params={
                # Optimal from walk-forward research (research/horizon_optimization_results.csv)
                'horizon': 102,           # 4.25 days - best Net PnL
                'z_window': 24,           # 24h rolling Z-score window
                'z_entry_threshold': 1.0, # Enter when |Z| > 1.0
                'profit_target': 0.005,   # 0.5% target for ML labels
                'stop_loss': 0.06,        # 6% stop loss
                'take_profit': 0.05,      # 5% take profit
                'train_ratio': 0.7,       # 70% train / 30% test
                'trend_window': 0,        # Disabled SMA filter
                'use_funding_filter': True, # Enabled Funding filter
                'funding_threshold': 0.005  # 0.005% threshold (Proven profitable)
            },
            grid_params={
                'horizon': [84, 96, 102, 108, 120],
                'z_entry_threshold': [0.8, 1.0, 1.2],
                'stop_loss': [0.04, 0.06, 0.08],
                'funding_threshold': [0.005, 0.01, 0.02]
            }
        ),
        "multi_pair": StrategyConfig(
            strategy_class=MultiPairDivergenceStrategy,
            default_params={
                # Multi-pair divergence strategy uses config object
                # Parameters passed here will override MultiPairConfig defaults
            },
            grid_params={
                'z_entry_threshold': [0.8, 1.0, 1.2],
                'prob_threshold': [0.4, 0.5, 0.6],
                'correlation_threshold': [0.75, 0.85, 0.95]
            }
        )
    }


# Module-level cache for the registry
_REGISTRY_CACHE: dict[str, StrategyConfig] | None = None


def get_registry() -> dict[str, StrategyConfig]:
    """Get the strategy registry, building it on first access."""
    global _REGISTRY_CACHE
    if _REGISTRY_CACHE is None:
        _REGISTRY_CACHE = _build_registry()
    return _REGISTRY_CACHE


def get_strategy_names() -> list[str]:
    """
    Get list of available strategy names.
    
    Returns:
        List of strategy name strings
    """
    return list(get_registry().keys())


def get_strategy(name: str, is_grid: bool = False) -> tuple[BaseStrategy, dict[str, Any]]:
    """
    Create a strategy instance with appropriate parameters.
    
    Args:
        name: Strategy identifier (e.g., 'rsi', 'macross', 'meta_st')
        is_grid: If True, return grid search parameters
        
    Returns:
        Tuple of (strategy instance, parameters dict)
        
    Raises:
        KeyError: If strategy name is not found in registry
    """
    registry = get_registry()
    
    if name not in registry:
        available = ", ".join(registry.keys())
        raise KeyError(f"Unknown strategy '{name}'. Available: {available}")
    
    config = registry[name]
    strategy = config.strategy_class()
    params = config.grid_params if is_grid else config.default_params
    
    return strategy, params.copy()
