//! Comprehensive Prometheus Metrics for ML Trading Operations
//!
//! This module provides production-grade metrics tracking for ML-powered trading
//! operations, covering predictions, orders, performance, and ensemble aggregation.
//!
//! ## Metric Categories
//!
//! 1. **ML Prediction Metrics**: Track model predictions, confidence, and accuracy
//! 2. **ML Order Metrics**: Monitor order submission, fills, and rejections
//! 3. **ML Performance Metrics**: Track Sharpe ratio, win rate, returns
//! 4. **Ensemble Metrics**: Monitor agreement, disagreement, and voting patterns
//!
//! ## Integration
//!
//! These metrics complement existing metrics in:
//! - `ml_metrics.rs`: Model health and inference metrics
//! - `ensemble_metrics.rs`: Ensemble-specific aggregation metrics
//! - `metrics_server.rs`: HTTP server for Prometheus scraping

// Metric registration failures are fatal — handled via abort() pattern

use once_cell::sync::Lazy;
use prometheus::{
    register_counter_vec, register_gauge_vec, register_histogram_vec, register_int_gauge_vec,
    CounterVec, GaugeVec, HistogramVec, IntGaugeVec,
};

// ============================================================================
// ML Prediction Metrics
// ============================================================================

/// Counter for ML predictions by model, symbol, and action
///
/// Labels:
/// - model_id: DQN, PPO, MAMBA-2, TFT, TLOB
/// - symbol: ES.FUT, NQ.FUT, ZN.FUT, 6E.FUT, etc.
/// - action: buy, sell, hold
///
/// Use this to track prediction volume and action distribution per model
pub static ML_PREDICTIONS_TOTAL: Lazy<CounterVec> = Lazy::new(|| {
    register_counter_vec!(
        "ml_predictions_total",
        "Total number of ML predictions by model, symbol, and action type",
        &["model_id", "symbol", "action"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_predictions_total: {e}");
        std::process::abort()
    })
});

/// Histogram for ML prediction confidence scores (0.0-1.0)
///
/// Labels:
/// - model_id: DQN, PPO, MAMBA-2, TFT, TLOB
///
/// Buckets: 0.1, 0.3, 0.5, 0.7, 0.8, 0.9, 0.95, 1.0
/// Low confidence (<0.5) may indicate model uncertainty or regime shift
pub static ML_PREDICTIONS_CONFIDENCE: Lazy<HistogramVec> = Lazy::new(|| {
    register_histogram_vec!(
        "ml_predictions_confidence",
        "Distribution of ML model prediction confidence scores",
        &["model_id"],
        vec![0.1, 0.3, 0.5, 0.7, 0.8, 0.9, 0.95, 1.0]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_predictions_confidence: {e}");
        std::process::abort()
    })
});

/// Gauge for ML model prediction accuracy (0-100 percent)
///
/// Labels:
/// - model_id: DQN, PPO, MAMBA-2, TFT, TLOB
///
/// Updated hourly from PostgreSQL ml_predictions table
/// Target: >55% for production deployment
pub static ML_PREDICTION_ACCURACY: Lazy<GaugeVec> = Lazy::new(|| {
    register_gauge_vec!(
        "ml_prediction_accuracy",
        "ML model prediction accuracy percentage (updated hourly)",
        &["model_id"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_prediction_accuracy: {e}");
        std::process::abort()
    })
});

/// Counter for ensemble voting events by symbol
///
/// Labels:
/// - symbol: Trading symbol
///
/// Tracks how many times the ensemble voted on predictions
pub static ML_ENSEMBLE_VOTES_TOTAL: Lazy<CounterVec> = Lazy::new(|| {
    register_counter_vec!(
        "ml_ensemble_votes_total",
        "Total ensemble voting events by symbol",
        &["symbol"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_ensemble_votes_total: {e}");
        std::process::abort()
    })
});

