#![allow(unexpected_cfgs)]
#![cfg(feature = "__trading_service_integration")]
//! E2E Test Suite for Paper Trading Executor
//!
//! This test suite provides comprehensive TDD validation for the paper trading executor,
//! which converts ensemble predictions into simulated orders. Tests cover the entire
//! prediction-to-order pipeline including SQL queries, enum conversion, position tracking,
//! error handling, and concurrent execution.
//!
//! ## Test Coverage
//! 1. Fetch pending predictions (SQL query with confidence/symbol filters)
//! 2. Prediction to order conversion (BUY→buy, SELL→sell, confidence≥60%)
//! 3. Order creation SQL (INSERT with lowercase enum conversion)
//! 4. Position tracking (HashMap-based position management)
//! 5. Error handling (database errors, invalid predictions)
//! 6. Polling interval timing (100ms interval validation)
//! 7. Concurrent execution (race condition prevention)
//!
//! ## Architecture
//! - Uses sqlx::test macro for automatic database setup/teardown
//! - Tests use real PostgreSQL (not mocks) for SQL validation
//! - Follows TDD principles from Agent 146
//! - Tests both happy path and error cases

use sqlx::PgPool;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use uuid::Uuid;

// Import paper trading executor types
// Note: This will need to be exposed in lib.rs or made pub(crate)
use trading_service::paper_trading_executor::{
    PaperTradingConfig, PaperTradingExecutor, PendingPrediction,
};

// Test database URL
fn get_test_db_url() -> String {
    std::env::var("DATABASE_URL").unwrap_or_else(|_| {
        "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
    })
}

// ============================================================================
// TEST 1: Fetch Pending Predictions
// ============================================================================

#[tokio::test]
async fn test_fetch_pending_predictions() {
    let pool = PgPool::connect(&get_test_db_url())
        .await
        .expect("Failed to connect to test database");

    // Setup: Insert test predictions with various confidence levels
    let pred_high_confidence = Uuid::new_v4();
    let pred_low_confidence = Uuid::new_v4();
    let pred_already_executed = Uuid::new_v4();
    let pred_wrong_symbol = Uuid::new_v4();
    let pred_hold_action = Uuid::new_v4();

    // High confidence BUY - should be fetched
    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
        ) VALUES (
            $1, 'ES.FUT', 'BUY', 0.75, 0.85, 0.10
        )
        "#,
        pred_high_confidence,
    )
    .execute(&pool)
    .await
    .expect("Failed to insert high confidence prediction");

    // Low confidence BUY - should NOT be fetched (< 60%)
    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
        ) VALUES (
            $1, 'ES.FUT', 'BUY', 0.55, 0.55, 0.30
        )
        "#,
        pred_low_confidence,
    )
    .execute(&pool)
    .await
    .expect("Failed to insert low confidence prediction");

    // Already executed (has order_id) - should NOT be fetched
    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate, order_id
        ) VALUES (
            $1, 'ES.FUT', 'BUY', 0.80, 0.90, 0.05, $2
        )
        "#,
        pred_already_executed,
        Uuid::new_v4(),
    )
    .execute(&pool)
    .await
    .expect("Failed to insert already executed prediction");

    // Wrong symbol - should NOT be fetched
    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
        ) VALUES (
            $1, 'UNKNOWN.FUT', 'BUY', 0.75, 0.85, 0.10
        )
        "#,
        pred_wrong_symbol,
    )
    .execute(&pool)
    .await
    .expect("Failed to insert wrong symbol prediction");

    // HOLD action - should NOT be fetched
    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
        ) VALUES (
            $1, 'ES.FUT', 'HOLD', 0.10, 0.70, 0.20
        )
        "#,
        pred_hold_action,
    )
    .execute(&pool)
    .await
    .expect("Failed to insert HOLD prediction");

    // Execute: Query pending predictions
    let config = PaperTradingConfig::default();
    let executor = PaperTradingExecutor::new(pool.clone(), config);

    let predictions = executor
        .fetch_pending_predictions()
        .await
        .expect("Failed to fetch predictions");

    // Assert: Only high confidence BUY/SELL predictions from allowed symbols should be fetched
    assert_eq!(
        predictions.len(),
        1,
        "Only high confidence BUY from ES.FUT should be fetched"
    );
    assert_eq!(predictions[0].id, pred_high_confidence);
    assert_eq!(predictions[0].ensemble_action, "BUY");
    assert!((predictions[0].ensemble_confidence - 0.85).abs() < 0.001);

    // Cleanup
    sqlx::query!(
        "DELETE FROM ensemble_predictions WHERE id = ANY($1)",
        &[
            pred_high_confidence,
            pred_low_confidence,
            pred_already_executed,
            pred_wrong_symbol,
            pred_hold_action
        ]
    )
    .execute(&pool)
    .await
    .expect("Failed to cleanup");

    pool.close().await;
}

