"""
OrbiQuant IA - ML Engine API
============================

FastAPI application for ML predictions and signal generation.
Integrated with Data Service for real market data from Massive.com/Polygon.
"""

from fastapi import FastAPI, HTTPException, Depends, Query
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any
from datetime import datetime
from enum import Enum
import os
import asyncio

from loguru import logger

# Import prediction service
from ..services.prediction_service import (
    PredictionService,
    get_prediction_service,
    initialize_prediction_service,
    Direction,
    AMDPhase as ServiceAMDPhase,
    VolatilityRegime as ServiceVolatilityRegime,
    HierarchicalResult,
    BacktestSummary
)

# API Models
class TimeframeEnum(str, Enum):
    m5 = "5m"
    m15 = "15m"
    m30 = "30m"
    h1 = "1h"
    h4 = "4h"
    d1 = "1d"


class DirectionEnum(str, Enum):
    long = "long"
    short = "short"


class AMDPhaseEnum(str, Enum):
    accumulation = "accumulation"
    manipulation = "manipulation"
    distribution = "distribution"
    unknown = "unknown"


class VolatilityRegimeEnum(str, Enum):
    low = "low"
    medium = "medium"
    high = "high"
    extreme = "extreme"


# Request/Response Models
class PredictionRequest(BaseModel):
    """Request for ML prediction"""
    symbol: str = Field(..., description="Trading symbol (e.g., XAUUSD)")
    timeframe: TimeframeEnum = Field(default=TimeframeEnum.m15)
    horizon: str = Field(default="15m", description="Prediction horizon")
    features: Optional[Dict[str, float]] = Field(
        default=None,
        description="Pre-computed features (optional)"
    )


class RangePredictionResponse(BaseModel):
    """Range prediction response"""
    horizon: str
    delta_high: float
    delta_low: float
    delta_high_bin: Optional[int] = None
    delta_low_bin: Optional[int] = None
    confidence_high: float
    confidence_low: float


class TPSLPredictionResponse(BaseModel):
    """TP/SL classification response"""
    prob_tp_first: float
    rr_config: str
    confidence: float
    calibrated: bool


class AttentionInfoResponse(BaseModel):
    """Attention score info from Level 0 models"""
    attention_score: float = Field(..., description="Market flow score (1.0=avg, >2.0=high)")
    flow_class: int = Field(..., description="Flow class: 0=low, 1=medium, 2=high")
    flow_label: str = Field(..., description="Flow label: low_flow, medium_flow, high_flow")
    is_high_flow: bool = Field(..., description="True if flow_class == 2")
    is_tradeable: bool = Field(..., description="True if attention_score >= 1.0")


class SignalResponse(BaseModel):
    """Trading signal response"""
    signal_id: str
    symbol: str
    direction: DirectionEnum
    entry_price: float
    stop_loss: float
    take_profit: float
    risk_reward_ratio: float
    prob_tp_first: float
    confidence_score: float
    amd_phase: AMDPhaseEnum
    volatility_regime: VolatilityRegimeEnum
    range_prediction: RangePredictionResponse
    attention: Optional[AttentionInfoResponse] = Field(
        None, description="Level 0 attention score info"
    )
    timestamp: datetime
    valid_until: datetime
    metadata: Optional[Dict[str, Any]] = None


class HealthResponse(BaseModel):
    """Health check response"""
    status: str
    version: str
    models_loaded: bool
    hierarchical_available: bool = False
    timestamp: datetime


class ModelInfoResponse(BaseModel):
    """Model information response"""
    model_type: str
    version: str
    status: str
    horizons: List[str]
    supported_symbols: List[str]
    last_trained: Optional[datetime] = None
    metrics: Optional[Dict[str, float]] = None


# Initialize FastAPI app
app = FastAPI(
    title="OrbiQuant IA - ML Engine",
    description="Machine Learning predictions for trading",
    version="0.1.0",
    docs_url="/docs",
    redoc_url="/redoc"
)

# CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=os.getenv("CORS_ORIGINS", "*").split(","),
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Global state for models and services
models_state = {
    "range_predictor": None,
    "tpsl_classifier": None,
    "signal_generator": None,
    "amd_detector": None,
    "amd_ensemble": None,
    "backtester": None,
    "pipeline": None,
    "loaded": False
}

# Prediction service instance
prediction_service: Optional[PredictionService] = None


@app.on_event("startup")
async def startup_event():
    """Load models and initialize services on startup"""
    global prediction_service
    logger.info("Starting ML Engine API...")

    try:
        # Initialize prediction service with data integration
        prediction_service = await initialize_prediction_service()
        models_state["loaded"] = prediction_service.models_loaded
        logger.info(f"Prediction service initialized (models_loaded={models_state['loaded']})")
    except Exception as e:
        logger.warning(f"Prediction service initialization failed: {e}")
        prediction_service = get_prediction_service()

    logger.info("ML Engine API started - Ready to serve predictions")


@app.on_event("shutdown")
async def shutdown_event():
    """Cleanup on shutdown"""
    logger.info("Shutting down ML Engine API...")


# Health endpoint
@app.get("/health", response_model=HealthResponse, tags=["System"])
async def health_check():
    """Health check endpoint"""
    hierarchical = False
    if prediction_service:
        hierarchical = prediction_service.hierarchical_available
    return HealthResponse(
        status="healthy",
        version="0.1.0",
        models_loaded=models_state["loaded"],
        hierarchical_available=hierarchical,
        timestamp=datetime.utcnow()
    )


# Models info endpoint
@app.get("/models", response_model=List[ModelInfoResponse], tags=["Models"])
async def list_models():
    """List available models and their status"""
    models = []

    if models_state["range_predictor"]:
        models.append(ModelInfoResponse(
            model_type="range_predictor",
            version="phase2",
            status="deployed",
            horizons=["15m", "1h"],
            supported_symbols=["XAUUSD", "EURUSD", "GBPUSD", "BTCUSD"]
        ))

    if models_state["tpsl_classifier"]:
        models.append(ModelInfoResponse(
            model_type="tpsl_classifier",
            version="phase2",
            status="deployed",
            horizons=["15m", "1h"],
            supported_symbols=["XAUUSD", "EURUSD", "GBPUSD", "BTCUSD"]
        ))

    return models