/// Gauge for timestamp of last prediction by model (Unix epoch seconds)
///
/// Labels:
/// - model_id: DQN, PPO, MAMBA-2, TFT, TLOB
///
/// Use for staleness detection: `time() - ml_model_last_prediction_time > 3600`
pub static ML_MODEL_LAST_PREDICTION_TIME: Lazy<IntGaugeVec> = Lazy::new(|| {
    register_int_gauge_vec!(
        "ml_model_last_prediction_time",
        "Unix timestamp of last prediction from model (for staleness detection)",
        &["model_id"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_model_last_prediction_time: {e}");
        std::process::abort()
    })
});

// ============================================================================
// ML Order Metrics
// ============================================================================

/// Counter for ML-generated orders submitted to exchange
///
/// Labels:
/// - model_id: DQN, PPO, MAMBA-2, TFT, TLOB
/// - symbol: Trading symbol
///
/// Tracks order submission volume by model and symbol
pub static ML_ORDERS_SUBMITTED_TOTAL: Lazy<CounterVec> = Lazy::new(|| {
    register_counter_vec!(
        "ml_orders_submitted_total",
        "Total ML-generated orders submitted to exchange",
        &["model_id", "symbol"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_orders_submitted_total: {e}");
        std::process::abort()
    })
});

/// Counter for ML-generated orders successfully filled
///
/// Labels:
/// - model_id: DQN, PPO, MAMBA-2, TFT, TLOB
/// - symbol: Trading symbol
///
/// Fill rate = filled_total / submitted_total
pub static ML_ORDERS_FILLED_TOTAL: Lazy<CounterVec> = Lazy::new(|| {
    register_counter_vec!(
        "ml_orders_filled_total",
        "Total ML-generated orders successfully filled",
        &["model_id", "symbol"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_orders_filled_total: {e}");
        std::process::abort()
    })
});

/// Counter for ML-generated orders rejected by exchange or risk system
///
/// Labels:
/// - model_id: DQN, PPO, MAMBA-2, TFT, TLOB
/// - symbol: Trading symbol
/// - reason: risk_limit, insufficient_margin, invalid_price, market_closed, etc.
///
/// High rejection rate may indicate:
/// - Risk limits too tight
/// - Model producing invalid signals
/// - Market microstructure issues
pub static ML_ORDERS_REJECTED_TOTAL: Lazy<CounterVec> = Lazy::new(|| {
    register_counter_vec!(
        "ml_orders_rejected_total",
        "Total ML-generated orders rejected with reason",
        &["model_id", "symbol", "reason"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_orders_rejected_total: {e}");
        std::process::abort()
    })
});

// ============================================================================
// ML Performance Metrics
// ============================================================================

/// Gauge for ML model Sharpe ratio (risk-adjusted returns)
///
/// Labels:
/// - model_id: DQN, PPO, MAMBA-2, TFT, TLOB
///
/// Calculated as: (average_return - risk_free_rate) / std_dev_returns
/// Annualized using √252 trading days
/// Target: >1.5 for production trading
pub static ML_MODEL_SHARPE_RATIO: Lazy<GaugeVec> = Lazy::new(|| {
    register_gauge_vec!(
        "ml_model_sharpe_ratio",
        "ML model Sharpe ratio (risk-adjusted returns)",
        &["model_id"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_model_sharpe_ratio: {e}");
        std::process::abort()
    })
});

/// Gauge for ML model win rate (percentage of profitable trades)
///
/// Labels:
/// - model_id: DQN, PPO, MAMBA-2, TFT, TLOB
///
/// Value: 0.0 to 1.0 (0% to 100%)
/// Target: >0.55 (55% win rate)
pub static ML_MODEL_WIN_RATE: Lazy<GaugeVec> = Lazy::new(|| {
    register_gauge_vec!(
        "ml_model_win_rate",
        "ML model win rate (percentage of profitable trades)",
        &["model_id"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_model_win_rate: {e}");
        std::process::abort()
    })
});

