"""
Prediction Service
==================

Service that orchestrates ML predictions using real market data.
Connects Data Service, Feature Engineering, and ML Models.
"""

import os
import asyncio
from datetime import datetime, timedelta
from typing import Optional, List, Dict, Any, Tuple
from dataclasses import dataclass, asdict
from enum import Enum
import uuid
import pandas as pd
import numpy as np
from pathlib import Path
from loguru import logger

# Feature flags y configuración centralizada
from ..config.feature_flags import FeatureFlags
from ..training.symbol_timeframe_trainer import SymbolTimeframeTrainer, SYMBOL_CONFIGS

# Data imports
from ..data.data_service_client import (
    DataServiceManager,
    DataServiceClient,
    Timeframe
)
from ..data.features import FeatureEngineer
from ..data.indicators import TechnicalIndicators

# Attention provider for Level 0 features
from .attention_provider import AttentionProvider, get_attention_provider

# Hierarchical Pipeline for L0→L1→L2 predictions
from ..pipelines.hierarchical_pipeline import (
    HierarchicalPipeline,
    PipelineConfig,
    PredictionResult as HierarchicalPredictionResult
)

# Backtesting engine
from ..backtesting.rr_backtester import RRBacktester, BacktestConfig, BacktestResult
from ..backtesting.metrics import TradingMetrics, TradeRecord

# Signal store for historical queries
from .signal_store import SignalStore, StoredSignal, get_signal_store


class Direction(Enum):
    LONG = "long"
    SHORT = "short"
    NEUTRAL = "neutral"


class AMDPhase(Enum):
    ACCUMULATION = "accumulation"
    MANIPULATION = "manipulation"
    DISTRIBUTION = "distribution"
    UNKNOWN = "unknown"