# Prediction endpoints
@app.post("/predict/range", response_model=List[RangePredictionResponse], tags=["Predictions"])
async def predict_range(request: PredictionRequest):
    """
    Predict price ranges (ΔHigh/ΔLow) for a symbol

    Returns predictions for configured horizons (15m, 1h)
    Uses real market data from Massive.com/Polygon via Data Service.
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    try:
        predictions = await prediction_service.predict_range(
            symbol=request.symbol,
            timeframe=request.timeframe.value,
            horizons=["15m", "1h"]
        )

        return [
            RangePredictionResponse(
                horizon=pred.horizon,
                delta_high=pred.delta_high,
                delta_low=pred.delta_low,
                delta_high_bin=pred.delta_high_bin,
                delta_low_bin=pred.delta_low_bin,
                confidence_high=pred.confidence_high,
                confidence_low=pred.confidence_low
            )
            for pred in predictions
        ]
    except Exception as e:
        logger.error(f"Range prediction failed: {e}")
        raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}")


@app.post("/predict/tpsl", response_model=TPSLPredictionResponse, tags=["Predictions"])
async def predict_tpsl(
    request: PredictionRequest,
    rr_config: str = Query(default="rr_2_1", description="Risk/Reward config")
):
    """
    Predict probability of hitting TP before SL

    Uses real market data from Massive.com/Polygon via Data Service.

    Args:
        request: Prediction request with symbol and features
        rr_config: Risk/Reward configuration (rr_2_1 or rr_3_1)
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    try:
        pred = await prediction_service.predict_tpsl(
            symbol=request.symbol,
            timeframe=request.timeframe.value,
            rr_config=rr_config
        )

        return TPSLPredictionResponse(
            prob_tp_first=pred.prob_tp_first,
            rr_config=pred.rr_config,
            confidence=pred.confidence,
            calibrated=pred.calibrated
        )
    except Exception as e:
        logger.error(f"TPSL prediction failed: {e}")
        raise HTTPException(status_code=500, detail=f"Prediction failed: {str(e)}")


@app.post("/generate/signal", response_model=SignalResponse, tags=["Signals"])
async def generate_signal(
    request: PredictionRequest,
    rr_config: str = Query(default="rr_2_1")
):
    """
    Generate a complete trading signal

    Combines range prediction, TP/SL classification, and AMD phase detection.
    Uses real market data from Massive.com/Polygon via Data Service.
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    try:
        signal = await prediction_service.generate_signal(
            symbol=request.symbol,
            timeframe=request.timeframe.value,
            rr_config=rr_config
        )

        # Map service enums to API enums
        direction_map = {
            Direction.LONG: DirectionEnum.long,
            Direction.SHORT: DirectionEnum.short,
            Direction.NEUTRAL: DirectionEnum.long  # Default to long for neutral
        }
        amd_map = {
            ServiceAMDPhase.ACCUMULATION: AMDPhaseEnum.accumulation,
            ServiceAMDPhase.MANIPULATION: AMDPhaseEnum.manipulation,
            ServiceAMDPhase.DISTRIBUTION: AMDPhaseEnum.distribution,
            ServiceAMDPhase.UNKNOWN: AMDPhaseEnum.unknown
        }
        vol_map = {
            ServiceVolatilityRegime.LOW: VolatilityRegimeEnum.low,
            ServiceVolatilityRegime.MEDIUM: VolatilityRegimeEnum.medium,
            ServiceVolatilityRegime.HIGH: VolatilityRegimeEnum.high,
            ServiceVolatilityRegime.EXTREME: VolatilityRegimeEnum.extreme
        }

        # Convert attention info if present
        attention_response = None
        if signal.attention:
            attention_response = AttentionInfoResponse(
                attention_score=signal.attention.attention_score,
                flow_class=signal.attention.flow_class,
                flow_label=signal.attention.flow_label,
                is_high_flow=signal.attention.is_high_flow,
                is_tradeable=signal.attention.is_tradeable
            )

        return SignalResponse(
            signal_id=signal.signal_id,
            symbol=signal.symbol,
            direction=direction_map.get(signal.direction, DirectionEnum.long),
            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=amd_map.get(signal.amd_phase, AMDPhaseEnum.unknown),
            volatility_regime=vol_map.get(signal.volatility_regime, VolatilityRegimeEnum.medium),
            range_prediction=RangePredictionResponse(
                horizon=signal.range_prediction.horizon,
                delta_high=signal.range_prediction.delta_high,
                delta_low=signal.range_prediction.delta_low,
                delta_high_bin=signal.range_prediction.delta_high_bin,
                delta_low_bin=signal.range_prediction.delta_low_bin,
                confidence_high=signal.range_prediction.confidence_high,
                confidence_low=signal.range_prediction.confidence_low
            ),
            attention=attention_response,
            timestamp=signal.timestamp,
            valid_until=signal.valid_until,
            metadata=signal.metadata
        )
    except Exception as e:
        logger.error(f"Signal generation failed: {e}")
        raise HTTPException(status_code=500, detail=f"Signal generation failed: {str(e)}")


# Symbols endpoint
@app.get("/symbols", response_model=List[str], tags=["Data"])
async def list_symbols():
    """List available trading symbols"""
    return ["XAUUSD", "EURUSD", "GBPUSD", "USDJPY", "BTCUSD", "ETHUSD"]


# Attention Score endpoint
@app.get("/api/attention/{symbol}", response_model=AttentionInfoResponse, tags=["Attention"])
async def get_attention_score(
    symbol: str,
    timeframe: TimeframeEnum = Query(default=TimeframeEnum.m5)
):
    """
    Get attention score for a symbol.

    Attention scores indicate market flow intensity:
    - < 1.0: Low flow (below average movement expected)
    - 1.0 - 2.0: Medium flow (average conditions)
    - > 2.0: High flow (above average movement expected)

    Use this to filter trades: only trade when attention_score >= 1.0
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    try:
        attention = await prediction_service.get_attention_info(
            symbol=symbol.upper(),
            timeframe=timeframe.value
        )

        if attention is None:
            # Return default values if no attention model available
            return AttentionInfoResponse(
                attention_score=1.0,
                flow_class=1,
                flow_label="medium_flow",
                is_high_flow=False,
                is_tradeable=True
            )

        return AttentionInfoResponse(
            attention_score=attention.attention_score,
            flow_class=attention.flow_class,
            flow_label=attention.flow_label,
            is_high_flow=attention.is_high_flow,
            is_tradeable=attention.is_tradeable
        )
    except Exception as e:
        logger.error(f"Attention score failed for {symbol}: {e}")
        raise HTTPException(status_code=500, detail=f"Attention score failed: {str(e)}")


@app.get("/api/attention/models", tags=["Attention"])
async def list_attention_models():
    """List available attention models"""
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    if prediction_service._attention_provider and prediction_service._attention_provider.is_loaded:
        return {
            "loaded": True,
            "models": prediction_service._attention_provider.available_models,
            "count": len(prediction_service._attention_provider.models)
        }

    return {
        "loaded": False,
        "models": [],
        "count": 0
    }


# =============================================================================
# Hierarchical Pipeline Endpoints (L0→L1→L2)
# =============================================================================

class HierarchicalPredictionResponse(BaseModel):
    """Full hierarchical L0→L1→L2 prediction response"""
    symbol: str
    timestamp: datetime
    # Level 0: Attention
    attention_score_5m: float = Field(..., description="Attention score for 5m timeframe")
    attention_score_15m: float = Field(..., description="Attention score for 15m timeframe")
    attention_class_5m: int = Field(..., description="Flow class 5m: 0=low, 1=med, 2=high")
    attention_class_15m: int = Field(..., description="Flow class 15m: 0=low, 1=med, 2=high")
    # Level 1: Base Models
    pred_high_5m: float = Field(..., description="Predicted delta high from 5m model")
    pred_low_5m: float = Field(..., description="Predicted delta low from 5m model")
    pred_high_15m: float = Field(..., description="Predicted delta high from 15m model")
    pred_low_15m: float = Field(..., description="Predicted delta low from 15m model")
    # Level 2: Metamodel
    delta_high_final: float = Field(..., description="Final synthesized delta high")
    delta_low_final: float = Field(..., description="Final synthesized delta low")
    confidence: bool = Field(..., description="Metamodel confidence flag")
    confidence_proba: float = Field(..., description="Metamodel confidence probability")
    # Trading signals
    should_trade: bool = Field(..., description="Should trade based on attention+confidence")
    trade_quality: str = Field(..., description="Trade quality: high, medium, low, skip")


