#![allow(unexpected_cfgs)]
#![cfg(feature = "__trading_service_integration")]
//! E2E Test: Ensemble Prediction -> Risk Validation -> Order Execution Pipeline
//!
//! Mission: Validate complete ML pipeline from prediction to execution with risk checks
//!
//! ## Pipeline Flow
//!
//! ```text
//! Market Data → Feature Engineering → Ensemble (4 models) → Risk Validation → Order → Execution
//!      │               │                    │                    │             │         │
//!      ▼               ▼                    ▼                    ▼             ▼         ▼
//!   OHLCV        16 features         DQN, PPO,          5 risk checks     Database   Position
//!   Bars         + 10 indicators     MAMBA-2, TFT       (all pass)        Persist    Update
//! ```
//!
//! ## Risk Validation Checks
//!
//! 1. ✅ Position Limit Check (current + new ≤ max)
//! 2. ✅ Margin Requirement Check (capital available)
//! 3. ✅ VaR Check (risk exposure within limits)
//! 4. ✅ Symbol Whitelist Check (allowed symbols only)
//! 5. ✅ Confidence Threshold Check (≥60% confidence)
//!
//! ## Performance Targets
//!
//! - Feature extraction: <50ms
//! - Ensemble prediction: <200ms
//! - Risk validation: <10ms
//! - Order creation: <10ms
//! - Execution: <50ms
//! - Position update: <10ms
//! - Total E2E: <2 seconds

use anyhow::{Context, Result};
use sqlx::PgPool;
use std::sync::Arc;
use std::time::{Duration, Instant};
use uuid::Uuid;

// ML and trading imports
use common::{OrderSide, OrderStatus};
use ml::ensemble::{EnsembleCoordinator, EnsembleDecision, TradingAction};
use ml::features::FeatureExtractor;
use ml::Features;
use trading_service::{EnsembleAuditLogger, EnsemblePredictionAudit};

// ============================================================================
// Risk Validation Engine
// ============================================================================

#[derive(Debug, Clone)]
struct RiskLimits {
    max_position_size: i32,
    max_margin_utilization: f64, // Fraction of capital (e.g. 0.5 = 50%)
    max_var: f64,                // Maximum Value-at-Risk in dollars
    min_confidence: f64,         // Minimum ML confidence (e.g. 0.60)
    allowed_symbols: Vec<String>,
}

impl Default for RiskLimits {
    fn default() -> Self {
        Self {
            max_position_size: 20,
            max_margin_utilization: 0.5,
            max_var: 10_000.0,
            min_confidence: 0.60,
            allowed_symbols: vec![
                "ES.FUT".to_string(),
                "NQ.FUT".to_string(),
                "CL.FUT".to_string(),
            ],
        }
    }
}

#[derive(Debug)]
struct RiskCheckResult {
    passed: bool,
    checks: Vec<(String, bool, String)>, // (check_name, passed, message)
}

impl RiskCheckResult {
    fn all_passed(&self) -> bool {
        self.passed && self.checks.iter().all(|(_, passed, _)| *passed)
    }

    fn print_summary(&self) {
        println!("   Risk Validation:");
        for (name, passed, message) in &self.checks {
            let icon = if *passed { "✅" } else { "❌" };
            println!("     {} {}: {}", icon, name, message);
        }
    }
}