// ============================================================================
// TEST 2: Prediction to Order Conversion
// ============================================================================

#[tokio::test]
async fn test_prediction_to_order_conversion() {
    let pool = PgPool::connect(&get_test_db_url())
        .await
        .expect("Failed to connect to test database");

    // Setup: Insert BUY prediction
    let pred_id = Uuid::new_v4();
    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
        ) VALUES (
            $1, 'ES.FUT', 'BUY', 0.75, 0.85, 0.10
        )
        "#,
        pred_id,
    )
    .execute(&pool)
    .await
    .expect("Failed to insert test prediction");

    // Execute: Create executor and execute prediction
    let config = PaperTradingConfig::default();
    let executor = Arc::new(PaperTradingExecutor::new(pool.clone(), config));

    let prediction = PendingPrediction {
        id: pred_id,
        symbol: "ES.FUT".to_string(),
        ensemble_action: "BUY".to_string(),
        ensemble_signal: 0.75,
        ensemble_confidence: 0.85,
    };

    executor
        .execute_prediction(&prediction)
        .await
        .expect("Failed to execute prediction");

    // Assert: Verify order was created with lowercase 'buy'
    let order = sqlx::query!(
        r#"
        SELECT id, symbol, side, status, quantity
        FROM orders
        WHERE symbol = 'ES.FUT'
          AND account_id = 'paper_trading_001'
        ORDER BY created_at DESC
        LIMIT 1
        "#,
    )
    .fetch_one(&pool)
    .await
    .expect("Failed to fetch created order");

    assert_eq!(order.side, "buy", "Order side should be lowercase 'buy'");
    assert_eq!(order.status, "filled", "Order should be filled");
    assert_eq!(order.symbol, "ES.FUT");

    // Assert: Verify prediction is linked to order
    let updated_pred = sqlx::query!(
        r#"
        SELECT order_id
        FROM ensemble_predictions
        WHERE id = $1
        "#,
        pred_id,
    )
    .fetch_one(&pool)
    .await
    .expect("Failed to fetch updated prediction");

    assert!(
        updated_pred.order_id.is_some(),
        "Prediction should be linked to order"
    );

    // Cleanup
    sqlx::query!("DELETE FROM orders WHERE id = $1", order.id)
        .execute(&pool)
        .await
        .expect("Failed to cleanup order");
    sqlx::query!("DELETE FROM ensemble_predictions WHERE id = $1", pred_id)
        .execute(&pool)
        .await
        .expect("Failed to cleanup prediction");

    pool.close().await;
}

// ============================================================================
// TEST 3: Order Creation SQL (BUY and SELL)
// ============================================================================

