#![allow(unexpected_cfgs)]
#![cfg(feature = "__trading_service_integration")]
//! RED Phase Tests for ML-specific gRPC methods
//!
//! These tests are written FIRST (TDD RED phase) and should initially FAIL.
//! They define the expected behavior of ML trading methods:
//! - SubmitMLOrder: Submit ML-generated trading orders
//! - GetMLPredictions: Query ML prediction history
//! - GetMLPerformance: Get ML model performance metrics
//!
//! Test Data Setup:
//! - Uses real PostgreSQL database with test schema
//! - Seeds ensemble_predictions table with test data
//! - Seeds ml_model_performance table with metrics
//! - Cleans up after each test

use anyhow::Result;
use sqlx::PgPool;
use tokio;
use tonic::Request;
use uuid::Uuid;

use trading_service::proto::trading::{
    trading_service_server::TradingService, MLOrderRequest, MLPerformanceRequest,
    MLPredictionsRequest,
};
use trading_service::services::trading::TradingServiceImpl;
use trading_service::state::TradingServiceState;

/// Helper: Create test trading service instance
async fn create_test_service() -> (TradingServiceImpl, PgPool) {
    // Get database URL from environment
    let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
        "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
    });

    let pool = PgPool::connect(&database_url)
        .await
        .expect("Failed to connect to test database");

    // Create trading service state
    let state = TradingServiceState::new_for_test(pool.clone())
        .await
        .expect("Failed to create trading service state");

    let service = TradingServiceImpl::new(std::sync::Arc::new(state));

    (service, pool)
}

/// Helper: Seed ensemble_predictions table with test data
async fn seed_ensemble_predictions(
    pool: &PgPool,
    symbol: &str,
    action: &str,
    confidence: f64,
) -> Uuid {
    let prediction_id = Uuid::new_v4();

    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence,
            dqn_signal, dqn_confidence, mamba2_signal, mamba2_confidence,
            ppo_signal, ppo_confidence, tft_signal, tft_confidence,
            account_id, timestamp
        ) VALUES (
            $1, $2, $3, $4, $5,
            0.5, 0.5, 0.6, 0.6,
            0.7, 0.7, 0.8, 0.8,
            'test_account', NOW()
        )
        "#,
        prediction_id,
        symbol,
        action,
        confidence,
        confidence,
    )
    .execute(pool)
    .await
    .expect("Failed to seed ensemble_predictions");

    prediction_id
}

/// Helper: Seed ml_model_performance table
async fn seed_model_performance(pool: &PgPool, model_name: &str, accuracy: f64, sharpe_ratio: f64) {
    sqlx::query!(
        r#"
        INSERT INTO ml_model_performance (
            model_name, total_predictions, predictions_with_outcomes,
            correct_predictions, accuracy, avg_pnl, sharpe_ratio
        ) VALUES (
            $1, 100, 80, 60, $2, 150.0, $3
        )
        ON CONFLICT (model_name) DO UPDATE SET
            accuracy = EXCLUDED.accuracy,
            sharpe_ratio = EXCLUDED.sharpe_ratio
        "#,
        model_name,
        accuracy,
        sharpe_ratio,
    )
    .execute(pool)
    .await
    .expect("Failed to seed ml_model_performance");
}

/// Helper: Clean up test data
async fn cleanup_test_data(pool: &PgPool, prediction_ids: &[Uuid]) {
    for id in prediction_ids {
        let _ = sqlx::query!("DELETE FROM ensemble_predictions WHERE id = $1", id)
            .execute(pool)
            .await;
    }
}

// ============================================================================
// RED PHASE TESTS (Should FAIL initially)
// ============================================================================