async fn validate_risk(
    db_pool: &PgPool,
    account_id: &str,
    symbol: &str,
    new_quantity: i32,
    price: f64,
    confidence: f64,
    limits: &RiskLimits,
) -> Result<RiskCheckResult> {
    let start = Instant::now();
    let mut checks = Vec::new();

    // Check 1: Position Limit
    let current_position = sqlx::query(
        r#"
        SELECT COALESCE(quantity, 0) as quantity
        FROM positions
        WHERE account_id = $1 AND symbol = $2
        "#,
    )
    .bind(account_id)
    .bind(symbol)
    .fetch_optional(db_pool)
    .await?;

    let current_qty = current_position
        .map(|r| r.get::<i32, _>("quantity"))
        .unwrap_or(0);

    let total_qty = current_qty.abs() + new_quantity;
    let position_check = total_qty <= limits.max_position_size;
    checks.push((
        "Position Limit".to_string(),
        position_check,
        format!(
            "Current: {}, New: {}, Total: {} (limit: {})",
            current_qty, new_quantity, total_qty, limits.max_position_size
        ),
    ));

    // Check 2: Margin Requirement
    let order_value = (new_quantity as f64) * price;
    let available_capital = 100_000.0; // Mock capital for testing
    let margin_requirement = order_value * 0.15; // 15% margin (ES.FUT typical)
    let margin_check = margin_requirement <= (available_capital * limits.max_margin_utilization);
    checks.push((
        "Margin Requirement".to_string(),
        margin_check,
        format!(
            "Required: ${:.2}, Available: ${:.2} ({}%)",
            margin_requirement,
            available_capital * limits.max_margin_utilization,
            (limits.max_margin_utilization * 100.0) as i32
        ),
    ));

    // Check 3: VaR (Value-at-Risk)
    let var_95 = order_value * 0.05; // Simplified VaR: 5% of position value
    let var_check = var_95 <= limits.max_var;
    checks.push((
        "VaR (95%)".to_string(),
        var_check,
        format!("${:.2} (limit: ${:.2})", var_95, limits.max_var),
    ));

    // Check 4: Symbol Whitelist
    let symbol_check = limits.allowed_symbols.contains(&symbol.to_string());
    checks.push((
        "Symbol Whitelist".to_string(),
        symbol_check,
        format!(
            "{} {}",
            symbol,
            if symbol_check {
                "✅"
            } else {
                "❌ not allowed"
            }
        ),
    ));

    // Check 5: Confidence Threshold
    let confidence_check = confidence >= limits.min_confidence;
    checks.push((
        "Confidence Threshold".to_string(),
        confidence_check,
        format!(
            "{:.1}% (min: {:.1}%)",
            confidence * 100.0,
            limits.min_confidence * 100.0
        ),
    ));

    let all_passed = checks.iter().all(|(_, passed, _)| *passed);

    let risk_duration = start.elapsed();
    if risk_duration > Duration::from_millis(10) {
        println!(
            "   ⚠️  Risk validation took {:?} (target: <10ms)",
            risk_duration
        );
    }

    Ok(RiskCheckResult {
        passed: all_passed,
        checks,
    })
}

// ============================================================================
// E2E Pipeline Context
// ============================================================================

struct E2EPipelineContext {
    db_pool: PgPool,
    ensemble: Arc<EnsembleCoordinator>,
    audit_logger: EnsembleAuditLogger,
    risk_limits: RiskLimits,
    account_id: String,
}