#[tokio::test]
async fn test_order_creation_sql() {
    let pool = PgPool::connect(&get_test_db_url())
        .await
        .expect("Failed to connect to test database");

    // Test BUY order
    {
        let pred_id = Uuid::new_v4();
        sqlx::query!(
            r#"
            INSERT INTO ensemble_predictions (
                id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
            ) VALUES (
                $1, 'NQ.FUT', 'BUY', 0.70, 0.80, 0.15
            )
            "#,
            pred_id,
        )
        .execute(&pool)
        .await
        .expect("Failed to insert BUY prediction");

        let config = PaperTradingConfig::default();
        let executor = Arc::new(PaperTradingExecutor::new(pool.clone(), config));

        let prediction = PendingPrediction {
            id: pred_id,
            symbol: "NQ.FUT".to_string(),
            ensemble_action: "BUY".to_string(),
            ensemble_signal: 0.70,
            ensemble_confidence: 0.80,
        };

        executor
            .execute_prediction(&prediction)
            .await
            .expect("Failed to execute BUY prediction");

        let order = sqlx::query!(
            r#"
            SELECT side, order_type, status, venue, time_in_force
            FROM orders
            WHERE symbol = 'NQ.FUT' AND account_id = 'paper_trading_001'
            ORDER BY created_at DESC
            LIMIT 1
            "#,
        )
        .fetch_one(&pool)
        .await
        .expect("Failed to fetch BUY order");

        assert_eq!(order.side, "buy");
        assert_eq!(order.order_type, "market");
        assert_eq!(order.status, "filled");
        assert_eq!(order.venue, "PAPER_TRADING");
        assert_eq!(order.time_in_force, "day");

        // Cleanup
        sqlx::query!(
            "DELETE FROM orders WHERE symbol = 'NQ.FUT' AND account_id = 'paper_trading_001'"
        )
        .execute(&pool)
        .await
        .expect("Failed to cleanup BUY order");
        sqlx::query!("DELETE FROM ensemble_predictions WHERE id = $1", pred_id)
            .execute(&pool)
            .await
            .expect("Failed to cleanup BUY prediction");
    }

    // Test SELL order
    {
        let pred_id = Uuid::new_v4();
        sqlx::query!(
            r#"
            INSERT INTO ensemble_predictions (
                id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
            ) VALUES (
                $1, 'ZN.FUT', 'SELL', -0.65, 0.75, 0.20
            )
            "#,
            pred_id,
        )
        .execute(&pool)
        .await
        .expect("Failed to insert SELL prediction");

        let config = PaperTradingConfig::default();
        let executor = Arc::new(PaperTradingExecutor::new(pool.clone(), config));

        let prediction = PendingPrediction {
            id: pred_id,
            symbol: "ZN.FUT".to_string(),
            ensemble_action: "SELL".to_string(),
            ensemble_signal: -0.65,
            ensemble_confidence: 0.75,
        };

        executor
            .execute_prediction(&prediction)
            .await
            .expect("Failed to execute SELL prediction");

        let order = sqlx::query!(
            r#"
            SELECT side, order_type, status
            FROM orders
            WHERE symbol = 'ZN.FUT' AND account_id = 'paper_trading_001'
            ORDER BY created_at DESC
            LIMIT 1
            "#,
        )
        .fetch_one(&pool)
        .await
        .expect("Failed to fetch SELL order");

        assert_eq!(order.side, "sell", "Order side should be lowercase 'sell'");
        assert_eq!(order.order_type, "market");
        assert_eq!(order.status, "filled");

        // Cleanup
        sqlx::query!(
            "DELETE FROM orders WHERE symbol = 'ZN.FUT' AND account_id = 'paper_trading_001'"
        )
        .execute(&pool)
        .await
        .expect("Failed to cleanup SELL order");
        sqlx::query!("DELETE FROM ensemble_predictions WHERE id = $1", pred_id)
            .execute(&pool)
            .await
            .expect("Failed to cleanup SELL prediction");
    }

    pool.close().await;
}

// ============================================================================
// TEST 4: Position Tracking
// ============================================================================

#[tokio::test]
async fn test_position_tracking() {
    let pool = PgPool::connect(&get_test_db_url())
        .await
        .expect("Failed to connect to test database");

    let config = PaperTradingConfig::default();
    let executor = Arc::new(PaperTradingExecutor::new(pool.clone(), config));

    // Execute multiple predictions for same symbol
    let symbols = vec!["ES.FUT", "ES.FUT", "NQ.FUT"];
    let mut pred_ids = Vec::new();

    for symbol in &symbols {
        let pred_id = Uuid::new_v4();
        pred_ids.push(pred_id);

        sqlx::query!(
            r#"
            INSERT INTO ensemble_predictions (
                id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
            ) VALUES (
                $1, $2, 'BUY', 0.75, 0.85, 0.10
            )
            "#,
            pred_id,
            symbol,
        )
        .execute(&pool)
        .await
        .expect("Failed to insert prediction");

        let prediction = PendingPrediction {
            id: pred_id,
            symbol: symbol.to_string(),
            ensemble_action: "BUY".to_string(),
            ensemble_signal: 0.75,
            ensemble_confidence: 0.85,
        };

        executor
            .execute_prediction(&prediction)
            .await
            .expect("Failed to execute prediction");
    }

    // Assert: Check position summary
    let summary = executor.get_position_summary().await;

    assert_eq!(
        summary.get("ES.FUT"),
        Some(&2),
        "ES.FUT should have 2 positions"
    );
    assert_eq!(
        summary.get("NQ.FUT"),
        Some(&1),
        "NQ.FUT should have 1 position"
    );

    // Cleanup
    for pred_id in pred_ids {
        sqlx::query!("DELETE FROM ensemble_predictions WHERE id = $1", pred_id)
            .execute(&pool)
            .await
            .expect("Failed to cleanup prediction");
    }
    sqlx::query!("DELETE FROM orders WHERE account_id = 'paper_trading_001'")
        .execute(&pool)
        .await
        .expect("Failed to cleanup orders");

    pool.close().await;
}