class VolatilityRegime(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
    EXTREME = "extreme"


@dataclass
class RangePrediction:
    """Range prediction result"""
    horizon: str
    delta_high: float
    delta_low: float
    delta_high_bin: Optional[int]
    delta_low_bin: Optional[int]
    confidence_high: float
    confidence_low: float


@dataclass
class TPSLPrediction:
    """TP/SL classification result"""
    prob_tp_first: float
    rr_config: str
    confidence: float
    calibrated: bool


@dataclass
class AttentionInfo:
    """Attention score information from Level 0 models"""
    attention_score: float  # 1.0 = average, >2.0 = high flow
    flow_class: int  # 0=low, 1=medium, 2=high
    flow_label: str  # 'low_flow', 'medium_flow', 'high_flow'
    is_high_flow: bool  # True if flow_class == 2
    is_tradeable: bool  # True if attention_score >= 1.0


@dataclass
class HierarchicalResult:
    """Result from hierarchical L0→L1→L2 pipeline"""
    symbol: str
    timestamp: datetime
    # Level 0 outputs
    attention_score_5m: float
    attention_score_15m: float
    attention_class_5m: int
    attention_class_15m: int
    # Level 1 outputs
    pred_high_5m: float
    pred_low_5m: float
    pred_high_15m: float
    pred_low_15m: float
    # Level 2 outputs (final)
    delta_high_final: float
    delta_low_final: float
    confidence: bool
    confidence_proba: float
    # Trading signals
    should_trade: bool
    trade_quality: str  # 'high', 'medium', 'low', 'skip'


@dataclass
class BacktestSummary:
    """Summary of backtest results"""
    total_trades: int
    winning_trades: int
    losing_trades: int
    winrate: float
    net_profit: float
    profit_factor: float
    max_drawdown: float
    max_drawdown_pct: float
    sharpe_ratio: float
    sortino_ratio: float
    signals_generated: int
    signals_filtered: int
    signals_traded: int
    avg_trade_duration: float
    metrics_by_rr: Dict[str, Dict] = None
    metrics_by_amd: Dict[str, Dict] = None


@dataclass
class TradingSignal:
    """Complete trading signal"""
    signal_id: str
    symbol: str
    direction: Direction
    entry_price: float
    stop_loss: float
    take_profit: float
    risk_reward_ratio: float
    prob_tp_first: float
    confidence_score: float
    amd_phase: AMDPhase
    volatility_regime: VolatilityRegime
    range_prediction: RangePrediction
    timestamp: datetime
    valid_until: datetime
    attention: Optional[AttentionInfo] = None  # Level 0 attention info
    metadata: Optional[Dict[str, Any]] = None


@dataclass
class AMDDetection:
    """AMD phase detection result"""
    phase: AMDPhase
    confidence: float
    start_time: datetime
    characteristics: Dict[str, float]
    signals: List[str]
    strength: float
    trading_bias: Dict[str, Any]


class PredictionService:
    """
    Main prediction service.

    Orchestrates:
    - Data fetching from Data Service
    - Feature engineering
    - Model inference
    - Signal generation
    """

    def __init__(
        self,
        data_service_url: Optional[str] = None,
        models_dir: str = "models"
    ):
        """
        Initialize prediction service.

        Args:
            data_service_url: URL of Data Service
            models_dir: Directory containing trained models
        """
        self.data_manager = DataServiceManager(
            DataServiceClient(base_url=data_service_url)
        )
        self.models_dir = models_dir
        self.feature_engineer = FeatureEngineer()
        self.indicators = TechnicalIndicators()

        # Model instances (loaded on demand)
        self._range_predictor = None
        self._tpsl_classifier = None
        self._amd_detector = None
        self._attention_provider = None  # Level 0 attention models
        self._hierarchical_pipeline = None  # L0→L1→L2 pipeline
        self._backtester = None  # Backtesting engine
        self._signal_store = None  # Historical signal store
        self._models_loaded = False

        # Symbol-specific trainers (nuevos modelos por símbolo/timeframe)
        self._symbol_trainers: Dict[str, SymbolTimeframeTrainer] = {}

        # Supported configurations
        self.supported_symbols = ["XAUUSD", "EURUSD", "GBPUSD", "BTCUSD", "ETHUSD"]
        self.supported_horizons = ["15m", "1h", "4h"]
        self.supported_rr_configs = ["rr_2_1", "rr_3_1"]

    async def initialize(self):
        """Load models and prepare service"""
        logger.info("Initializing PredictionService...")

        # Try to load models
        await self._load_models()

        logger.info("PredictionService initialized")

    async def _load_models(self):
        """Load ML models from disk"""
        try:
            # Import model classes
            from ..models.range_predictor import RangePredictor
            from ..models.tp_sl_classifier import TPSLClassifier
            from ..models.amd_detector import AMDDetector

            # Load Range Predictor
            range_path = os.path.join(self.models_dir, "range_predictor")
            if os.path.exists(range_path):
                self._range_predictor = RangePredictor()
                self._range_predictor.load(range_path)
                logger.info("✅ RangePredictor loaded")

            # Load TPSL Classifier
            tpsl_path = os.path.join(self.models_dir, "tpsl_classifier")
            if os.path.exists(tpsl_path):
                self._tpsl_classifier = TPSLClassifier()
                self._tpsl_classifier.load(tpsl_path)
                logger.info("✅ TPSLClassifier loaded")

            # Initialize AMD Detector (doesn't need pre-trained weights)
            self._amd_detector = AMDDetector()
            logger.info("✅ AMDDetector initialized")

            # Load Attention Provider (Level 0 models)
            attention_path = os.path.join(self.models_dir, "attention")
            if os.path.exists(attention_path):
                self._attention_provider = AttentionProvider(attention_path)
                if self._attention_provider.load():
                    logger.info(f"✅ AttentionProvider loaded ({len(self._attention_provider.models)} models)")
                else:
                    logger.warning("AttentionProvider: No models loaded")
            else:
                logger.info("No attention models directory found, creating provider anyway")
                self._attention_provider = AttentionProvider(attention_path)

            # Load Hierarchical Pipeline (L0→L1→L2)
            try:
                pipeline_config = PipelineConfig(
                    attention_model_path=os.path.join(self.models_dir, "attention"),
                    base_model_path=os.path.join(self.models_dir, "symbol_timeframe_models"),
                    metamodel_path=os.path.join(self.models_dir, "metamodels"),
                    neural_gating_path=os.path.join(self.models_dir, "metamodels_neural")
                )
                self._hierarchical_pipeline = HierarchicalPipeline(pipeline_config)
                logger.info("✅ HierarchicalPipeline initialized")
            except Exception as e:
                logger.warning(f"HierarchicalPipeline initialization failed: {e}")
                self._hierarchical_pipeline = None

            # Initialize Backtesting Engine
            try:
                self._backtester = RRBacktester(BacktestConfig())
                logger.info("✅ RRBacktester initialized")
            except Exception as e:
                logger.warning(f"RRBacktester initialization failed: {e}")
                self._backtester = None

            # Initialize Signal Store for historical queries
            try:
                persistence_dir = os.path.join(self.models_dir, "..", "logs", "signals")
                self._signal_store = get_signal_store(
                    max_signals=10000,
                    retention_hours=168,  # 7 days
                    persistence_dir=persistence_dir
                )
                logger.info("✅ SignalStore initialized")
            except Exception as e:
                logger.warning(f"SignalStore initialization failed: {e}")
                self._signal_store = None

            self._models_loaded = True

            # Cargar modelos por símbolo si el feature flag está activo
            if FeatureFlags.USE_SYMBOL_TRAINERS:
                self._load_symbol_trainers()

        except ImportError as e:
            logger.warning(f"Model import failed: {e}")
            self._models_loaded = False
        except Exception as e:
            logger.error(f"Model loading failed: {e}")
            self._models_loaded = False

    def _load_symbol_trainers(self):
        """
        Cargar modelos entrenados por símbolo desde múltiples directorios.

        Busca en los siguientes directorios (en orden de prioridad):
        1. models/symbol_timeframe_models/ (nuevos modelos con normalización ATR)
        2. models/ml_first/ (modelos legacy)

        Estructura esperada:
        models/symbol_timeframe_models/
        ├── XAUUSD_5m_high_h3.joblib
        ├── XAUUSD_5m_low_h3.joblib
        ├── trainer_metadata.joblib
        └── ...
        """
        # Lista de directorios a buscar (en orden de prioridad)
        model_dirs = [
            Path(self.models_dir) / 'symbol_timeframe_models',  # Nuevos modelos
            Path(self.models_dir) / 'ml_first',  # Modelos legacy
        ]

        loaded_count = 0

        for model_path in model_dirs:
            if not model_path.exists():
                logger.debug(f"Model directory not found: {model_path}")
                continue

            logger.info(f"Loading models from {model_path}")

            # Check if it's a flat structure (symbol_timeframe_models) or hierarchical (ml_first)
            metadata_path = model_path / 'trainer_metadata.joblib'

            if metadata_path.exists():
                # Flat structure: Load single trainer with all models
                try:
                    trainer = SymbolTimeframeTrainer()
                    trainer.load(str(model_path))

                    # Register trainer for all symbols it contains
                    for key in trainer.models.keys():
                        symbol = key.split('_')[0]
                        if symbol not in self._symbol_trainers:
                            self._symbol_trainers[symbol] = trainer
                            loaded_count += 1
                            logger.info(f"✅ Loaded symbol trainer for {symbol} from {model_path.name}")

                    # If we loaded models successfully, stop searching
                    if loaded_count > 0:
                        logger.info(f"Loaded {loaded_count} symbol trainers from {model_path}")
                        return

                except Exception as e:
                    logger.warning(f"Failed to load trainer from {model_path}: {e}")
                    continue
            else:
                # Hierarchical structure: Load from symbol subdirectories
                for symbol_dir in model_path.iterdir():
                    if not symbol_dir.is_dir():
                        continue

                    symbol = symbol_dir.name
                    if symbol not in SYMBOL_CONFIGS:
                        logger.debug(f"Skipping unknown symbol: {symbol}")
                        continue

                    if symbol in self._symbol_trainers:
                        # Already loaded from higher priority directory
                        continue

                    try:
                        trainer = SymbolTimeframeTrainer()
                        trainer.load(str(symbol_dir))
                        self._symbol_trainers[symbol] = trainer
                        loaded_count += 1
                        logger.info(f"✅ Loaded symbol trainer for {symbol} from {model_path.name}")
                    except Exception as e:
                        logger.warning(f"Failed to load trainer for {symbol}: {e}")

        if loaded_count == 0:
            logger.warning("No symbol-specific trainers loaded - using heuristic predictions")
        else:
            logger.info(f"Total loaded: {loaded_count} symbol-specific trainers")

    @property
    def models_loaded(self) -> bool:
        return self._models_loaded

    async def get_market_data(
        self,
        symbol: str,
        timeframe: str = "15m",
        lookback_periods: int = 500
    ) -> pd.DataFrame:
        """
        Get market data with features.

        Args:
            symbol: Trading symbol
            timeframe: Timeframe string
            lookback_periods: Number of periods

        Returns:
            DataFrame with OHLCV and features
        """
        tf = Timeframe(timeframe)

        async with self.data_manager.client:
            df = await self.data_manager.get_ml_features_data(
                symbol=symbol,
                timeframe=tf,
                lookback_periods=lookback_periods
            )

        if df.empty:
            logger.warning(f"No data available for {symbol}")
            return df

        # Add technical indicators
        df = self.indicators.add_all_indicators(df)

        return df

    async def predict_range(
        self,
        symbol: str,
        timeframe: str = "15m",
        horizons: Optional[List[str]] = None
    ) -> List[RangePrediction]:
        """
        Predict price ranges.

        Args:
            symbol: Trading symbol
            timeframe: Analysis timeframe
            horizons: Prediction horizons

        Returns:
            List of range predictions
        """
        horizons = horizons or self.supported_horizons[:2]

        # Get market data
        df = await self.get_market_data(symbol, timeframe)

        if df.empty:
            # Return default predictions
            return self._default_range_predictions(horizons)

        predictions = []

        for horizon in horizons:
            # Generate features
            features = self.feature_engineer.create_features(df)
            prediction_made = False

            # Prioridad 1: Usar symbol-specific trainer si está disponible
            if FeatureFlags.USE_SYMBOL_TRAINERS and symbol in self._symbol_trainers:
                try:
                    trainer = self._symbol_trainers[symbol]
                    pred = trainer.predict(df, symbol, timeframe)
                    logger.debug(f"Using symbol-specific trainer for {symbol}")
                    predictions.append(RangePrediction(
                        horizon=horizon,
                        delta_high=pred.get("delta_high", 0),
                        delta_low=pred.get("delta_low", 0),
                        delta_high_bin=pred.get("delta_high_bin"),
                        delta_low_bin=pred.get("delta_low_bin"),
                        confidence_high=pred.get("confidence_high", 0.7),
                        confidence_low=pred.get("confidence_low", 0.7)
                    ))
                    prediction_made = True
                except Exception as e:
                    logger.warning(f"Symbol trainer failed for {symbol}, falling back to legacy: {e}")

            # Prioridad 2: Usar modelo legacy
            if not prediction_made and self._range_predictor:
                # Use trained model
                pred = self._range_predictor.predict(features, horizon)
                predictions.append(RangePrediction(
                    horizon=horizon,
                    delta_high=pred.get("delta_high", 0),
                    delta_low=pred.get("delta_low", 0),
                    delta_high_bin=pred.get("delta_high_bin"),
                    delta_low_bin=pred.get("delta_low_bin"),
                    confidence_high=pred.get("confidence_high", 0.5),
                    confidence_low=pred.get("confidence_low", 0.5)
                ))
                prediction_made = True

            # Prioridad 3: Heurística basada en ATR
            if not prediction_made:
                # Heuristic-based prediction using ATR
                atr = df['atr'].iloc[-1] if 'atr' in df.columns else df['high'].iloc[-1] - df['low'].iloc[-1]
                multiplier = {"15m": 1.0, "1h": 1.5, "4h": 2.5}.get(horizon, 1.0)

                predictions.append(RangePrediction(
                    horizon=horizon,
                    delta_high=float(atr * multiplier * 0.8),
                    delta_low=float(atr * multiplier * 0.6),
                    delta_high_bin=None,
                    delta_low_bin=None,
                    confidence_high=0.6,
                    confidence_low=0.55
                ))

        return predictions

    async def predict_tpsl(
        self,
        symbol: str,
        timeframe: str = "15m",
        rr_config: str = "rr_2_1"
    ) -> TPSLPrediction:
        """
        Predict TP/SL probability.

        Args:
            symbol: Trading symbol
            timeframe: Analysis timeframe
            rr_config: Risk/Reward configuration

        Returns:
            TP/SL prediction
        """
        df = await self.get_market_data(symbol, timeframe)

        if df.empty or not self._tpsl_classifier:
            # Heuristic based on trend
            if not df.empty:
                sma_short = df['close'].rolling(10).mean().iloc[-1]
                sma_long = df['close'].rolling(20).mean().iloc[-1]
                trend_strength = (sma_short - sma_long) / sma_long

                prob = 0.5 + (trend_strength * 10)  # Adjust based on trend
                prob = max(0.3, min(0.7, prob))
            else:
                prob = 0.5

            return TPSLPrediction(
                prob_tp_first=prob,
                rr_config=rr_config,
                confidence=0.5,
                calibrated=False
            )

        # Use trained model
        features = self.feature_engineer.create_features(df)
        pred = self._tpsl_classifier.predict(features, rr_config)

        return TPSLPrediction(
            prob_tp_first=pred.get("prob_tp_first", 0.5),
            rr_config=rr_config,
            confidence=pred.get("confidence", 0.5),
            calibrated=pred.get("calibrated", False)
        )

    async def detect_amd_phase(
        self,
        symbol: str,
        timeframe: str = "15m",
        lookback_periods: int = 100
    ) -> AMDDetection:
        """
        Detect AMD phase.

        Args:
            symbol: Trading symbol
            timeframe: Analysis timeframe
            lookback_periods: Periods for analysis

        Returns:
            AMD phase detection
        """
        df = await self.get_market_data(symbol, timeframe, lookback_periods)

        if df.empty:
            return self._default_amd_detection()

        if self._amd_detector:
            # Use AMD detector
            detection = self._amd_detector.detect_phase(df)
            bias = self._amd_detector.get_trading_bias(detection.get("phase", "unknown"))

            return AMDDetection(
                phase=AMDPhase(detection.get("phase", "unknown")),
                confidence=detection.get("confidence", 0.5),
                start_time=datetime.utcnow(),
                characteristics=detection.get("characteristics", {}),
                signals=detection.get("signals", []),
                strength=detection.get("strength", 0.5),
                trading_bias=bias
            )

        # Heuristic AMD detection
        return self._heuristic_amd_detection(df)

    async def get_attention_info(
        self,
        symbol: str,
        timeframe: str = "5m"
    ) -> Optional[AttentionInfo]:
        """
        Get attention score info from Level 0 models.

        Args:
            symbol: Trading symbol
            timeframe: Timeframe (5m or 15m)

        Returns:
            AttentionInfo or None if not available
        """
        if not self._attention_provider or not self._attention_provider.is_loaded:
            return None

        # Get data for attention calculation
        df = await self.get_market_data(symbol, timeframe, lookback_periods=250)

        if df.empty:
            return None

        try:
            attention_dict = self._attention_provider.get_latest_attention(
                df, symbol, timeframe
            )

            return AttentionInfo(
                attention_score=attention_dict['attention_score'],
                flow_class=attention_dict['flow_class'],
                flow_label=attention_dict['flow_label'],
                is_high_flow=attention_dict['is_high_flow'],
                is_tradeable=attention_dict['is_tradeable']
            )
        except Exception as e:
            logger.warning(f"Failed to get attention info for {symbol}/{timeframe}: {e}")
            return None

    async def should_trade(
        self,
        symbol: str,
        timeframe: str = "5m",
        min_attention: float = 1.0
    ) -> Tuple[bool, Optional[AttentionInfo]]:
        """
        Check if current market conditions are favorable for trading.

        Uses Level 0 attention models to determine if market has enough flow.

        Args:
            symbol: Trading symbol
            timeframe: Timeframe
            min_attention: Minimum attention score to trade

        Returns:
            Tuple of (should_trade, attention_info)
        """
        attention = await self.get_attention_info(symbol, timeframe)

        if attention is None:
            # If no attention model, allow trading
            return True, None

        should_trade = attention.attention_score >= min_attention
        return should_trade, attention

    @property
    def hierarchical_available(self) -> bool:
        """Check if hierarchical pipeline is available."""
        return self._hierarchical_pipeline is not None

    async def get_hierarchical_prediction(
        self,
        symbol: str,
        load_models: bool = True
    ) -> Optional[HierarchicalResult]:
        """
        Get full hierarchical L0→L1→L2 prediction.

        This is the main method for getting predictions from the 3-level
        architecture combining attention, base models, and metamodels.

        Args:
            symbol: Trading symbol (e.g., 'XAUUSD')
            load_models: Whether to load models if not already loaded

        Returns:
            HierarchicalResult with all layer outputs, or None if unavailable
        """
        if not self._hierarchical_pipeline:
            logger.warning("HierarchicalPipeline not available")
            return None

        # Load models for this symbol if needed
        if load_models:
            try:
                self._hierarchical_pipeline.load_models(symbol)
            except Exception as e:
                logger.warning(f"Failed to load hierarchical models for {symbol}: {e}")
                return None

        # Get market data for both timeframes
        try:
            df_5m, df_15m = await asyncio.gather(
                self.get_market_data(symbol, "5m", lookback_periods=300),
                self.get_market_data(symbol, "15m", lookback_periods=150)
            )

            if df_5m.empty or df_15m.empty:
                logger.warning(f"Insufficient data for hierarchical prediction: {symbol}")
                return None

            # Run hierarchical prediction
            result = self._hierarchical_pipeline.predict(df_5m, df_15m, symbol)

            # Convert to HierarchicalResult dataclass
            return HierarchicalResult(
                symbol=result.symbol,
                timestamp=result.timestamp,
                attention_score_5m=result.attention_score_5m,
                attention_score_15m=result.attention_score_15m,
                attention_class_5m=result.attention_class_5m,
                attention_class_15m=result.attention_class_15m,
                pred_high_5m=result.pred_high_5m,
                pred_low_5m=result.pred_low_5m,
                pred_high_15m=result.pred_high_15m,
                pred_low_15m=result.pred_low_15m,
                delta_high_final=result.delta_high_final,
                delta_low_final=result.delta_low_final,
                confidence=result.confidence,
                confidence_proba=result.confidence_proba,
                should_trade=result.should_trade,
                trade_quality=result.trade_quality
            )

        except Exception as e:
            logger.error(f"Hierarchical prediction failed for {symbol}: {e}")
            return None

    async def get_hierarchical_model_info(self, symbol: str) -> Optional[Dict]:
        """Get information about loaded hierarchical models for a symbol."""
        if not self._hierarchical_pipeline:
            return None
        return self._hierarchical_pipeline.get_model_info(symbol)

    @property
    def backtester_available(self) -> bool:
        """Check if backtester is available."""
        return self._backtester is not None

    async def run_backtest(
        self,
        symbol: str,
        start_date: datetime,
        end_date: datetime,
        initial_capital: float = 10000.0,
        risk_per_trade: float = 0.02,
        rr_config: str = "rr_2_1",
        min_confidence: float = 0.55,
        filter_by_amd: bool = True
    ) -> Optional[BacktestSummary]:
        """
        Run backtest on historical data.

        Args:
            symbol: Trading symbol
            start_date: Start date for backtest
            end_date: End date for backtest
            initial_capital: Starting capital
            risk_per_trade: Risk per trade as fraction (0.02 = 2%)
            rr_config: Risk/Reward configuration (rr_2_1 or rr_3_1)
            min_confidence: Minimum confidence to trade
            filter_by_amd: Filter by AMD phase

        Returns:
            BacktestSummary with results, or None if failed
        """
        if not self._backtester:
            logger.warning("Backtester not available")
            return None

        try:
            # Configure backtester
            config = BacktestConfig(
                initial_capital=initial_capital,
                risk_per_trade=risk_per_trade,
                min_confidence=min_confidence,
                filter_by_amd=filter_by_amd
            )
            self._backtester.config = config

            # Fetch historical data
            days_diff = (end_date - start_date).days
            periods = days_diff * 288  # Approximate 5-minute bars per day

            df = await self.get_market_data(symbol, "5m", lookback_periods=min(periods, 10000))

            if df.empty:
                logger.warning(f"No data available for backtest: {symbol}")
                return None

            # Filter by date range
            df = df[(df.index >= start_date) & (df.index <= end_date)]

            if len(df) < 100:
                logger.warning(f"Insufficient data for backtest: {len(df)} bars")
                return None

            # Generate signals for the period
            signals = await self._generate_backtest_signals(df, symbol, rr_config)

            # Run backtest
            result = self._backtester.run_backtest(
                price_data=df,
                signals=signals,
                rr_config={'name': rr_config, 'sl': 5.0, 'tp': 10.0 if rr_config == 'rr_2_1' else 15.0}
            )

            # Convert to summary
            return BacktestSummary(
                total_trades=result.metrics.total_trades,
                winning_trades=result.metrics.winning_trades,
                losing_trades=result.metrics.losing_trades,
                winrate=result.metrics.winrate,
                net_profit=result.metrics.net_profit,
                profit_factor=result.metrics.profit_factor,
                max_drawdown=result.metrics.max_drawdown,
                max_drawdown_pct=result.metrics.max_drawdown_pct,
                sharpe_ratio=result.metrics.sharpe_ratio,
                sortino_ratio=result.metrics.sortino_ratio,
                signals_generated=result.signals_generated,
                signals_filtered=result.signals_filtered,
                signals_traded=result.signals_traded,
                avg_trade_duration=result.metrics.avg_trade_duration,
                metrics_by_rr={k: v.to_dict() for k, v in result.metrics_by_rr.items()},
                metrics_by_amd={k: v.to_dict() for k, v in result.metrics_by_amd.items()}
            )

        except Exception as e:
            logger.error(f"Backtest failed for {symbol}: {e}")
            return None

    async def _generate_backtest_signals(
        self,
        df: pd.DataFrame,
        symbol: str,
        rr_config: str
    ) -> pd.DataFrame:
        """
        Generate signals for backtesting from price data.

        Uses the prediction models to generate prob_tp_first and direction.
        """
        signals = pd.DataFrame(index=df.index)

        # Generate signals at regular intervals (every 12 bars = 1 hour for 5m data)
        signal_interval = 12
        n = len(df)

        signals['prob_tp_first'] = np.nan
        signals['direction'] = 'long'
        signals['horizon'] = '15m'
        signals['rr_config'] = rr_config
        signals['amd_phase'] = None
        signals['volatility_regime'] = 'medium'
        signals['confidence'] = np.nan

        for i in range(100, n, signal_interval):
            try:
                # Get slice of data for prediction
                df_slice = df.iloc[:i+1]

                # Generate features
                features = self.feature_engineer.create_features(df_slice)

                # Get AMD phase
                amd_detection = self._heuristic_amd_detection(df_slice)

                # Generate probability (use TPSL classifier if available)
                if self._tpsl_classifier:
                    pred = self._tpsl_classifier.predict(features, rr_config)
                    prob = pred.get("prob_tp_first", 0.5)
                    confidence = pred.get("confidence", 0.5)
                else:
                    # Heuristic probability based on momentum
                    returns_5 = df_slice['close'].pct_change(5).iloc[-1]
                    prob = 0.5 + (returns_5 * 5)  # Simple momentum-based
                    prob = max(0.35, min(0.65, prob))
                    confidence = 0.5

                # Determine direction based on AMD phase
                if amd_detection.phase == AMDPhase.ACCUMULATION:
                    direction = 'long'
                elif amd_detection.phase == AMDPhase.DISTRIBUTION:
                    direction = 'short'
                else:
                    # Use momentum
                    direction = 'long' if returns_5 > 0 else 'short'

                # Set signal values
                idx = df.index[i]
                signals.loc[idx, 'prob_tp_first'] = prob
                signals.loc[idx, 'direction'] = direction
                signals.loc[idx, 'amd_phase'] = amd_detection.phase.value
                signals.loc[idx, 'confidence'] = confidence

            except Exception as e:
                logger.debug(f"Signal generation failed at bar {i}: {e}")
                continue

        return signals

    async def generate_signal(
        self,
        symbol: str,
        timeframe: str = "15m",
        rr_config: str = "rr_2_1"
    ) -> TradingSignal:
        """
        Generate complete trading signal.

        Args:
            symbol: Trading symbol
            timeframe: Analysis timeframe
            rr_config: Risk/Reward configuration

        Returns:
            Complete trading signal
        """
        # Get all predictions in parallel
        range_preds, tpsl_pred, amd_detection = await asyncio.gather(
            self.predict_range(symbol, timeframe, ["15m"]),
            self.predict_tpsl(symbol, timeframe, rr_config),
            self.detect_amd_phase(symbol, timeframe)
        )

        range_pred = range_preds[0] if range_preds else self._default_range_predictions(["15m"])[0]

        # Get current price
        current_price = await self.data_manager.get_latest_price(symbol)
        if not current_price:
            df = await self.get_market_data(symbol, timeframe, 10)
            current_price = df['close'].iloc[-1] if not df.empty else 0

        # Determine direction based on AMD phase and predictions
        direction = self._determine_direction(amd_detection, tpsl_pred)

        # Calculate entry, SL, TP
        entry, sl, tp = self._calculate_levels(
            current_price,
            direction,
            range_pred,
            rr_config
        )

        # Calculate confidence score
        confidence = self._calculate_confidence(
            range_pred,
            tpsl_pred,
            amd_detection
        )

        # Determine volatility regime
        volatility = self._determine_volatility(range_pred)

        now = datetime.utcnow()
        validity_minutes = {"15m": 15, "1h": 60, "4h": 240}.get(timeframe, 15)

        # Get attention info from Level 0 models
        attention_info = await self.get_attention_info(symbol, timeframe)

        # Boost confidence if high flow detected
        adjusted_confidence = confidence
        if attention_info and attention_info.is_high_flow:
            adjusted_confidence = min(1.0, confidence * 1.15)  # 15% boost
            logger.debug(f"High flow detected, boosted confidence: {confidence:.3f} -> {adjusted_confidence:.3f}")

        signal = TradingSignal(
            signal_id=f"SIG-{uuid.uuid4().hex[:8].upper()}",
            symbol=symbol,
            direction=direction,
            entry_price=entry,
            stop_loss=sl,
            take_profit=tp,
            risk_reward_ratio=float(rr_config.split("_")[1]),
            prob_tp_first=tpsl_pred.prob_tp_first,
            confidence_score=adjusted_confidence,
            amd_phase=amd_detection.phase,
            volatility_regime=volatility,
            range_prediction=range_pred,
            timestamp=now,
            valid_until=now + timedelta(minutes=validity_minutes),
            attention=attention_info,
            metadata={
                "timeframe": timeframe,
                "rr_config": rr_config,
                "amd_signals": amd_detection.signals,
                "attention_score": attention_info.attention_score if attention_info else 1.0
            }
        )

        # Store signal for historical queries
        self._store_signal(signal)

        return signal

    def _store_signal(self, signal: TradingSignal):
        """Store a generated signal in the signal store"""
        if not self._signal_store:
            return

        try:
            stored = StoredSignal(
                signal_id=signal.signal_id,
                symbol=signal.symbol,
                direction=signal.direction.value,
                entry_price=signal.entry_price,
                stop_loss=signal.stop_loss,
                take_profit=signal.take_profit,
                risk_reward_ratio=signal.risk_reward_ratio,
                prob_tp_first=signal.prob_tp_first,
                confidence_score=signal.confidence_score,
                amd_phase=signal.amd_phase.value,
                volatility_regime=signal.volatility_regime.value,
                timeframe=signal.metadata.get("timeframe", "15m"),
                timestamp=signal.timestamp,
                valid_until=signal.valid_until,
                attention_score=signal.attention.attention_score if signal.attention else None,
                flow_class=signal.attention.flow_class if signal.attention else None,
                is_high_flow=signal.attention.is_high_flow if signal.attention else None,
                metadata=signal.metadata
            )
            self._signal_store.store(stored)
        except Exception as e:
            logger.debug(f"Failed to store signal: {e}")

    @property
    def signal_store_available(self) -> bool:
        """Check if signal store is available"""
        return self._signal_store is not None

    def query_signals(
        self,
        symbol: Optional[str] = None,
        direction: Optional[str] = None,
        start_date: Optional[datetime] = None,
        end_date: Optional[datetime] = None,
        min_confidence: Optional[float] = None,
        amd_phase: Optional[str] = None,
        timeframe: Optional[str] = None,
        is_valid_only: bool = False,
        page: int = 1,
        page_size: int = 50
    ) -> Dict[str, Any]:
        """
        Query historical signals.

        Args:
            symbol: Filter by symbol
            direction: Filter by direction ('long', 'short', 'neutral')
            start_date: Filter by start date
            end_date: Filter by end date
            min_confidence: Minimum confidence score
            amd_phase: Filter by AMD phase
            timeframe: Filter by timeframe
            is_valid_only: Only return signals still valid
            page: Page number
            page_size: Results per page

        Returns:
            Dict with signals and pagination info
        """
        if not self._signal_store:
            return {
                "signals": [],
                "total_count": 0,
                "page": page,
                "page_size": page_size,
                "has_more": False,
                "error": "Signal store not available"
            }

        result = self._signal_store.query(
            symbol=symbol,
            direction=direction,
            start_date=start_date,
            end_date=end_date,
            min_confidence=min_confidence,
            amd_phase=amd_phase,
            timeframe=timeframe,
            is_valid_only=is_valid_only,
            page=page,
            page_size=page_size
        )

        return {
            "signals": [s.to_dict() for s in result.signals],
            "total_count": result.total_count,
            "page": result.page,
            "page_size": result.page_size,
            "has_more": result.has_more,
            "query_params": result.query_params
        }

    def get_latest_signals(self, symbol: Optional[str] = None, limit: int = 10) -> List[Dict]:
        """Get the most recent signals"""
        if not self._signal_store:
            return []

        signals = self._signal_store.get_latest(symbol=symbol, limit=limit)
        return [s.to_dict() for s in signals]

    def get_valid_signals(self, symbol: Optional[str] = None) -> List[Dict]:
        """Get all currently valid signals"""
        if not self._signal_store:
            return []

        signals = self._signal_store.get_valid(symbol=symbol)
        return [s.to_dict() for s in signals]

    def get_signal_by_id(self, signal_id: str) -> Optional[Dict]:
        """Get a specific signal by ID"""
        if not self._signal_store:
            return None

        signal = self._signal_store.get(signal_id)
        return signal.to_dict() if signal else None

    def get_signal_store_stats(self) -> Dict[str, Any]:
        """Get signal store statistics"""
        if not self._signal_store:
            return {"available": False}

        stats = self._signal_store.get_statistics()
        stats["available"] = True
        return stats

    def _determine_direction(
        self,
        amd: AMDDetection,
        tpsl: TPSLPrediction
    ) -> Direction:
        """Determine trade direction based on analysis"""
        bias = amd.trading_bias.get("direction", "neutral")

        if bias == "long" and tpsl.prob_tp_first > 0.55:
            return Direction.LONG
        elif bias == "short" and tpsl.prob_tp_first > 0.55:
            return Direction.SHORT

        # Default based on AMD phase
        phase_bias = {
            AMDPhase.ACCUMULATION: Direction.LONG,
            AMDPhase.MANIPULATION: Direction.NEUTRAL,
            AMDPhase.DISTRIBUTION: Direction.SHORT,
            AMDPhase.UNKNOWN: Direction.NEUTRAL
        }

        return phase_bias.get(amd.phase, Direction.NEUTRAL)

    def _calculate_levels(
        self,
        current_price: float,
        direction: Direction,
        range_pred: RangePrediction,
        rr_config: str
    ) -> Tuple[float, float, float]:
        """Calculate entry, SL, TP levels"""
        rr_ratio = float(rr_config.split("_")[1])

        if direction == Direction.LONG:
            entry = current_price
            sl = current_price - range_pred.delta_low
            tp = current_price + (range_pred.delta_low * rr_ratio)
        elif direction == Direction.SHORT:
            entry = current_price
            sl = current_price + range_pred.delta_high
            tp = current_price - (range_pred.delta_high * rr_ratio)
        else:
            entry = current_price
            sl = current_price - range_pred.delta_low
            tp = current_price + range_pred.delta_high

        return round(entry, 2), round(sl, 2), round(tp, 2)

    def _calculate_confidence(
        self,
        range_pred: RangePrediction,
        tpsl: TPSLPrediction,
        amd: AMDDetection
    ) -> float:
        """Calculate overall confidence score"""
        weights = {"range": 0.3, "tpsl": 0.4, "amd": 0.3}

        range_conf = (range_pred.confidence_high + range_pred.confidence_low) / 2
        tpsl_conf = tpsl.confidence
        amd_conf = amd.confidence

        confidence = (
            weights["range"] * range_conf +
            weights["tpsl"] * tpsl_conf +
            weights["amd"] * amd_conf
        )

        return round(confidence, 3)

    def _determine_volatility(self, range_pred: RangePrediction) -> VolatilityRegime:
        """Determine volatility regime from range prediction"""
        avg_delta = (range_pred.delta_high + range_pred.delta_low) / 2

        # Thresholds (adjust based on asset)
        if avg_delta < 5:
            return VolatilityRegime.LOW
        elif avg_delta < 15:
            return VolatilityRegime.MEDIUM
        elif avg_delta < 30:
            return VolatilityRegime.HIGH
        else:
            return VolatilityRegime.EXTREME

    def _default_range_predictions(self, horizons: List[str]) -> List[RangePrediction]:
        """Return default range predictions"""
        return [
            RangePrediction(
                horizon=h,
                delta_high=10.0 * (i + 1),
                delta_low=8.0 * (i + 1),
                delta_high_bin=None,
                delta_low_bin=None,
                confidence_high=0.5,
                confidence_low=0.5
            )
            for i, h in enumerate(horizons)
        ]

    def _default_amd_detection(self) -> AMDDetection:
        """Return default AMD detection"""
        return AMDDetection(
            phase=AMDPhase.UNKNOWN,
            confidence=0.5,
            start_time=datetime.utcnow(),
            characteristics={},
            signals=[],
            strength=0.5,
            trading_bias={"direction": "neutral"}
        )

    def _heuristic_amd_detection(self, df: pd.DataFrame) -> AMDDetection:
        """Heuristic AMD detection using price action"""
        # Analyze recent price action
        recent = df.tail(20)
        older = df.tail(50).head(30)

        recent_range = recent['high'].max() - recent['low'].min()
        older_range = older['high'].max() - older['low'].min()
        range_compression = recent_range / older_range if older_range > 0 else 1

        # Volume analysis
        recent_vol = recent['volume'].mean() if 'volume' in recent.columns else 1
        older_vol = older['volume'].mean() if 'volume' in older.columns else 1
        vol_ratio = recent_vol / older_vol if older_vol > 0 else 1

        # Determine phase
        if range_compression < 0.5 and vol_ratio < 0.8:
            phase = AMDPhase.ACCUMULATION
            signals = ["range_compression", "low_volume"]
            bias = {"direction": "long", "position_size": 0.7}
        elif range_compression > 1.2 and vol_ratio > 1.2:
            phase = AMDPhase.MANIPULATION
            signals = ["range_expansion", "high_volume"]
            bias = {"direction": "neutral", "position_size": 0.3}
        elif vol_ratio > 1.5:
            phase = AMDPhase.DISTRIBUTION
            signals = ["high_volume", "potential_distribution"]
            bias = {"direction": "short", "position_size": 0.6}
        else:
            phase = AMDPhase.UNKNOWN
            signals = []
            bias = {"direction": "neutral", "position_size": 0.5}

        return AMDDetection(
            phase=phase,
            confidence=0.6,
            start_time=datetime.utcnow(),
            characteristics={
                "range_compression": range_compression,
                "volume_ratio": vol_ratio
            },
            signals=signals,
            strength=0.6,
            trading_bias=bias
        )


# Singleton instance
_prediction_service: Optional[PredictionService] = None


def get_prediction_service() -> PredictionService:
    """Get or create prediction service singleton"""
    global _prediction_service
    if _prediction_service is None:
        _prediction_service = PredictionService()
    return _prediction_service


async def initialize_prediction_service():
    """Initialize the prediction service"""
    service = get_prediction_service()
    await service.initialize()
    return service