/// Gauge for ML model average return per trade (dollars)
///
/// Labels:
/// - model_id: DQN, PPO, MAMBA-2, TFT, TLOB
///
/// Updated periodically from PostgreSQL ml_predictions table
/// Tracks profitability per trade
pub static ML_MODEL_AVG_RETURN: Lazy<GaugeVec> = Lazy::new(|| {
    register_gauge_vec!(
        "ml_model_avg_return",
        "ML model average return per trade (dollars)",
        &["model_id"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_model_avg_return: {e}");
        std::process::abort()
    })
});

/// Histogram for ML model inference latency (microseconds)
///
/// Labels:
/// - model_id: DQN, PPO, MAMBA-2, TFT, TLOB
///
/// Buckets: 10, 50, 100, 500, 1000, 5000, 10000 μs
/// Target: P99 < 1000μs (1ms) for real-time trading
pub static ML_MODEL_INFERENCE_LATENCY: Lazy<HistogramVec> = Lazy::new(|| {
    register_histogram_vec!(
        "ml_model_inference_latency",
        "ML model inference latency in microseconds",
        &["model_id"],
        vec![10.0, 50.0, 100.0, 500.0, 1000.0, 5000.0, 10000.0]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_model_inference_latency: {e}");
        std::process::abort()
    })
});

/// Gauge for ML model cumulative PnL (dollars)
///
/// Labels:
/// - model_id: DQN, PPO, MAMBA-2, TFT, TLOB
///
/// Tracks total profit/loss from model since deployment
pub static ML_MODEL_CUMULATIVE_PNL: Lazy<GaugeVec> = Lazy::new(|| {
    register_gauge_vec!(
        "ml_model_cumulative_pnl",
        "ML model cumulative profit/loss in dollars",
        &["model_id"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_model_cumulative_pnl: {e}");
        std::process::abort()
    })
});

/// Gauge for ML model maximum drawdown (dollars)
///
/// Labels:
/// - model_id: DQN, PPO, MAMBA-2, TFT, TLOB
///
/// Maximum peak-to-trough decline in PnL
/// Risk metric for capital preservation
pub static ML_MODEL_MAX_DRAWDOWN: Lazy<GaugeVec> = Lazy::new(|| {
    register_gauge_vec!(
        "ml_model_max_drawdown",
        "ML model maximum drawdown in dollars",
        &["model_id"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_model_max_drawdown: {e}");
        std::process::abort()
    })
});

// ============================================================================
// Ensemble Metrics
// ============================================================================

/// Gauge for ensemble agreement rate (0.0-1.0)
///
/// Agreement rate = models with same prediction / total models
/// Value: 1.0 = all models agree, 0.0 = all models disagree
/// Low agreement (<0.5) may indicate regime shift or data quality issues
pub static ML_ENSEMBLE_AGREEMENT_RATE: Lazy<GaugeVec> = Lazy::new(|| {
    register_gauge_vec!(
        "ml_ensemble_agreement_rate",
        "Ensemble model agreement rate (0.0=disagree, 1.0=agree)",
        &["symbol"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_ensemble_agreement_rate: {e}");
        std::process::abort()
    })
});

/// Counter for high disagreement events (>50% models disagree)
///
/// Labels:
/// - symbol: Trading symbol
/// - threshold: 0.5, 0.7, 0.9 (disagreement threshold)
///
/// High disagreement events indicate:
/// - Market regime uncertainty
/// - Potential data quality issues
/// - Conflicting model strategies
pub static ML_ENSEMBLE_DISAGREEMENT_EVENTS: Lazy<CounterVec> = Lazy::new(|| {
    register_counter_vec!(
        "ml_ensemble_disagreement_events",
        "Count of high ensemble disagreement events",
        &["symbol", "threshold"]
    )
    .unwrap_or_else(|e| {
        eprintln!("FATAL: Failed to register ml_ensemble_disagreement_events: {e}");
        std::process::abort()
    })
});

// ============================================================================
// Helper Functions for Recording Metrics
// ============================================================================