// ============================================================================
// TEST 5: Error Handling
// ============================================================================

#[tokio::test]
async fn test_error_handling_invalid_symbol() {
    let pool = PgPool::connect(&get_test_db_url())
        .await
        .expect("Failed to connect to test database");

    let config = PaperTradingConfig::default();
    let executor = Arc::new(PaperTradingExecutor::new(pool.clone(), config));

    // Test invalid symbol
    let prediction = PendingPrediction {
        id: Uuid::new_v4(),
        symbol: "INVALID.FUT".to_string(),
        ensemble_action: "BUY".to_string(),
        ensemble_signal: 0.75,
        ensemble_confidence: 0.85,
    };

    let result = executor.execute_prediction(&prediction).await;

    assert!(result.is_err(), "Should fail on invalid symbol");
    assert!(
        result
            .unwrap_err()
            .to_string()
            .contains("not in allowed list"),
        "Error should mention allowed list"
    );

    pool.close().await;
}

#[tokio::test]
async fn test_error_handling_low_confidence() {
    let pool = PgPool::connect(&get_test_db_url())
        .await
        .expect("Failed to connect to test database");

    let config = PaperTradingConfig::default();
    let executor = Arc::new(PaperTradingExecutor::new(pool.clone(), config));

    // Test low confidence (below 60% threshold)
    let prediction = PendingPrediction {
        id: Uuid::new_v4(),
        symbol: "ES.FUT".to_string(),
        ensemble_action: "BUY".to_string(),
        ensemble_signal: 0.55,
        ensemble_confidence: 0.50, // Below 60% threshold
    };

    let result = executor.execute_prediction(&prediction).await;

    assert!(result.is_err(), "Should fail on low confidence");
    assert!(
        result.unwrap_err().to_string().contains("below threshold"),
        "Error should mention threshold"
    );

    pool.close().await;
}

#[tokio::test]
async fn test_error_handling_position_limit() {
    let pool = PgPool::connect(&get_test_db_url())
        .await
        .expect("Failed to connect to test database");

    let config = PaperTradingConfig::default();
    let executor = Arc::new(PaperTradingExecutor::new(pool.clone(), config));

    // Create 10 positions (max limit)
    let mut pred_ids = Vec::new();
    for i in 0..10 {
        let pred_id = Uuid::new_v4();
        pred_ids.push(pred_id);

        sqlx::query!(
            r#"
            INSERT INTO ensemble_predictions (
                id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
            ) VALUES (
                $1, 'ES.FUT', 'BUY', 0.75, 0.85, 0.10
            )
            "#,
            pred_id,
        )
        .execute(&pool)
        .await
        .expect("Failed to insert prediction");

        let prediction = PendingPrediction {
            id: pred_id,
            symbol: "ES.FUT".to_string(),
            ensemble_action: "BUY".to_string(),
            ensemble_signal: 0.75,
            ensemble_confidence: 0.85,
        };

        executor
            .execute_prediction(&prediction)
            .await
            .expect("Failed to execute prediction");
    }

    // Try to create 11th position - should fail
    let pred_id = Uuid::new_v4();
    pred_ids.push(pred_id);

    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
        ) VALUES (
            $1, 'ES.FUT', 'BUY', 0.75, 0.85, 0.10
        )
        "#,
        pred_id,
    )
    .execute(&pool)
    .await
    .expect("Failed to insert prediction");

    let prediction = PendingPrediction {
        id: pred_id,
        symbol: "ES.FUT".to_string(),
        ensemble_action: "BUY".to_string(),
        ensemble_signal: 0.75,
        ensemble_confidence: 0.85,
    };

    let result = executor.execute_prediction(&prediction).await;

    assert!(result.is_err(), "Should fail when position limit reached");
    assert!(
        result.unwrap_err().to_string().contains("position limit"),
        "Error should mention position limit"
    );

    // Cleanup
    for pred_id in pred_ids {
        sqlx::query!("DELETE FROM ensemble_predictions WHERE id = $1", pred_id)
            .execute(&pool)
            .await
            .expect("Failed to cleanup prediction");
    }
    sqlx::query!("DELETE FROM orders WHERE account_id = 'paper_trading_001' AND symbol = 'ES.FUT'")
        .execute(&pool)
        .await
        .expect("Failed to cleanup orders");

    pool.close().await;
}