@app.get("/api/hierarchical/{symbol}", response_model=HierarchicalPredictionResponse, tags=["Hierarchical"])
async def get_hierarchical_prediction(symbol: str):
    """
    Get full hierarchical L0→L1→L2 prediction for a symbol.

    This is the main prediction endpoint that uses the 3-level architecture:
    - Level 0 (Attention): Determines WHEN to pay attention to market
    - Level 1 (Base Models): Symbol/timeframe specific predictions (5m + 15m)
    - Level 2 (Metamodel): Synthesizes predictions into final values

    The metamodel type (XGBoost or Neural Gating) is selected automatically
    based on best performance per symbol.

    Returns should_trade=True only when attention is sufficient AND
    metamodel confidence exceeds threshold.
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    if not prediction_service.hierarchical_available:
        raise HTTPException(
            status_code=503,
            detail="Hierarchical pipeline not available"
        )

    try:
        result = await prediction_service.get_hierarchical_prediction(
            symbol=symbol.upper()
        )

        if result is None:
            raise HTTPException(
                status_code=500,
                detail=f"Hierarchical prediction failed for {symbol}"
            )

        return HierarchicalPredictionResponse(
            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 HTTPException:
        raise
    except Exception as e:
        logger.error(f"Hierarchical prediction failed for {symbol}: {e}")
        raise HTTPException(status_code=500, detail=f"Hierarchical prediction failed: {str(e)}")


@app.get("/api/hierarchical/{symbol}/models", tags=["Hierarchical"])
async def get_hierarchical_model_info(symbol: str):
    """
    Get information about loaded hierarchical models for a symbol.

    Shows which models are loaded for each level of the hierarchy.
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    if not prediction_service.hierarchical_available:
        return {
            "available": False,
            "symbol": symbol.upper(),
            "models_loaded": False
        }

    try:
        info = await prediction_service.get_hierarchical_model_info(symbol.upper())
        return {
            "available": True,
            **info
        } if info else {
            "available": True,
            "symbol": symbol.upper(),
            "models_loaded": False,
            "attention_models": [],
            "base_models": [],
            "metamodel_type": "none"
        }
    except Exception as e:
        logger.error(f"Failed to get hierarchical model info: {e}")
        return {
            "available": False,
            "error": str(e)
        }


@app.get("/api/hierarchical/status", tags=["Hierarchical"])
async def get_hierarchical_status():
    """
    Get overall status of the hierarchical pipeline.

    Returns availability and configuration info.
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    available = prediction_service.hierarchical_available

    return {
        "available": available,
        "supported_symbols": ["XAUUSD", "EURUSD", "BTCUSD", "GBPUSD", "USDJPY"] if available else [],
        "timeframes": ["5m", "15m"] if available else [],
        "architecture": {
            "level_0": "Attention Models (flow detection)",
            "level_1": "Base Models (symbol/timeframe specific)",
            "level_2": "Metamodels (XGBoost or Neural Gating)"
        } if available else {}
    }


# Active signals endpoint - GET version for easy consumption
class ActiveSignalsResponse(BaseModel):
    """Response with active signals for all symbols"""
    signals: List[SignalResponse]
    generated_at: datetime
    symbols_processed: List[str]
    errors: List[str] = []


@app.get("/api/signals/active", response_model=ActiveSignalsResponse, tags=["Signals"])
async def get_active_signals(
    symbols: Optional[str] = Query(
        default=None,
        description="Comma-separated list of symbols (default: all)"
    ),
    timeframe: TimeframeEnum = Query(default=TimeframeEnum.m15),
    rr_config: str = Query(default="rr_2_1")
):
    """
    Get active trading signals for multiple symbols.

    This is a convenience endpoint that generates signals for all requested symbols
    in parallel. Useful for dashboard displays.

    Args:
        symbols: Comma-separated symbols (e.g., 'XAUUSD,EURUSD') or None for all
        timeframe: Analysis timeframe
        rr_config: Risk/Reward configuration
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    # Parse symbols
    if symbols:
        symbol_list = [s.strip().upper() for s in symbols.split(",")]
    else:
        symbol_list = ["XAUUSD", "EURUSD", "GBPUSD", "BTCUSD"]

    signals = []
    errors = []

    # Generate signals in parallel
    async def generate_for_symbol(sym: str):
        try:
            return await prediction_service.generate_signal(
                symbol=sym,
                timeframe=timeframe.value,
                rr_config=rr_config
            )
        except Exception as e:
            logger.warning(f"Failed to generate signal for {sym}: {e}")
            return None

    results = await asyncio.gather(
        *[generate_for_symbol(sym) for sym in symbol_list],
        return_exceptions=True
    )

    for sym, result in zip(symbol_list, results):
        if isinstance(result, Exception):
            errors.append(f"{sym}: {str(result)}")
        elif result is not None:
            # Convert to API response model
            direction_map = {
                Direction.LONG: DirectionEnum.long,
                Direction.SHORT: DirectionEnum.short,
                Direction.NEUTRAL: DirectionEnum.long
            }
            amd_map = {
                ServiceAMDPhase.ACCUMULATION: AMDPhaseEnum.accumulation,
                ServiceAMDPhase.MANIPULATION: AMDPhaseEnum.manipulation,
                ServiceAMDPhase.DISTRIBUTION: AMDPhaseEnum.distribution,
                ServiceAMDPhase.UNKNOWN: AMDPhaseEnum.unknown
            }
            vol_map = {
                ServiceVolatilityRegime.LOW: VolatilityRegimeEnum.low,
                ServiceVolatilityRegime.MEDIUM: VolatilityRegimeEnum.medium,
                ServiceVolatilityRegime.HIGH: VolatilityRegimeEnum.high,
                ServiceVolatilityRegime.EXTREME: VolatilityRegimeEnum.extreme
            }

            signals.append(SignalResponse(
                signal_id=result.signal_id,
                symbol=result.symbol,
                direction=direction_map.get(result.direction, DirectionEnum.long),
                entry_price=result.entry_price,
                stop_loss=result.stop_loss,
                take_profit=result.take_profit,
                risk_reward_ratio=result.risk_reward_ratio,
                prob_tp_first=result.prob_tp_first,
                confidence_score=result.confidence_score,
                amd_phase=amd_map.get(result.amd_phase, AMDPhaseEnum.unknown),
                volatility_regime=vol_map.get(result.volatility_regime, VolatilityRegimeEnum.medium),
                range_prediction=RangePredictionResponse(
                    horizon=result.range_prediction.horizon,
                    delta_high=result.range_prediction.delta_high,
                    delta_low=result.range_prediction.delta_low,
                    delta_high_bin=result.range_prediction.delta_high_bin,
                    delta_low_bin=result.range_prediction.delta_low_bin,
                    confidence_high=result.range_prediction.confidence_high,
                    confidence_low=result.range_prediction.confidence_low
                ),
                timestamp=result.timestamp,
                valid_until=result.valid_until,
                metadata=result.metadata
            ))

    return ActiveSignalsResponse(
        signals=signals,
        generated_at=datetime.utcnow(),
        symbols_processed=symbol_list,
        errors=errors
    )