/// Record a ML prediction with all relevant metrics
///
/// # Arguments
/// * `model_id` - Model identifier (e.g., "DQN", "MAMBA-2")
/// * `symbol` - Trading symbol (e.g., "ES.FUT")
/// * `action` - Prediction action ("buy", "sell", "hold")
/// * `confidence` - Prediction confidence (0.0-1.0)
/// * `latency_us` - Inference latency in microseconds
pub fn record_ml_prediction(
    model_id: &str,
    symbol: &str,
    action: &str,
    confidence: f64,
    latency_us: f64,
) {
    // Increment prediction counter
    ML_PREDICTIONS_TOTAL
        .with_label_values(&[model_id, symbol, action])
        .inc();

    // Record confidence distribution
    ML_PREDICTIONS_CONFIDENCE
        .with_label_values(&[model_id])
        .observe(confidence);

    // Record inference latency
    ML_MODEL_INFERENCE_LATENCY
        .with_label_values(&[model_id])
        .observe(latency_us);

    // Update last prediction timestamp
    let now = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_secs() as i64)
        .unwrap_or(0);
    ML_MODEL_LAST_PREDICTION_TIME
        .with_label_values(&[model_id])
        .set(now);
}

/// Record a ML order submission
///
/// # Arguments
/// * `model_id` - Model identifier
/// * `symbol` - Trading symbol
pub fn record_ml_order_submitted(model_id: &str, symbol: &str) {
    ML_ORDERS_SUBMITTED_TOTAL
        .with_label_values(&[model_id, symbol])
        .inc();
}

/// Record a ML order fill
///
/// # Arguments
/// * `model_id` - Model identifier
/// * `symbol` - Trading symbol
pub fn record_ml_order_filled(model_id: &str, symbol: &str) {
    ML_ORDERS_FILLED_TOTAL
        .with_label_values(&[model_id, symbol])
        .inc();
}

/// Record a ML order rejection
///
/// # Arguments
/// * `model_id` - Model identifier
/// * `symbol` - Trading symbol
/// * `reason` - Rejection reason
pub fn record_ml_order_rejected(model_id: &str, symbol: &str, reason: &str) {
    ML_ORDERS_REJECTED_TOTAL
        .with_label_values(&[model_id, symbol, reason])
        .inc();
}

/// Update ML model performance metrics
///
/// # Arguments
/// * `model_id` - Model identifier
/// * `sharpe_ratio` - Sharpe ratio (risk-adjusted returns)
/// * `win_rate` - Win rate (0.0-1.0)
/// * `avg_return` - Average return per trade (dollars)
/// * `accuracy` - Prediction accuracy (0.0-1.0)
pub fn update_ml_model_performance(
    model_id: &str,
    sharpe_ratio: f64,
    win_rate: f64,
    avg_return: f64,
    accuracy: f64,
) {
    ML_MODEL_SHARPE_RATIO
        .with_label_values(&[model_id])
        .set(sharpe_ratio);

    ML_MODEL_WIN_RATE
        .with_label_values(&[model_id])
        .set(win_rate);

    ML_MODEL_AVG_RETURN
        .with_label_values(&[model_id])
        .set(avg_return);

    ML_PREDICTION_ACCURACY
        .with_label_values(&[model_id])
        .set(accuracy * 100.0); // Convert to percentage
}

/// Update ML model PnL metrics
///
/// # Arguments
/// * `model_id` - Model identifier
/// * `cumulative_pnl` - Total PnL (dollars)
/// * `max_drawdown` - Maximum drawdown (dollars)
pub fn update_ml_model_pnl(model_id: &str, cumulative_pnl: f64, max_drawdown: f64) {
    ML_MODEL_CUMULATIVE_PNL
        .with_label_values(&[model_id])
        .set(cumulative_pnl);

    ML_MODEL_MAX_DRAWDOWN
        .with_label_values(&[model_id])
        .set(max_drawdown);
}