// ============================================================================
// TEST 6: Polling Interval Timing
// ============================================================================

#[tokio::test]
async fn test_polling_interval_timing() {
    let pool = PgPool::connect(&get_test_db_url())
        .await
        .expect("Failed to connect to test database");

    // Create config with 50ms polling interval for faster testing
    let mut config = PaperTradingConfig::default();
    config.poll_interval_ms = 50;

    let executor = Arc::new(PaperTradingExecutor::new(pool.clone(), config.clone()));

    // Measure time for 5 polling cycles
    let start = Instant::now();
    let mut interval = tokio::time::interval(Duration::from_millis(config.poll_interval_ms));

    for _ in 0..5 {
        interval.tick().await;
    }

    let elapsed = start.elapsed();

    // Assert: Should take approximately 250ms (5 cycles × 50ms)
    // Allow ±50ms tolerance for system jitter
    let expected_ms = 5 * config.poll_interval_ms;
    let actual_ms = elapsed.as_millis() as u64;

    assert!(
        actual_ms >= expected_ms - 50 && actual_ms <= expected_ms + 50,
        "Polling interval timing incorrect: expected ~{}ms, got {}ms",
        expected_ms,
        actual_ms
    );

    pool.close().await;
}

// ============================================================================
// TEST 7: Concurrent Execution (Race Condition Prevention)
// ============================================================================

#[tokio::test]
async fn test_concurrent_execution() {
    let pool = PgPool::connect(&get_test_db_url())
        .await
        .expect("Failed to connect to test database");

    // Insert 20 predictions
    let mut pred_ids = Vec::new();
    for i in 0..20 {
        let pred_id = Uuid::new_v4();
        pred_ids.push(pred_id);

        let symbol = if i % 2 == 0 { "ES.FUT" } else { "NQ.FUT" };

        sqlx::query!(
            r#"
            INSERT INTO ensemble_predictions (
                id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
            ) VALUES (
                $1, $2, 'BUY', 0.75, 0.85, 0.10
            )
            "#,
            pred_id,
            symbol,
        )
        .execute(&pool)
        .await
        .expect("Failed to insert prediction");
    }

    // Create two executors (simulating concurrent instances)
    let config = PaperTradingConfig::default();
    let executor1 = Arc::new(PaperTradingExecutor::new(pool.clone(), config.clone()));
    let executor2 = Arc::new(PaperTradingExecutor::new(pool.clone(), config.clone()));

    // Execute concurrently
    let handle1 = {
        let executor = executor1.clone();
        tokio::spawn(async move { executor.execute_cycle().await })
    };

    let handle2 = {
        let executor = executor2.clone();
        tokio::spawn(async move { executor.execute_cycle().await })
    };

    let result1 = handle1
        .await
        .expect("Task 1 panicked")
        .expect("Executor 1 failed");
    let result2 = handle2
        .await
        .expect("Task 2 panicked")
        .expect("Executor 2 failed");

    let total_processed = result1 + result2;

    // Assert: Total processed should be <= 20 (no duplicates)
    // Note: Some predictions may be processed twice if executors run simultaneously,
    // but order_id update should be idempotent
    assert!(
        total_processed <= 20,
        "Concurrent execution processed {} predictions (expected <= 20)",
        total_processed
    );

    // Assert: All predictions should be linked to exactly one order
    let linked_count = sqlx::query!(
        r#"
        SELECT COUNT(*) as count
        FROM ensemble_predictions
        WHERE id = ANY($1) AND order_id IS NOT NULL
        "#,
        &pred_ids,
    )
    .fetch_one(&pool)
    .await
    .expect("Failed to count linked predictions");

    assert!(
        linked_count.count.unwrap_or(0) > 0,
        "At least some predictions should be linked to orders"
    );

    // Cleanup
    for pred_id in pred_ids {
        sqlx::query!("DELETE FROM ensemble_predictions WHERE id = $1", pred_id)
            .execute(&pool)
            .await
            .expect("Failed to cleanup prediction");
    }
    sqlx::query!("DELETE FROM orders WHERE account_id = 'paper_trading_001'")
        .execute(&pool)
        .await
        .expect("Failed to cleanup orders");

    pool.close().await;
}