# AMD Phase Detection endpoint
class AMDDetectionResponse(BaseModel):
    """AMD phase detection response"""
    phase: AMDPhaseEnum
    confidence: float
    start_time: datetime
    end_time: Optional[datetime]
    characteristics: Dict[str, float]
    signals: List[str]
    strength: float
    trading_bias: Dict[str, Any]


@app.post("/api/amd/{symbol}", response_model=AMDDetectionResponse, tags=["AMD"])
async def detect_amd_phase(
    symbol: str,
    timeframe: TimeframeEnum = TimeframeEnum.m15,
    lookback_periods: int = Query(default=100, ge=50, le=500)
):
    """
    Detect current AMD phase for a symbol

    Uses real market data from Massive.com/Polygon via Data Service.
    AMD = Accumulation, Manipulation, Distribution - Smart Money Concepts.

    Args:
        symbol: Trading symbol
        timeframe: Timeframe for analysis
        lookback_periods: Number of periods to analyze
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    try:
        detection = await prediction_service.detect_amd_phase(
            symbol=symbol,
            timeframe=timeframe.value,
            lookback_periods=lookback_periods
        )

        # Map service enum to API enum
        amd_map = {
            ServiceAMDPhase.ACCUMULATION: AMDPhaseEnum.accumulation,
            ServiceAMDPhase.MANIPULATION: AMDPhaseEnum.manipulation,
            ServiceAMDPhase.DISTRIBUTION: AMDPhaseEnum.distribution,
            ServiceAMDPhase.UNKNOWN: AMDPhaseEnum.unknown
        }

        return AMDDetectionResponse(
            phase=amd_map.get(detection.phase, AMDPhaseEnum.unknown),
            confidence=detection.confidence,
            start_time=detection.start_time,
            end_time=None,
            characteristics=detection.characteristics,
            signals=detection.signals,
            strength=detection.strength,
            trading_bias=detection.trading_bias
        )
    except Exception as e:
        logger.error(f"AMD detection failed: {e}")
        raise HTTPException(status_code=500, detail=f"AMD detection failed: {str(e)}")


# Backtesting endpoint
class BacktestRequest(BaseModel):
    """Backtest request"""
    symbol: str
    start_date: datetime
    end_date: datetime
    initial_capital: float = Field(default=10000.0)
    risk_per_trade: float = Field(default=0.02, ge=0.001, le=0.1)
    rr_config: str = Field(default="rr_2_1")
    filter_by_amd: bool = True
    min_confidence: float = Field(default=0.55, ge=0.0, le=1.0)


class BacktestResponse(BaseModel):
    """Backtest results response"""
    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 = 0.0
    metrics_by_rr: Optional[Dict[str, Any]] = None
    metrics_by_amd: Optional[Dict[str, Any]] = None


@app.post("/api/backtest", response_model=BacktestResponse, tags=["Backtesting"])
async def run_backtest(request: BacktestRequest):
    """
    Run backtest on historical data.

    Simulates trading based on ML predictions and returns performance metrics.
    Uses RRBacktester with TP/SL simulation and AMD phase filtering.

    Args:
        request: Backtest configuration including:
            - symbol: Trading symbol
            - start_date/end_date: Date range for backtest
            - initial_capital: Starting capital
            - risk_per_trade: Risk per trade as fraction
            - rr_config: Risk/Reward config (rr_2_1 or rr_3_1)
            - min_confidence: Minimum probability to trade
            - filter_by_amd: Whether to filter by AMD phase
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    if not prediction_service.backtester_available:
        raise HTTPException(
            status_code=503,
            detail="Backtester not available. Service may still be initializing."
        )

    try:
        result = await prediction_service.run_backtest(
            symbol=request.symbol.upper(),
            start_date=request.start_date,
            end_date=request.end_date,
            initial_capital=request.initial_capital,
            risk_per_trade=request.risk_per_trade,
            rr_config=request.rr_config,
            min_confidence=request.min_confidence,
            filter_by_amd=request.filter_by_amd
        )

        if result is None:
            raise HTTPException(
                status_code=500,
                detail="Backtest failed. Check logs for details."
            )

        return BacktestResponse(
            total_trades=result.total_trades,
            winning_trades=result.winning_trades,
            losing_trades=result.losing_trades,
            winrate=result.winrate,
            net_profit=result.net_profit,
            profit_factor=result.profit_factor,
            max_drawdown=result.max_drawdown,
            max_drawdown_pct=result.max_drawdown_pct,
            sharpe_ratio=result.sharpe_ratio,
            sortino_ratio=result.sortino_ratio,
            signals_generated=result.signals_generated,
            signals_filtered=result.signals_filtered,
            signals_traded=result.signals_traded,
            avg_trade_duration=result.avg_trade_duration,
            metrics_by_rr=result.metrics_by_rr,
            metrics_by_amd=result.metrics_by_amd
        )

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Backtest failed: {e}")
        raise HTTPException(status_code=500, detail=f"Backtest failed: {str(e)}")