/// Record ensemble voting metrics
///
/// # Arguments
/// * `symbol` - Trading symbol
/// * `agreement_rate` - Model agreement rate (0.0-1.0)
pub fn record_ensemble_vote(symbol: &str, agreement_rate: f64) {
    // Increment vote counter
    ML_ENSEMBLE_VOTES_TOTAL.with_label_values(&[symbol]).inc();

    // Update agreement rate
    ML_ENSEMBLE_AGREEMENT_RATE
        .with_label_values(&[symbol])
        .set(agreement_rate);

    // Record high disagreement events (complement of agreement)
    let disagreement_rate = 1.0 - agreement_rate;

    if disagreement_rate >= 0.5 {
        ML_ENSEMBLE_DISAGREEMENT_EVENTS
            .with_label_values(&[symbol, "0.5"])
            .inc();
    }
    if disagreement_rate >= 0.7 {
        ML_ENSEMBLE_DISAGREEMENT_EVENTS
            .with_label_values(&[symbol, "0.7"])
            .inc();
    }
    if disagreement_rate >= 0.9 {
        ML_ENSEMBLE_DISAGREEMENT_EVENTS
            .with_label_values(&[symbol, "0.9"])
            .inc();
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_record_ml_prediction() {
        let model_id = "DQN";
        let symbol = "ES.FUT";
        let action = "buy";
        let confidence = 0.85;
        let latency_us = 125.5;

        // Record prediction
        record_ml_prediction(model_id, symbol, action, confidence, latency_us);

        // Verify metrics are recorded
        let pred_count = ML_PREDICTIONS_TOTAL
            .with_label_values(&[model_id, symbol, action])
            .get();
        assert!(pred_count >= 1.0);
    }

    #[test]
    fn test_record_ml_order_lifecycle() {
        let model_id = "MAMBA-2";
        let symbol = "ZN.FUT";

        // Record order submission
        record_ml_order_submitted(model_id, symbol);
        let submitted = ML_ORDERS_SUBMITTED_TOTAL
            .with_label_values(&[model_id, symbol])
            .get();
        assert!(submitted >= 1.0);

        // Record order fill
        record_ml_order_filled(model_id, symbol);
        let filled = ML_ORDERS_FILLED_TOTAL
            .with_label_values(&[model_id, symbol])
            .get();
        assert!(filled >= 1.0);

        // Record order rejection
        record_ml_order_rejected(model_id, symbol, "risk_limit");
        let rejected = ML_ORDERS_REJECTED_TOTAL
            .with_label_values(&[model_id, symbol, "risk_limit"])
            .get();
        assert!(rejected >= 1.0);
    }

    #[test]
    fn test_update_ml_model_performance() {
        let model_id = "PPO";
        let sharpe = 1.85;
        let win_rate = 0.62;
        let avg_return = 125.50;
        let accuracy = 0.68;

        update_ml_model_performance(model_id, sharpe, win_rate, avg_return, accuracy);

        // Verify Sharpe ratio
        let recorded_sharpe = ML_MODEL_SHARPE_RATIO.with_label_values(&[model_id]).get();
        assert!((recorded_sharpe - sharpe).abs() < 1e-6);

        // Verify win rate
        let recorded_win_rate = ML_MODEL_WIN_RATE.with_label_values(&[model_id]).get();
        assert!((recorded_win_rate - win_rate).abs() < 1e-6);

        // Verify accuracy (converted to percentage)
        let recorded_accuracy = ML_PREDICTION_ACCURACY.with_label_values(&[model_id]).get();
        assert!((recorded_accuracy - accuracy * 100.0).abs() < 1e-6);
    }

    #[test]
    fn test_update_ml_model_pnl() {
        let model_id = "TFT";
        let cumulative_pnl = 5432.10;
        let max_drawdown = -1250.75;

        update_ml_model_pnl(model_id, cumulative_pnl, max_drawdown);

        let recorded_pnl = ML_MODEL_CUMULATIVE_PNL.with_label_values(&[model_id]).get();
        assert!((recorded_pnl - cumulative_pnl).abs() < 1e-6);

        let recorded_drawdown = ML_MODEL_MAX_DRAWDOWN.with_label_values(&[model_id]).get();
        assert!((recorded_drawdown - max_drawdown).abs() < 1e-6);
    }

    #[test]
    fn test_record_ensemble_vote_high_agreement() {
        let symbol = "6E.FUT";
        let agreement_rate = 0.85; // 85% models agree

        let before_votes = ML_ENSEMBLE_VOTES_TOTAL.with_label_values(&[symbol]).get() as i64;

        record_ensemble_vote(symbol, agreement_rate);

        // Verify vote count incremented
        let after_votes = ML_ENSEMBLE_VOTES_TOTAL.with_label_values(&[symbol]).get() as i64;
        assert_eq!(after_votes, before_votes + 1);

        // Verify agreement rate
        let recorded_agreement = ML_ENSEMBLE_AGREEMENT_RATE
            .with_label_values(&[symbol])
            .get();
        assert!((recorded_agreement - agreement_rate).abs() < 1e-6);

        // Should NOT trigger disagreement events (disagreement = 0.15)
        // (cannot reliably test counter did not increment in parallel tests)
    }

    #[test]
    fn test_record_ensemble_vote_high_disagreement() {
        let symbol = "NQ.FUT";
        let agreement_rate = 0.25; // 25% agreement = 75% disagreement

        let before_50 = ML_ENSEMBLE_DISAGREEMENT_EVENTS
            .with_label_values(&[symbol, "0.5"])
            .get() as i64;
        let before_70 = ML_ENSEMBLE_DISAGREEMENT_EVENTS
            .with_label_values(&[symbol, "0.7"])
            .get() as i64;
        let before_90 = ML_ENSEMBLE_DISAGREEMENT_EVENTS
            .with_label_values(&[symbol, "0.9"])
            .get() as i64;

        record_ensemble_vote(symbol, agreement_rate);

        // Should trigger 0.5 and 0.7 thresholds, but not 0.9
        let after_50 = ML_ENSEMBLE_DISAGREEMENT_EVENTS
            .with_label_values(&[symbol, "0.5"])
            .get() as i64;
        let after_70 = ML_ENSEMBLE_DISAGREEMENT_EVENTS
            .with_label_values(&[symbol, "0.7"])
            .get() as i64;
        let after_90 = ML_ENSEMBLE_DISAGREEMENT_EVENTS
            .with_label_values(&[symbol, "0.9"])
            .get() as i64;

        assert_eq!(after_50, before_50 + 1);
        assert_eq!(after_70, before_70 + 1);
        assert_eq!(after_90, before_90); // Should not increment
    }

    #[test]
    fn test_ml_prediction_confidence_buckets() {
        let model_id = "TLOB";

        // Test different confidence levels
        let confidences = vec![0.15, 0.45, 0.65, 0.82, 0.92, 0.98];

        for conf in confidences {
            record_ml_prediction(model_id, "CL.FUT", "hold", conf, 50.0);
        }

        // Histogram should have recorded observations
        // (actual bucket verification requires histogram introspection)
    }

    #[test]
    fn test_ml_order_rejection_reasons() {
        let model_id = "DQN";
        let symbol = "GC.FUT";

        // Test various rejection reasons
        let reasons = vec![
            "risk_limit",
            "insufficient_margin",
            "invalid_price",
            "market_closed",
        ];

        for reason in reasons {
            record_ml_order_rejected(model_id, symbol, reason);

            let count = ML_ORDERS_REJECTED_TOTAL
                .with_label_values(&[model_id, symbol, reason])
                .get();
            assert!(count >= 1.0);
        }
    }

    #[test]
    fn test_ml_model_last_prediction_timestamp() {
        let model_id = "MAMBA-2";

        record_ml_prediction(model_id, "ES.FUT", "buy", 0.75, 100.0);

        let timestamp = ML_MODEL_LAST_PREDICTION_TIME
            .with_label_values(&[model_id])
            .get();

        // Timestamp should be recent (within last 60 seconds)
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_secs() as i64;

        assert!(timestamp > 0);
        assert!(now - timestamp < 60);
    }
}