// ============================================================================
// TEST 8: End-to-End Execute Cycle
// ============================================================================

#[tokio::test]
async fn test_execute_cycle_e2e() {
    let pool = PgPool::connect(&get_test_db_url())
        .await
        .expect("Failed to connect to test database");

    // Insert mixed predictions
    let mut pred_ids = Vec::new();

    // Valid BUY prediction
    let pred1 = Uuid::new_v4();
    pred_ids.push(pred1);
    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
        ) VALUES (
            $1, 'ES.FUT', 'BUY', 0.75, 0.85, 0.10
        )
        "#,
        pred1,
    )
    .execute(&pool)
    .await
    .expect("Failed to insert valid BUY prediction");

    // Valid SELL prediction
    let pred2 = Uuid::new_v4();
    pred_ids.push(pred2);
    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
        ) VALUES (
            $1, 'NQ.FUT', 'SELL', -0.70, 0.80, 0.15
        )
        "#,
        pred2,
    )
    .execute(&pool)
    .await
    .expect("Failed to insert valid SELL prediction");

    // Low confidence - should be ignored
    let pred3 = Uuid::new_v4();
    pred_ids.push(pred3);
    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
        ) VALUES (
            $1, 'ZN.FUT', 'BUY', 0.55, 0.50, 0.40
        )
        "#,
        pred3,
    )
    .execute(&pool)
    .await
    .expect("Failed to insert low confidence prediction");

    // Execute cycle
    let config = PaperTradingConfig::default();
    let executor = Arc::new(PaperTradingExecutor::new(pool.clone(), config));

    let processed_count = executor
        .execute_cycle()
        .await
        .expect("Execute cycle failed");

    // Assert: Should process 2 predictions (pred1 and pred2)
    assert_eq!(processed_count, 2, "Should process 2 valid predictions");

    // Assert: Both valid predictions should be linked to orders
    let linked_valid = sqlx::query!(
        r#"
        SELECT COUNT(*) as count
        FROM ensemble_predictions
        WHERE id = ANY($1) AND order_id IS NOT NULL
        "#,
        &[pred1, pred2],
    )
    .fetch_one(&pool)
    .await
    .expect("Failed to count linked predictions");

    assert_eq!(
        linked_valid.count.unwrap_or(0),
        2,
        "Both valid predictions should be linked"
    );

    // Assert: Low confidence prediction should NOT be linked
    let linked_low = sqlx::query!(
        r#"
        SELECT order_id
        FROM ensemble_predictions
        WHERE id = $1
        "#,
        pred3,
    )
    .fetch_one(&pool)
    .await
    .expect("Failed to fetch low confidence prediction");

    assert!(
        linked_low.order_id.is_none(),
        "Low confidence prediction should NOT be executed"
    );

    // Assert: Two orders should be created
    let order_count = sqlx::query!(
        r#"
        SELECT COUNT(*) as count
        FROM orders
        WHERE account_id = 'paper_trading_001'
        "#,
    )
    .fetch_one(&pool)
    .await
    .expect("Failed to count orders");

    assert_eq!(order_count.count.unwrap_or(0), 2, "Should create 2 orders");

    // Cleanup
    for pred_id in pred_ids {
        sqlx::query!("DELETE FROM ensemble_predictions WHERE id = $1", pred_id)
            .execute(&pool)
            .await
            .expect("Failed to cleanup prediction");
    }
    sqlx::query!("DELETE FROM orders WHERE account_id = 'paper_trading_001'")
        .execute(&pool)
        .await
        .expect("Failed to cleanup orders");

    pool.close().await;
}

// ============================================================================
// TEST 9: Batch Processing
// ============================================================================