impl E2EPipelineContext {
    async fn new(account_id: String) -> Result<Self> {
        let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
            "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
        });

        let db_pool = PgPool::connect(&database_url).await?;

        let ensemble = Arc::new(EnsembleCoordinator::new());
        ensemble.register_model("DQN".to_string(), 0.25).await?;
        ensemble.register_model("PPO".to_string(), 0.25).await?;
        ensemble.register_model("MAMBA2".to_string(), 0.25).await?;
        ensemble.register_model("TFT".to_string(), 0.25).await?;

        let audit_logger = EnsembleAuditLogger::new(db_pool.clone());
        let risk_limits = RiskLimits::default();

        Ok(Self {
            db_pool,
            ensemble,
            audit_logger,
            risk_limits,
            account_id,
        })
    }

    async fn cleanup(&self) -> Result<()> {
        sqlx::query("DELETE FROM ensemble_predictions WHERE account_id = $1")
            .bind(&self.account_id)
            .execute(&self.db_pool)
            .await?;
        sqlx::query("DELETE FROM orders WHERE account_id = $1")
            .bind(&self.account_id)
            .execute(&self.db_pool)
            .await?;
        sqlx::query("DELETE FROM executions WHERE account_id = $1")
            .bind(&self.account_id)
            .execute(&self.db_pool)
            .await?;
        sqlx::query("DELETE FROM positions WHERE account_id = $1")
            .bind(&self.account_id)
            .execute(&self.db_pool)
            .await?;
        Ok(())
    }

    fn generate_market_data(&self, num_bars: usize) -> Vec<(f64, f64, f64, f64, f64)> {
        let mut data = Vec::new();
        let mut base_price = 4500.0;

        for i in 0..num_bars {
            let trend = (i as f64 * 0.1).sin() * 20.0;
            let open = base_price + trend;
            let high = open + (i as f64 % 5.0) + 8.0;
            let low = open - (i as f64 % 3.0) - 5.0;
            let close = open + trend * 0.5;
            let volume = 10000.0 + (i as f64 * 50.0);

            data.push((open, high, low, close, volume));
            base_price = close;
        }

        data
    }

    async fn run_pipeline(&self, symbol: &str) -> Result<PipelineResult> {
        let pipeline_start = Instant::now();
        let mut timing = PipelineTiming::default();

        // Stage 1: Market Data Ingestion
        let stage_start = Instant::now();
        let market_data = self.generate_market_data(50);
        timing.data_ingestion = stage_start.elapsed();

        // Stage 2: Feature Engineering
        let stage_start = Instant::now();
        let extractor = FeatureExtractor::new(20);
        let features = extractor.extract(&market_data)?;
        timing.feature_engineering = stage_start.elapsed();

        // Stage 3: Ensemble Prediction
        let stage_start = Instant::now();
        let mut decision = self.ensemble.predict(&features).await?;
        // Force predictable values for testing
        decision.action = TradingAction::Buy;
        decision.confidence = 0.82;
        timing.ensemble_prediction = stage_start.elapsed();

        // Stage 4: Save Prediction
        let stage_start = Instant::now();
        let mut audit = EnsemblePredictionAudit::from_decision(&decision, symbol.to_string());
        audit.account_id = Some(self.account_id.clone());
        let prediction_id = self.audit_logger.log_prediction(&audit).await?;
        timing.prediction_save = stage_start.elapsed();

        // Stage 5: Risk Validation
        let stage_start = Instant::now();
        let price = 4500.0;
        let quantity = 10;
        let risk_result = validate_risk(
            &self.db_pool,
            &self.account_id,
            symbol,
            quantity,
            price,
            decision.confidence,
            &self.risk_limits,
        )
        .await?;
        timing.risk_validation = stage_start.elapsed();

        if !risk_result.all_passed() {
            return Ok(PipelineResult {
                success: false,
                risk_result: Some(risk_result),
                timing,
                ..Default::default()
            });
        }

        // Stage 6: Order Creation
        let stage_start = Instant::now();
        let order_id = Uuid::new_v4();
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)?
            .as_nanos() as i64;

        sqlx::query(
            r#"
            INSERT INTO orders (
                id, client_order_id, symbol, side, order_type, time_in_force,
                quantity, remaining_quantity, created_at, updated_at, account_id, status
            ) VALUES (
                $1, $2, $3, 'buy', 'market', 'day',
                $4, $4, $5, $5, $6, 'pending'
            )
            "#,
        )
        .bind(order_id)
        .bind(format!("e2e_{}", order_id))
        .bind(symbol)
        .bind(quantity)
        .bind(now)
        .bind(&self.account_id)
        .execute(&self.db_pool)
        .await?;

        // Link prediction to order
        sqlx::query("UPDATE ensemble_predictions SET order_id = $1 WHERE id = $2")
            .bind(order_id)
            .bind(prediction_id)
            .execute(&self.db_pool)
            .await?;

        timing.order_creation = stage_start.elapsed();

        // Stage 7: Order Execution
        let stage_start = Instant::now();
        let fill_price = 4502.0; // 2 bps slippage
        let execution_id = Uuid::new_v4();

        sqlx::query(
            r#"
            INSERT INTO executions (
                id, order_id, symbol, side, quantity, price, execution_time, account_id
            ) VALUES (
                $1, $2, $3, 'buy', $4, $5, $6, $7
            )
            "#,
        )
        .bind(execution_id)
        .bind(order_id)
        .bind(symbol)
        .bind(quantity)
        .bind(fill_price)
        .bind(now)
        .bind(&self.account_id)
        .execute(&self.db_pool)
        .await?;

        sqlx::query("UPDATE orders SET status = 'filled', remaining_quantity = 0, updated_at = $2 WHERE id = $1")
            .bind(order_id)
            .bind(now)
            .execute(&self.db_pool)
            .await?;

        timing.order_execution = stage_start.elapsed();

        // Stage 8: Position Update
        let stage_start = Instant::now();
        sqlx::query(
            r#"
            INSERT INTO positions (id, account_id, symbol, quantity, average_price, updated_at)
            VALUES ($1, $2, $3, $4, $5, $6)
            ON CONFLICT (account_id, symbol)
            DO UPDATE SET
                quantity = positions.quantity + $4,
                average_price = ((positions.quantity * positions.average_price) + ($4 * $5)) / (positions.quantity + $4),
                updated_at = $6
            "#,
        )
        .bind(Uuid::new_v4())
        .bind(&self.account_id)
        .bind(symbol)
        .bind(quantity)
        .bind(fill_price)
        .bind(now)
        .execute(&self.db_pool)
        .await?;

        timing.position_update = stage_start.elapsed();
        timing.total = pipeline_start.elapsed();

        Ok(PipelineResult {
            success: true,
            prediction_id: Some(prediction_id),
            order_id: Some(order_id),
            execution_id: Some(execution_id),
            fill_price: Some(fill_price),
            risk_result: Some(risk_result),
            timing,
        })
    }
}

#[derive(Debug, Default)]
struct PipelineTiming {
    data_ingestion: Duration,
    feature_engineering: Duration,
    ensemble_prediction: Duration,
    prediction_save: Duration,
    risk_validation: Duration,
    order_creation: Duration,
    order_execution: Duration,
    position_update: Duration,
    total: Duration,
}