#[tokio::test]
async fn test_submit_ml_order_with_ensemble() -> Result<()> {
    let (service, pool) = create_test_service().await;

    // Arrange: Create 26 features (OHLCV + 21 technical indicators)
    let features: Vec<f64> = vec![
        // OHLCV (5 features)
        4500.0, 4510.0, 4490.0, 4505.0, 100000.0, // Technical indicators (21 features)
        0.5, 0.6, 0.7, 0.8, 0.9, // RSI, MACD, etc.
        4500.0, 4480.0, // Bollinger bands
        100.0,  // ATR
        4490.0, 4500.0, 4510.0, // EMAs
        0.6, 0.7, 0.8, // Additional indicators
        1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, // More features to reach 26
    ];

    let request = Request::new(MLOrderRequest {
        symbol: "ES.FUT".to_string(),
        account_id: "test_account".to_string(),
        use_ensemble: true,
        model_name: None,
        features,
    });

    // Act
    let response = service.submit_ml_order(request).await?;
    let ml_order = response.into_inner();

    // Assert
    assert!(
        !ml_order.order_id.is_empty(),
        "Order ID should not be empty"
    );
    assert!(
        !ml_order.prediction_id.is_empty(),
        "Prediction ID should not be empty"
    );
    assert!(
        ml_order.action == "BUY" || ml_order.action == "SELL" || ml_order.action == "HOLD",
        "Action should be BUY, SELL, or HOLD"
    );
    assert!(
        ml_order.confidence >= 0.0 && ml_order.confidence <= 1.0,
        "Confidence should be 0-1"
    );
    assert_eq!(
        ml_order.executed,
        ml_order.action != "HOLD",
        "Should execute if not HOLD"
    );

    // Cleanup
    if let Ok(pred_id) = Uuid::parse_str(&ml_order.prediction_id) {
        cleanup_test_data(&pool, &[pred_id]).await;
    }

    Ok(())
}

#[tokio::test]
async fn test_submit_ml_order_below_confidence_threshold() -> Result<()> {
    let (service, pool) = create_test_service().await;

    // Arrange: Create features that should produce low confidence (<60%)
    let features: Vec<f64> = vec![
        // Neutral market conditions (low signal)
        4500.0, 4501.0, 4499.0, 4500.0, 50000.0, 0.5, 0.5, 0.5, 0.5, 0.5, // Neutral indicators
        4500.0, 4500.0, 50.0, // Low volatility
        4500.0, 4500.0, 4500.0, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
    ];

    let request = Request::new(MLOrderRequest {
        symbol: "ES.FUT".to_string(),
        account_id: "test_account".to_string(),
        use_ensemble: true,
        model_name: None,
        features,
    });

    // Act
    let response = service.submit_ml_order(request).await?;
    let ml_order = response.into_inner();

    // Assert
    assert_eq!(ml_order.action, "HOLD", "Should HOLD with low confidence");
    assert!(!ml_order.executed, "Should not execute with low confidence");
    assert!(ml_order.confidence < 0.60, "Confidence should be below 60%");

    // Cleanup
    if let Ok(pred_id) = Uuid::parse_str(&ml_order.prediction_id) {
        cleanup_test_data(&pool, &[pred_id]).await;
    }

    Ok(())
}

#[tokio::test]
async fn test_get_ml_predictions_with_filter() -> Result<()> {
    let (service, pool) = create_test_service().await;

    // Arrange: Seed 3 predictions for ES.FUT
    let pred1 = seed_ensemble_predictions(&pool, "ES.FUT", "BUY", 0.85).await;
    let pred2 = seed_ensemble_predictions(&pool, "ES.FUT", "SELL", 0.75).await;
    let pred3 = seed_ensemble_predictions(&pool, "NQ.FUT", "BUY", 0.90).await;

    let request = Request::new(MLPredictionsRequest {
        symbol: "ES.FUT".to_string(),
        model_name: None,
        limit: 10,
        start_time: None,
        end_time: None,
    });

    // Act
    let response = service.get_ml_predictions(request).await?;
    let predictions = response.into_inner();

    // Assert
    assert!(
        predictions.predictions.len() >= 2,
        "Should return at least 2 ES.FUT predictions"
    );
    assert!(
        predictions.predictions.iter().all(|p| p.symbol == "ES.FUT"),
        "All predictions should be for ES.FUT"
    );
    assert!(
        predictions
            .predictions
            .iter()
            .any(|p| p.ensemble_action == "BUY"),
        "Should include BUY prediction"
    );
    assert!(
        predictions
            .predictions
            .iter()
            .any(|p| p.ensemble_action == "SELL"),
        "Should include SELL prediction"
    );

    // Cleanup
    cleanup_test_data(&pool, &[pred1, pred2, pred3]).await;

    Ok(())
}