#[tokio::test]
async fn test_batch_processing_limit() {
    let pool = PgPool::connect(&get_test_db_url())
        .await
        .expect("Failed to connect to test database");

    // Insert 150 predictions (exceeds default batch size of 100)
    let mut pred_ids = Vec::new();
    for i in 0..150 {
        let pred_id = Uuid::new_v4();
        pred_ids.push(pred_id);

        let symbol = match i % 4 {
            0 => "ES.FUT",
            1 => "NQ.FUT",
            2 => "ZN.FUT",
            _ => "6E.FUT",
        };

        sqlx::query!(
            r#"
            INSERT INTO ensemble_predictions (
                id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
            ) VALUES (
                $1, $2, 'BUY', 0.75, 0.85, 0.10
            )
            "#,
            pred_id,
            symbol,
        )
        .execute(&pool)
        .await
        .expect("Failed to insert prediction");
    }

    // Execute single cycle
    let config = PaperTradingConfig::default();
    let executor = Arc::new(PaperTradingExecutor::new(pool.clone(), config.clone()));

    let processed_count = executor
        .execute_cycle()
        .await
        .expect("Execute cycle failed");

    // Assert: Should process exactly batch_size (100) predictions
    assert_eq!(
        processed_count, config.batch_size,
        "Should process batch_size predictions per cycle"
    );

    // Execute second cycle to process remaining 50
    let processed_count2 = executor
        .execute_cycle()
        .await
        .expect("Second execute cycle failed");

    assert_eq!(
        processed_count2, 50,
        "Second cycle should process remaining 50 predictions"
    );

    // Cleanup
    for pred_id in pred_ids {
        sqlx::query!("DELETE FROM ensemble_predictions WHERE id = $1", pred_id)
            .execute(&pool)
            .await
            .expect("Failed to cleanup prediction");
    }
    sqlx::query!("DELETE FROM orders WHERE account_id = 'paper_trading_001'")
        .execute(&pool)
        .await
        .expect("Failed to cleanup orders");

    pool.close().await;
}

// ============================================================================
// TEST 10: Configuration Variations
// ============================================================================

#[tokio::test]
async fn test_custom_configuration() {
    let pool = PgPool::connect(&get_test_db_url())
        .await
        .expect("Failed to connect to test database");

    // Custom config: Higher confidence threshold (75%)
    let mut config = PaperTradingConfig::default();
    config.min_confidence = 0.75;
    config.allowed_symbols = vec!["ES.FUT".to_string()];

    // Insert predictions with varying confidence
    let pred_high = Uuid::new_v4();
    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
        ) VALUES (
            $1, 'ES.FUT', 'BUY', 0.80, 0.85, 0.10
        )
        "#,
        pred_high,
    )
    .execute(&pool)
    .await
    .expect("Failed to insert high confidence prediction");

    let pred_medium = Uuid::new_v4();
    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
        ) VALUES (
            $1, 'ES.FUT', 'BUY', 0.65, 0.70, 0.20
        )
        "#,
        pred_medium,
    )
    .execute(&pool)
    .await
    .expect("Failed to insert medium confidence prediction");

    let pred_wrong_symbol = Uuid::new_v4();
    sqlx::query!(
        r#"
        INSERT INTO ensemble_predictions (
            id, symbol, ensemble_action, ensemble_signal, ensemble_confidence, disagreement_rate
        ) VALUES (
            $1, 'NQ.FUT', 'BUY', 0.80, 0.85, 0.10
        )
        "#,
        pred_wrong_symbol,
    )
    .execute(&pool)
    .await
    .expect("Failed to insert wrong symbol prediction");

    // Execute with custom config
    let executor = Arc::new(PaperTradingExecutor::new(pool.clone(), config));

    let processed_count = executor
        .execute_cycle()
        .await
        .expect("Execute cycle failed");

    // Assert: Only high confidence ES.FUT prediction should be processed
    assert_eq!(
        processed_count, 1,
        "Only high confidence ES.FUT should be processed"
    );

    // Cleanup
    sqlx::query!(
        "DELETE FROM ensemble_predictions WHERE id = ANY($1)",
        &[pred_high, pred_medium, pred_wrong_symbol]
    )
    .execute(&pool)
    .await
    .expect("Failed to cleanup predictions");
    sqlx::query!("DELETE FROM orders WHERE account_id = 'paper_trading_001'")
        .execute(&pool)
        .await
        .expect("Failed to cleanup orders");

    pool.close().await;
}