@app.get("/api/backtest/status", tags=["Backtesting"])
async def get_backtest_status():
    """
    Get backtester availability status.
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    return {
        "available": prediction_service.backtester_available,
        "supported_symbols": ["XAUUSD", "EURUSD", "BTCUSD", "GBPUSD", "USDJPY"],
        "rr_configs": ["rr_2_1", "rr_3_1"],
        "default_config": {
            "initial_capital": 10000.0,
            "risk_per_trade": 0.02,
            "min_confidence": 0.55,
            "filter_by_amd": True
        }
    }


# Training endpoint
class TrainingRequest(BaseModel):
    """Training request"""
    symbol: str
    start_date: datetime
    end_date: datetime
    models_to_train: List[str] = Field(
        default=["range_predictor", "tpsl_classifier"],
        description="Models to train"
    )
    use_walk_forward: bool = True
    n_splits: int = Field(default=5, ge=2, le=10)


class TrainingResponse(BaseModel):
    """Training results response"""
    status: str
    models_trained: List[str]
    training_time_seconds: float
    metrics: Dict[str, Any]
    model_paths: Dict[str, str]


@app.post("/api/train/full", response_model=TrainingResponse, tags=["Training"])
async def train_models(request: TrainingRequest):
    """
    Train ML models with walk-forward validation

    Args:
        request: Training configuration
    """
    if not models_state.get("pipeline"):
        raise HTTPException(
            status_code=503,
            detail="Training pipeline not loaded"
        )

    # TODO: Implement actual training
    # pipeline = models_state["pipeline"]
    # metrics = pipeline.train(features, targets, walk_forward=request.use_walk_forward)

    # Mock response
    return TrainingResponse(
        status="completed",
        models_trained=request.models_to_train,
        training_time_seconds=3600.5,
        metrics={
            "range_predictor": {
                "val_mse": 0.025,
                "val_r2": 0.78
            },
            "tpsl_classifier": {
                "val_accuracy": 0.62,
                "val_auc": 0.68
            }
        },
        model_paths={
            "range_predictor": "models/phase2/range_predictor",
            "tpsl_classifier": "models/phase2/tpsl_classifier"
        }
    )


# =============================================================================
# ICT/SMC Analysis Endpoints
# =============================================================================

class ICTAnalysisResponse(BaseModel):
    """ICT/SMC analysis response"""
    timestamp: datetime
    symbol: str
    timeframe: str
    market_bias: str
    bias_confidence: float
    current_trend: str
    order_blocks: List[Dict[str, Any]]
    fair_value_gaps: List[Dict[str, Any]]
    liquidity_sweeps: List[Dict[str, Any]]
    structure_breaks: List[Dict[str, Any]]
    premium_zone: Dict[str, float]
    discount_zone: Dict[str, float]
    equilibrium: float
    entry_zone: Optional[Dict[str, float]]
    stop_loss: Optional[float]
    take_profits: Dict[str, Optional[float]]
    risk_reward: Optional[float]
    signals: List[str]
    score: float


@app.post("/api/ict/{symbol}", response_model=ICTAnalysisResponse, tags=["ICT/SMC"])
async def analyze_ict_smc(
    symbol: str,
    timeframe: TimeframeEnum = TimeframeEnum.h1,
    lookback_periods: int = Query(default=200, ge=100, le=500)
):
    """
    Perform ICT/SMC (Smart Money Concepts) analysis for a symbol

    Detects:
    - Order Blocks (institutional zones)
    - Fair Value Gaps (price imbalances)
    - Liquidity Sweeps (stop hunts)
    - Break of Structure / Change of Character
    - Premium/Discount zones

    Uses real market data from Massive.com/Polygon via Data Service.
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    try:
        # Fetch market data
        df = await prediction_service.fetch_ohlcv(
            symbol=symbol,
            timeframe=timeframe.value,
            limit=lookback_periods
        )

        if df is None or len(df) < 100:
            raise HTTPException(
                status_code=400,
                detail=f"Insufficient data for {symbol}"
            )

        # Run ICT analysis
        from ..models.ict_smc_detector import ICTSMCDetector
        detector = ICTSMCDetector(swing_lookback=10)
        analysis = detector.analyze(df, symbol, timeframe.value)

        return ICTAnalysisResponse(
            timestamp=analysis.timestamp,
            symbol=analysis.symbol,
            timeframe=analysis.timeframe,
            market_bias=analysis.market_bias.value,
            bias_confidence=analysis.bias_confidence,
            current_trend=analysis.current_trend,
            order_blocks=[ob.to_dict() for ob in analysis.order_blocks],
            fair_value_gaps=[fvg.to_dict() for fvg in analysis.fair_value_gaps],
            liquidity_sweeps=[ls.to_dict() for ls in analysis.liquidity_sweeps],
            structure_breaks=[sb.to_dict() for sb in analysis.structure_breaks],
            premium_zone={'low': analysis.premium_zone[0], 'high': analysis.premium_zone[1]},
            discount_zone={'low': analysis.discount_zone[0], 'high': analysis.discount_zone[1]},
            equilibrium=analysis.equilibrium,
            entry_zone={'low': analysis.entry_zone[0], 'high': analysis.entry_zone[1]} if analysis.entry_zone else None,
            stop_loss=analysis.stop_loss,
            take_profits={
                'tp1': analysis.take_profit_1,
                'tp2': analysis.take_profit_2,
                'tp3': analysis.take_profit_3
            },
            risk_reward=analysis.risk_reward,
            signals=analysis.signals,
            score=analysis.score
        )
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"ICT analysis failed: {e}")
        raise HTTPException(status_code=500, detail=f"ICT analysis failed: {str(e)}")


# =============================================================================
# Strategy Ensemble Endpoints
# =============================================================================

class EnsembleSignalResponse(BaseModel):
    """Ensemble trading signal response"""
    timestamp: datetime
    symbol: str
    timeframe: str
    action: str
    confidence: float
    strength: str
    scores: Dict[str, float]
    levels: Dict[str, Optional[float]]
    position: Dict[str, float]
    model_signals: List[Dict[str, Any]]
    confluence_count: int
    market_phase: str
    market_bias: str
    key_levels: Dict[str, float]
    signals: List[str]
    setup_score: float


@app.post("/api/ensemble/{symbol}", response_model=EnsembleSignalResponse, tags=["Ensemble"])
async def get_ensemble_signal(
    symbol: str,
    timeframe: TimeframeEnum = TimeframeEnum.h1
):
    """
    Get combined ensemble trading signal

    Combines multiple ML models and strategies:
    - AMD Detector (25% weight)
    - ICT/SMC Detector (35% weight)
    - Range Predictor (20% weight)
    - TP/SL Classifier (20% weight)

    Returns a high-confidence signal when multiple models agree.
    Uses real market data from Massive.com/Polygon via Data Service.
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    try:
        # Fetch market data
        df = await prediction_service.fetch_ohlcv(
            symbol=symbol,
            timeframe=timeframe.value,
            limit=300
        )

        if df is None or len(df) < 100:
            raise HTTPException(
                status_code=400,
                detail=f"Insufficient data for {symbol}"
            )

        # Run ensemble analysis
        from ..models.strategy_ensemble import StrategyEnsemble
        ensemble = StrategyEnsemble()
        signal = ensemble.analyze(df, symbol, timeframe.value)

        return EnsembleSignalResponse(
            timestamp=signal.timestamp,
            symbol=signal.symbol,
            timeframe=signal.timeframe,
            action=signal.action.value,
            confidence=signal.confidence,
            strength=signal.strength.value,
            scores={
                'bullish': signal.bullish_score,
                'bearish': signal.bearish_score,
                'net': signal.net_score
            },
            levels={
                'entry': signal.entry_price,
                'stop_loss': signal.stop_loss,
                'take_profit_1': signal.take_profit_1,
                'take_profit_2': signal.take_profit_2,
                'take_profit_3': signal.take_profit_3,
                'risk_reward': signal.risk_reward
            },
            position={
                'risk_percent': signal.suggested_risk_percent,
                'size_multiplier': signal.position_size_multiplier
            },
            model_signals=[
                {
                    'model': s.model_name,
                    'action': s.action,
                    'confidence': s.confidence,
                    'weight': s.weight
                }
                for s in signal.model_signals
            ],
            confluence_count=signal.confluence_count,
            market_phase=signal.market_phase,
            market_bias=signal.market_bias,
            key_levels=signal.key_levels,
            signals=signal.signals,
            setup_score=signal.setup_score
        )
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Ensemble analysis failed: {e}")
        raise HTTPException(status_code=500, detail=f"Ensemble analysis failed: {str(e)}")


@app.get("/api/ensemble/quick/{symbol}", tags=["Ensemble"])
async def get_quick_signal(
    symbol: str,
    timeframe: TimeframeEnum = TimeframeEnum.h1
):
    """
    Get a quick trading signal for immediate use

    Returns simplified signal data for fast consumption.
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    try:
        # Fetch market data
        df = await prediction_service.fetch_ohlcv(
            symbol=symbol,
            timeframe=timeframe.value,
            limit=200
        )

        if df is None or len(df) < 100:
            raise HTTPException(
                status_code=400,
                detail=f"Insufficient data for {symbol}"
            )

        # Run ensemble analysis
        from ..models.strategy_ensemble import StrategyEnsemble
        ensemble = StrategyEnsemble()
        return ensemble.get_quick_signal(df, symbol)

    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"Quick signal failed: {e}")
        raise HTTPException(status_code=500, detail=f"Quick signal failed: {str(e)}")