#[tokio::test]
async fn test_get_ml_predictions_with_limit() -> Result<()> {
    let (service, pool) = create_test_service().await;

    // Arrange: Seed 5 predictions
    let mut pred_ids = Vec::new();
    for i in 0..5 {
        let action = if i % 2 == 0 { "BUY" } else { "SELL" };
        let pred_id = seed_ensemble_predictions(&pool, "ES.FUT", action, 0.75).await;
        pred_ids.push(pred_id);
    }

    let request = Request::new(MLPredictionsRequest {
        symbol: "ES.FUT".to_string(),
        model_name: None,
        limit: 3,
        start_time: None,
        end_time: None,
    });

    // Act
    let response = service.get_ml_predictions(request).await?;
    let predictions = response.into_inner();

    // Assert
    assert!(
        predictions.predictions.len() <= 3,
        "Should respect limit of 3"
    );

    // Cleanup
    cleanup_test_data(&pool, &pred_ids).await;

    Ok(())
}

#[tokio::test]
async fn test_get_ml_performance_all_models() -> Result<()> {
    let (service, pool) = create_test_service().await;

    // Arrange: Seed performance data for 4 models
    seed_model_performance(&pool, "DQN", 0.65, 1.2).await;
    seed_model_performance(&pool, "MAMBA2", 0.70, 1.5).await;
    seed_model_performance(&pool, "PPO", 0.62, 1.1).await;
    seed_model_performance(&pool, "TFT", 0.68, 1.3).await;

    let request = Request::new(MLPerformanceRequest {
        model_name: None,
        start_time: None,
        end_time: None,
    });

    // Act
    let response = service.get_ml_performance(request).await?;
    let performance = response.into_inner();

    // Assert
    assert!(performance.models.len() >= 4, "Should return all 4 models");
    assert!(
        performance.models.iter().any(|m| m.model_name == "DQN"),
        "Should include DQN"
    );
    assert!(
        performance.models.iter().any(|m| m.model_name == "MAMBA2"),
        "Should include MAMBA2"
    );
    assert!(
        performance.models.iter().any(|m| m.model_name == "PPO"),
        "Should include PPO"
    );
    assert!(
        performance.models.iter().any(|m| m.model_name == "TFT"),
        "Should include TFT"
    );

    // Verify metrics
    let mamba2 = performance
        .models
        .iter()
        .find(|m| m.model_name == "MAMBA2")
        .unwrap();
    assert!(
        (mamba2.accuracy - 0.70).abs() < 0.01,
        "MAMBA2 accuracy should be ~0.70"
    );
    assert!(
        (mamba2.sharpe_ratio - 1.5).abs() < 0.1,
        "MAMBA2 Sharpe should be ~1.5"
    );

    Ok(())
}

#[tokio::test]
async fn test_get_ml_performance_single_model() -> Result<()> {
    let (service, pool) = create_test_service().await;

    // Arrange: Seed performance data
    seed_model_performance(&pool, "DQN", 0.65, 1.2).await;
    seed_model_performance(&pool, "MAMBA2", 0.70, 1.5).await;

    let request = Request::new(MLPerformanceRequest {
        model_name: Some("DQN".to_string()),
        start_time: None,
        end_time: None,
    });

    // Act
    let response = service.get_ml_performance(request).await?;
    let performance = response.into_inner();

    // Assert
    assert_eq!(performance.models.len(), 1, "Should return only DQN");
    assert_eq!(performance.models[0].model_name, "DQN");
    assert!(
        (performance.models[0].accuracy - 0.65).abs() < 0.01,
        "DQN accuracy should be ~0.65"
    );

    Ok(())
}

#[tokio::test]
async fn test_submit_ml_order_invalid_features() -> Result<()> {
    let (service, _pool) = create_test_service().await;

    // Arrange: Provide only 5 features (should require 26)
    let features: Vec<f64> = vec![4500.0, 4510.0, 4490.0, 4505.0, 100000.0];

    let request = Request::new(MLOrderRequest {
        symbol: "ES.FUT".to_string(),
        account_id: "test_account".to_string(),
        use_ensemble: true,
        model_name: None,
        features,
    });

    // Act
    let result = service.submit_ml_order(request).await;

    // Assert
    assert!(result.is_err(), "Should fail with insufficient features");
    let err = result.unwrap_err();
    assert!(
        err.message().contains("26 features"),
        "Error should mention 26 features requirement"
    );

    Ok(())
}