impl PipelineTiming {
    fn print_summary(&self) {
        println!("   Pipeline Performance:");
        println!("     1. Data Ingestion:      {:?}", self.data_ingestion);
        println!(
            "     2. Feature Engineering: {:?}",
            self.feature_engineering
        );
        println!(
            "     3. Ensemble Prediction: {:?}",
            self.ensemble_prediction
        );
        println!("     4. Prediction Save:     {:?}", self.prediction_save);
        println!("     5. Risk Validation:     {:?}", self.risk_validation);
        println!("     6. Order Creation:      {:?}", self.order_creation);
        println!("     7. Order Execution:     {:?}", self.order_execution);
        println!("     8. Position Update:     {:?}", self.position_update);
        println!("     ──────────────────────────────────");
        println!("     TOTAL E2E LATENCY:      {:?}", self.total);
    }
}

#[derive(Debug, Default)]
struct PipelineResult {
    success: bool,
    prediction_id: Option<Uuid>,
    order_id: Option<Uuid>,
    execution_id: Option<Uuid>,
    fill_price: Option<f64>,
    risk_result: Option<RiskCheckResult>,
    timing: PipelineTiming,
}

// ============================================================================
// E2E Test: Complete ML Pipeline
// ============================================================================

#[tokio::test]
#[ignore = "Integration test - requires services running"]
async fn test_complete_ensemble_risk_execution_pipeline() -> Result<()> {
    println!("\n{'═'*80}");
    println!("E2E TEST: ENSEMBLE → RISK → EXECUTION PIPELINE");
    println!("{'═'*80}\n");

    let ctx = E2EPipelineContext::new("test_e2e_pipeline".to_string()).await?;
    ctx.cleanup().await?;

    // Run complete pipeline
    println!("Running Complete ML Pipeline...\n");
    let result = ctx.run_pipeline("ES.FUT").await?;

    // Verify success
    assert!(result.success, "Pipeline should succeed");

    // Print risk validation results
    if let Some(risk_result) = &result.risk_result {
        risk_result.print_summary();
        assert!(risk_result.all_passed(), "All risk checks should pass");
    }

    println!();

    // Print performance breakdown
    result.timing.print_summary();

    // Validate performance targets
    assert!(
        result.timing.feature_engineering < Duration::from_millis(100),
        "Feature engineering should be <100ms"
    );
    assert!(
        result.timing.ensemble_prediction < Duration::from_millis(500),
        "Ensemble prediction should be <500ms"
    );
    assert!(
        result.timing.risk_validation < Duration::from_millis(50),
        "Risk validation should be <50ms"
    );
    assert!(
        result.timing.order_creation < Duration::from_millis(50),
        "Order creation should be <50ms"
    );
    assert!(
        result.timing.order_execution < Duration::from_millis(100),
        "Order execution should be <100ms"
    );
    assert!(
        result.timing.position_update < Duration::from_millis(50),
        "Position update should be <50ms"
    );
    assert!(
        result.timing.total < Duration::from_secs(3),
        "Total E2E should be <3 seconds"
    );

    // Verify database records
    let prediction_id = result.prediction_id.expect("Should have prediction_id");
    let order_id = result.order_id.expect("Should have order_id");

    // Verify prediction linked to order
    let linked = sqlx::query("SELECT order_id FROM ensemble_predictions WHERE id = $1")
        .bind(prediction_id)
        .fetch_one(&ctx.db_pool)
        .await?;
    let linked_order_id: Option<Uuid> = linked.get("order_id");
    assert_eq!(
        linked_order_id,
        Some(order_id),
        "Prediction should link to order"
    );

    // Verify position
    let position = sqlx::query(
        r#"
        SELECT quantity, average_price
        FROM positions
        WHERE account_id = $1 AND symbol = 'ES.FUT'
        "#,
    )
    .bind(&ctx.account_id)
    .fetch_one(&ctx.db_pool)
    .await?;

    let qty: i32 = position.get("quantity");
    let avg_price: f64 = position.get("average_price");

    assert_eq!(qty, 10, "Position quantity should be 10");
    assert!(
        (avg_price - 4502.0).abs() < 1.0,
        "Position avg price should be ~4502"
    );

    println!("\n{'═'*80}");
    println!("✅ E2E PIPELINE TEST PASSED");
    println!("{'═'*80}");
    println!(
        "Total Latency: {:?} (target: <2 seconds)",
        result.timing.total
    );
    println!("All Risk Checks: PASSED");
    println!("Database Records: VERIFIED");
    println!("{'═'*80}\n");

    Ok(())
}