# =============================================================================
# Multi-Symbol Analysis
# =============================================================================

class MultiSymbolRequest(BaseModel):
    """Request for multi-symbol analysis"""
    symbols: List[str] = Field(..., description="List of symbols to analyze")
    timeframe: str = Field(default="1h")
    min_score: float = Field(default=50.0, ge=0, le=100)


class MultiSymbolResponse(BaseModel):
    """Response with analysis for multiple symbols"""
    timestamp: datetime
    signals: List[Dict[str, Any]]
    best_setups: List[Dict[str, Any]]
    market_overview: Dict[str, Any]


@app.post("/api/scan", response_model=MultiSymbolResponse, tags=["Scanner"])
async def scan_symbols(request: MultiSymbolRequest):
    """
    Scan multiple symbols for trading opportunities

    Returns ensemble signals for all symbols, sorted by setup score.
    Useful for finding the best trading opportunities across markets.
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    from ..models.strategy_ensemble import StrategyEnsemble
    ensemble = StrategyEnsemble()

    signals = []
    bullish_count = 0
    bearish_count = 0
    neutral_count = 0

    for symbol in request.symbols:
        try:
            df = await prediction_service.fetch_ohlcv(
                symbol=symbol,
                timeframe=request.timeframe,
                limit=200
            )

            if df is not None and len(df) >= 100:
                signal = ensemble.get_quick_signal(df, symbol)
                signals.append(signal)

                if signal['action'] in ['strong_buy', 'buy']:
                    bullish_count += 1
                elif signal['action'] in ['strong_sell', 'sell']:
                    bearish_count += 1
                else:
                    neutral_count += 1

        except Exception as e:
            logger.warning(f"Failed to analyze {symbol}: {e}")

    # Sort by score descending
    signals.sort(key=lambda x: x.get('score', 0), reverse=True)

    # Filter by minimum score
    best_setups = [s for s in signals if s.get('score', 0) >= request.min_score]

    return MultiSymbolResponse(
        timestamp=datetime.utcnow(),
        signals=signals,
        best_setups=best_setups[:5],  # Top 5 setups
        market_overview={
            'total_analyzed': len(signals),
            'bullish': bullish_count,
            'bearish': bearish_count,
            'neutral': neutral_count,
            'sentiment': 'bullish' if bullish_count > bearish_count else 'bearish' if bearish_count > bullish_count else 'neutral'
        }
    )


# =============================================================================
# WebSocket for real-time signals
# =============================================================================
from fastapi import WebSocket, WebSocketDisconnect
import json


class SignalStreamManager:
    """
    Manages WebSocket connections for real-time signal streaming.

    Supports:
    - Per-symbol subscriptions
    - Broadcast to all connections
    - Targeted messages to specific subscriptions
    - Connection state tracking
    """

    def __init__(self):
        # Map of websocket -> set of subscribed symbols
        self.subscriptions: Dict[WebSocket, set] = {}
        # Map of symbol -> set of websockets
        self.symbol_subscribers: Dict[str, set] = {}
        # All active connections
        self.active_connections: List[WebSocket] = []
        # Background task reference
        self._signal_task = None
        self._running = False

    async def connect(self, websocket: WebSocket):
        """Accept new WebSocket connection"""
        await websocket.accept()
        self.active_connections.append(websocket)
        self.subscriptions[websocket] = set()
        logger.info(f"WebSocket connected. Total connections: {len(self.active_connections)}")

    def disconnect(self, websocket: WebSocket):
        """Handle WebSocket disconnection"""
        if websocket in self.active_connections:
            self.active_connections.remove(websocket)

        # Remove from all symbol subscriptions
        if websocket in self.subscriptions:
            for symbol in self.subscriptions[websocket]:
                if symbol in self.symbol_subscribers:
                    self.symbol_subscribers[symbol].discard(websocket)
            del self.subscriptions[websocket]

        logger.info(f"WebSocket disconnected. Total connections: {len(self.active_connections)}")

    def subscribe(self, websocket: WebSocket, symbols: List[str]):
        """Subscribe websocket to specific symbols"""
        if websocket not in self.subscriptions:
            self.subscriptions[websocket] = set()

        for symbol in symbols:
            symbol = symbol.upper()
            self.subscriptions[websocket].add(symbol)

            if symbol not in self.symbol_subscribers:
                self.symbol_subscribers[symbol] = set()
            self.symbol_subscribers[symbol].add(websocket)

        logger.debug(f"Subscribed to {symbols}. Total symbols: {len(self.subscriptions[websocket])}")

    def unsubscribe(self, websocket: WebSocket, symbols: List[str]):
        """Unsubscribe websocket from specific symbols"""
        if websocket not in self.subscriptions:
            return

        for symbol in symbols:
            symbol = symbol.upper()
            self.subscriptions[websocket].discard(symbol)

            if symbol in self.symbol_subscribers:
                self.symbol_subscribers[symbol].discard(websocket)

    async def send_to_symbol(self, symbol: str, message: dict):
        """Send message to all subscribers of a symbol"""
        symbol = symbol.upper()
        if symbol not in self.symbol_subscribers:
            return

        disconnected = []
        for websocket in self.symbol_subscribers[symbol]:
            try:
                await websocket.send_json(message)
            except Exception:
                disconnected.append(websocket)

        # Clean up disconnected
        for ws in disconnected:
            self.disconnect(ws)

    async def broadcast(self, message: dict):
        """Broadcast message to all connections"""
        disconnected = []
        for websocket in self.active_connections:
            try:
                await websocket.send_json(message)
            except Exception:
                disconnected.append(websocket)

        # Clean up disconnected
        for ws in disconnected:
            self.disconnect(ws)

    async def send_personal(self, websocket: WebSocket, message: dict):
        """Send message to specific connection"""
        try:
            await websocket.send_json(message)
        except Exception:
            self.disconnect(websocket)

    def get_subscribed_symbols(self, websocket: WebSocket) -> List[str]:
        """Get list of symbols a websocket is subscribed to"""
        return list(self.subscriptions.get(websocket, set()))

    @property
    def connection_count(self) -> int:
        return len(self.active_connections)

    @property
    def subscription_stats(self) -> Dict[str, int]:
        """Get subscription statistics"""
        return {
            symbol: len(subscribers)
            for symbol, subscribers in self.symbol_subscribers.items()
        }


# Global signal stream manager
signal_manager = SignalStreamManager()

# Background signal generation task
_signal_generation_task = None


async def generate_signals_background():
    """
    Background task that generates signals periodically and broadcasts to subscribers.
    """
    global prediction_service

    logger.info("Starting background signal generation task")

    default_symbols = ["XAUUSD", "EURUSD", "BTCUSD", "GBPUSD"]

    while True:
        try:
            # Only generate if there are active connections
            if signal_manager.connection_count > 0:
                # Get symbols that have subscribers
                active_symbols = list(signal_manager.symbol_subscribers.keys())
                if not active_symbols:
                    active_symbols = default_symbols

                for symbol in active_symbols:
                    try:
                        if prediction_service is None:
                            prediction_service = get_prediction_service()

                        # Generate signal
                        signal = await prediction_service.generate_signal(
                            symbol=symbol,
                            timeframe="15m",
                            rr_config="rr_2_1"
                        )

                        # Get attention info
                        attention = await prediction_service.get_attention_info(symbol, "5m")

                        # Build message
                        message = {
                            "type": "signal",
                            "symbol": symbol,
                            "timestamp": datetime.utcnow().isoformat(),
                            "data": {
                                "signal_id": signal.signal_id,
                                "direction": signal.direction.value,
                                "entry_price": signal.entry_price,
                                "stop_loss": signal.stop_loss,
                                "take_profit": signal.take_profit,
                                "confidence": signal.confidence_score,
                                "amd_phase": signal.amd_phase.value,
                                "valid_until": signal.valid_until.isoformat(),
                                "attention": {
                                    "score": attention.attention_score if attention else 1.0,
                                    "flow_class": attention.flow_class if attention else 1,
                                    "is_tradeable": attention.is_tradeable if attention else True
                                } if attention else None
                            }
                        }

                        # Send to symbol subscribers
                        await signal_manager.send_to_symbol(symbol, message)

                    except Exception as e:
                        logger.debug(f"Signal generation failed for {symbol}: {e}")
                        continue

            # Wait before next cycle (60 seconds)
            await asyncio.sleep(60)

        except asyncio.CancelledError:
            logger.info("Background signal generation task cancelled")
            break
        except Exception as e:
            logger.error(f"Background signal task error: {e}")
            await asyncio.sleep(10)


@app.on_event("startup")
async def start_signal_streaming():
    """Start background signal streaming on app startup"""
    global _signal_generation_task
    _signal_generation_task = asyncio.create_task(generate_signals_background())


@app.on_event("shutdown")
async def stop_signal_streaming():
    """Stop background signal streaming on app shutdown"""
    global _signal_generation_task
    if _signal_generation_task:
        _signal_generation_task.cancel()
        try:
            await _signal_generation_task
        except asyncio.CancelledError:
            pass


@app.websocket("/ws/signals")
async def websocket_signals(websocket: WebSocket):
    """
    WebSocket endpoint for real-time trading signals.

    Protocol:
    1. Connect to /ws/signals
    2. Send subscription message: {"action": "subscribe", "symbols": ["XAUUSD", "EURUSD"]}
    3. Receive signals: {"type": "signal", "symbol": "XAUUSD", "data": {...}}
    4. Unsubscribe: {"action": "unsubscribe", "symbols": ["XAUUSD"]}
    5. Request immediate signal: {"action": "get_signal", "symbol": "XAUUSD"}

    Message Types Received:
    - signal: Trading signal for subscribed symbol
    - attention: Attention score update
    - hierarchical: Full L0→L1→L2 prediction
    - error: Error message
    - subscribed: Confirmation of subscription
    """
    await signal_manager.connect(websocket)

    # Send welcome message
    await signal_manager.send_personal(websocket, {
        "type": "connected",
        "message": "Connected to ML Engine signal stream",
        "available_symbols": ["XAUUSD", "EURUSD", "BTCUSD", "GBPUSD", "USDJPY"],
        "instructions": {
            "subscribe": {"action": "subscribe", "symbols": ["XAUUSD"]},
            "unsubscribe": {"action": "unsubscribe", "symbols": ["XAUUSD"]},
            "get_signal": {"action": "get_signal", "symbol": "XAUUSD"},
            "get_attention": {"action": "get_attention", "symbol": "XAUUSD"},
            "get_hierarchical": {"action": "get_hierarchical", "symbol": "XAUUSD"}
        }
    })

    try:
        while True:
            # Receive message from client
            data = await websocket.receive_text()

            try:
                message = json.loads(data)
                action = message.get("action", "")

                if action == "subscribe":
                    symbols = message.get("symbols", [])
                    if symbols:
                        signal_manager.subscribe(websocket, symbols)
                        await signal_manager.send_personal(websocket, {
                            "type": "subscribed",
                            "symbols": symbols,
                            "total_subscriptions": len(signal_manager.get_subscribed_symbols(websocket))
                        })

                elif action == "unsubscribe":
                    symbols = message.get("symbols", [])
                    if symbols:
                        signal_manager.unsubscribe(websocket, symbols)
                        await signal_manager.send_personal(websocket, {
                            "type": "unsubscribed",
                            "symbols": symbols
                        })

                elif action == "get_signal":
                    symbol = message.get("symbol", "XAUUSD").upper()
                    try:
                        if prediction_service:
                            signal = await prediction_service.generate_signal(symbol, "15m", "rr_2_1")
                            await signal_manager.send_personal(websocket, {
                                "type": "signal",
                                "symbol": symbol,
                                "timestamp": datetime.utcnow().isoformat(),
                                "data": {
                                    "signal_id": signal.signal_id,
                                    "direction": signal.direction.value,
                                    "entry_price": signal.entry_price,
                                    "stop_loss": signal.stop_loss,
                                    "take_profit": signal.take_profit,
                                    "confidence": signal.confidence_score,
                                    "amd_phase": signal.amd_phase.value
                                }
                            })
                    except Exception as e:
                        await signal_manager.send_personal(websocket, {
                            "type": "error",
                            "message": f"Failed to get signal: {str(e)}"
                        })

                elif action == "get_attention":
                    symbol = message.get("symbol", "XAUUSD").upper()
                    timeframe = message.get("timeframe", "5m")
                    try:
                        if prediction_service:
                            attention = await prediction_service.get_attention_info(symbol, timeframe)
                            if attention:
                                await signal_manager.send_personal(websocket, {
                                    "type": "attention",
                                    "symbol": symbol,
                                    "timeframe": timeframe,
                                    "data": {
                                        "attention_score": attention.attention_score,
                                        "flow_class": attention.flow_class,
                                        "flow_label": attention.flow_label,
                                        "is_high_flow": attention.is_high_flow,
                                        "is_tradeable": attention.is_tradeable
                                    }
                                })
                            else:
                                await signal_manager.send_personal(websocket, {
                                    "type": "attention",
                                    "symbol": symbol,
                                    "data": None,
                                    "message": "Attention model not available"
                                })
                    except Exception as e:
                        await signal_manager.send_personal(websocket, {
                            "type": "error",
                            "message": f"Failed to get attention: {str(e)}"
                        })

                elif action == "get_hierarchical":
                    symbol = message.get("symbol", "XAUUSD").upper()
                    try:
                        if prediction_service and prediction_service.hierarchical_available:
                            result = await prediction_service.get_hierarchical_prediction(symbol)
                            if result:
                                await signal_manager.send_personal(websocket, {
                                    "type": "hierarchical",
                                    "symbol": symbol,
                                    "timestamp": result.timestamp.isoformat(),
                                    "data": {
                                        "attention_5m": result.attention_score_5m,
                                        "attention_15m": result.attention_score_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_proba,
                                        "should_trade": result.should_trade,
                                        "trade_quality": result.trade_quality
                                    }
                                })
                            else:
                                await signal_manager.send_personal(websocket, {
                                    "type": "error",
                                    "message": "Hierarchical prediction failed"
                                })
                        else:
                            await signal_manager.send_personal(websocket, {
                                "type": "error",
                                "message": "Hierarchical pipeline not available"
                            })
                    except Exception as e:
                        await signal_manager.send_personal(websocket, {
                            "type": "error",
                            "message": f"Failed to get hierarchical prediction: {str(e)}"
                        })

                elif action == "ping":
                    await signal_manager.send_personal(websocket, {
                        "type": "pong",
                        "timestamp": datetime.utcnow().isoformat()
                    })

                else:
                    await signal_manager.send_personal(websocket, {
                        "type": "error",
                        "message": f"Unknown action: {action}"
                    })

            except json.JSONDecodeError:
                await signal_manager.send_personal(websocket, {
                    "type": "error",
                    "message": "Invalid JSON message"
                })

    except WebSocketDisconnect:
        signal_manager.disconnect(websocket)


@app.get("/ws/status", tags=["WebSocket"])
async def websocket_status():
    """Get WebSocket streaming status"""
    return {
        "active_connections": signal_manager.connection_count,
        "subscriptions": signal_manager.subscription_stats,
        "streaming_active": _signal_generation_task is not None and not _signal_generation_task.done(),
        "supported_symbols": ["XAUUSD", "EURUSD", "BTCUSD", "GBPUSD", "USDJPY"],
        "signal_interval_seconds": 60
    }


# =============================================================================
# Historical Signals Query Endpoints
# =============================================================================

class SignalQueryParams(BaseModel):
    """Parameters for signal query"""
    symbol: Optional[str] = None
    direction: Optional[str] = None
    start_date: Optional[datetime] = None
    end_date: Optional[datetime] = None
    min_confidence: Optional[float] = Field(default=None, ge=0.0, le=1.0)
    amd_phase: Optional[str] = None
    timeframe: Optional[str] = None
    is_valid_only: bool = False
    page: int = Field(default=1, ge=1)
    page_size: int = Field(default=50, ge=1, le=200)


class StoredSignalResponse(BaseModel):
    """Single stored signal response"""
    signal_id: str
    symbol: str
    direction: str
    entry_price: float
    stop_loss: float
    take_profit: float
    risk_reward_ratio: float
    prob_tp_first: float
    confidence_score: float
    amd_phase: str
    volatility_regime: str
    timeframe: str
    timestamp: datetime
    valid_until: datetime
    attention_score: Optional[float] = None
    flow_class: Optional[int] = None
    is_high_flow: Optional[bool] = None


class SignalQueryResponse(BaseModel):
    """Signal query response with pagination"""
    signals: List[Dict[str, Any]]
    total_count: int
    page: int
    page_size: int
    has_more: bool
    query_params: Optional[Dict[str, Any]] = None


@app.get("/api/signals/history", response_model=SignalQueryResponse, tags=["Historical Signals"])
async def query_historical_signals(
    symbol: Optional[str] = Query(default=None, description="Filter by symbol"),
    direction: Optional[str] = Query(default=None, description="Filter by direction: long, short, neutral"),
    start_date: Optional[datetime] = Query(default=None, description="Start date filter"),
    end_date: Optional[datetime] = Query(default=None, description="End date filter"),
    min_confidence: Optional[float] = Query(default=None, ge=0.0, le=1.0, description="Minimum confidence"),
    amd_phase: Optional[str] = Query(default=None, description="Filter by AMD phase"),
    timeframe: Optional[str] = Query(default=None, description="Filter by timeframe"),
    is_valid_only: bool = Query(default=False, description="Only return still-valid signals"),
    page: int = Query(default=1, ge=1, description="Page number"),
    page_size: int = Query(default=50, ge=1, le=200, description="Results per page")
):
    """
    Query historical signals with filters.

    Supports filtering by symbol, direction, date range, confidence, AMD phase, etc.
    Returns paginated results sorted by timestamp (newest first).

    Example queries:
    - Get all XAUUSD signals: /api/signals/history?symbol=XAUUSD
    - Get high confidence longs: /api/signals/history?direction=long&min_confidence=0.7
    - Get signals from last 24h: /api/signals/history?start_date=2026-01-24T00:00:00
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    if not prediction_service.signal_store_available:
        raise HTTPException(
            status_code=503,
            detail="Signal store not available"
        )

    result = prediction_service.query_signals(
        symbol=symbol.upper() if symbol else None,
        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 SignalQueryResponse(**result)


@app.get("/api/signals/latest", tags=["Historical Signals"])
async def get_latest_signals(
    symbol: Optional[str] = Query(default=None, description="Filter by symbol"),
    limit: int = Query(default=10, ge=1, le=100, description="Number of signals")
):
    """
    Get the most recent signals.

    Returns the latest signals, optionally filtered by symbol.
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    if not prediction_service.signal_store_available:
        raise HTTPException(
            status_code=503,
            detail="Signal store not available"
        )

    signals = prediction_service.get_latest_signals(
        symbol=symbol.upper() if symbol else None,
        limit=limit
    )

    return {
        "count": len(signals),
        "signals": signals
    }


@app.get("/api/signals/valid", tags=["Historical Signals"])
async def get_valid_signals(
    symbol: Optional[str] = Query(default=None, description="Filter by symbol")
):
    """
    Get all currently valid signals.

    Returns signals that have not yet expired (valid_until > now).
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    if not prediction_service.signal_store_available:
        raise HTTPException(
            status_code=503,
            detail="Signal store not available"
        )

    signals = prediction_service.get_valid_signals(
        symbol=symbol.upper() if symbol else None
    )

    return {
        "count": len(signals),
        "valid_signals": signals
    }


@app.get("/api/signals/{signal_id}", tags=["Historical Signals"])
async def get_signal_by_id(signal_id: str):
    """
    Get a specific signal by its ID.

    Signal IDs are in the format: SIG-XXXXXXXX
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    if not prediction_service.signal_store_available:
        raise HTTPException(
            status_code=503,
            detail="Signal store not available"
        )

    signal = prediction_service.get_signal_by_id(signal_id)

    if signal is None:
        raise HTTPException(
            status_code=404,
            detail=f"Signal {signal_id} not found"
        )

    return signal


@app.get("/api/signals/stats", tags=["Historical Signals"])
async def get_signal_store_stats():
    """
    Get signal store statistics.

    Returns information about stored signals, including counts by symbol
    and direction, capacity, and retention settings.
    """
    global prediction_service

    if prediction_service is None:
        prediction_service = get_prediction_service()

    stats = prediction_service.get_signal_store_stats()
    return stats


# Main entry point
if __name__ == "__main__":
    import uvicorn
    import os
    port = int(os.getenv("PORT", "3083"))
    uvicorn.run(
        "main:app",
        host="0.0.0.0",
        port=port,
        reload=True,
        log_level="info"
    )
