//! Comprehensive tests for autonomous capital-based scaling
//!
//! Tests cover:
//! - Tier selection based on capital
//! - System constraint validation
//! - Performance-based auto-adjustment
//! - Universe reselection on tier changes
//! - Database persistence

use bigdecimal::BigDecimal;
use chrono::Utc;
use sqlx::PgPool;
use std::str::FromStr;
use trading_agent_service::autonomous_scaling::{
    AutonomousUniverseManager, CapitalScalingTier, PerformanceMetrics, PositionSizingMode,
    ScalingError, SystemConstraints,
};

/// Helper to create test database pool
async fn create_test_pool() -> PgPool {
    let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
        "postgresql://foxhunt:foxhunt_dev_password@localhost:5432/foxhunt".to_string()
    });

    PgPool::connect(&database_url)
        .await
        .expect("Failed to connect to test database")
}

/// Helper to clean up test data
async fn cleanup_test_data(pool: &PgPool) {
    sqlx::query("DELETE FROM autonomous_scaling_config WHERE current_tier = 999")
        .execute(pool)
        .await
        .ok();

    sqlx::query("DELETE FROM scaling_tier_history WHERE reason LIKE 'TEST:%'")
        .execute(pool)
        .await
        .ok();
}

#[tokio::test]
async fn test_tier_selection_for_different_capitals() {
    // Tier 1: $10K-$49K
    let tier = CapitalScalingTier::for_capital(25_000.0).unwrap();
    assert_eq!(tier.tier, 1);
    assert_eq!(tier.max_symbols, 3);
    assert_eq!(tier.position_sizing, PositionSizingMode::EqualWeight);

    // Tier 2: $50K-$99K
    let tier = CapitalScalingTier::for_capital(75_000.0).unwrap();
    assert_eq!(tier.tier, 2);
    assert_eq!(tier.max_symbols, 6);
    assert_eq!(tier.position_sizing, PositionSizingMode::MLOptimized);

    // Tier 3: $100K-$249K
    let tier = CapitalScalingTier::for_capital(150_000.0).unwrap();
    assert_eq!(tier.tier, 3);
    assert_eq!(tier.max_symbols, 12);
    assert_eq!(tier.position_sizing, PositionSizingMode::RiskParity);

    // Tier 6: $1M+
    let tier = CapitalScalingTier::for_capital(2_500_000.0).unwrap();
    assert_eq!(tier.tier, 6);
    assert_eq!(tier.max_symbols, 50);
    assert_eq!(tier.position_sizing, PositionSizingMode::BlackLitterman);
}

#[tokio::test]
async fn test_tier_boundaries() {
    // Exact boundaries
    let tier = CapitalScalingTier::for_capital(10_000.0).unwrap();
    assert_eq!(tier.tier, 1);

    let tier = CapitalScalingTier::for_capital(50_000.0).unwrap();
    assert_eq!(tier.tier, 2);

    let tier = CapitalScalingTier::for_capital(100_000.0).unwrap();
    assert_eq!(tier.tier, 3);

    let tier = CapitalScalingTier::for_capital(250_000.0).unwrap();
    assert_eq!(tier.tier, 4);

    let tier = CapitalScalingTier::for_capital(500_000.0).unwrap();
    assert_eq!(tier.tier, 5);

    let tier = CapitalScalingTier::for_capital(1_000_000.0).unwrap();
    assert_eq!(tier.tier, 6);

    // Below minimum
    assert!(CapitalScalingTier::for_capital(5_000.0).is_none());
}

#[tokio::test]
async fn test_system_constraints_latency_budget() {
    let constraints = SystemConstraints::default();

    // 3 symbols: 3 * 15ms = 45ms < 100ms ✓
    assert!(constraints.can_handle_symbols(3).is_ok());

    // 6 symbols: 6 * 15ms = 90ms < 100ms ✓
    assert!(constraints.can_handle_symbols(6).is_ok());

    // 7 symbols: 7 * 15ms = 105ms > 100ms ✗
    let result = constraints.can_handle_symbols(7);
    assert!(result.is_err());
    if let Err(ScalingError::ConstraintViolation(msg)) = result {
        assert!(msg.contains("Latency budget exceeded"));
    }
}

#[tokio::test]
async fn test_system_constraints_memory_budget() {
    let constraints = SystemConstraints {
        max_ml_latency: 10000, // Disable latency check
        ..SystemConstraints::default()
    };

    // 3 symbols: 6 * 3 * 50MB = 900MB < 8GB ✓
    assert!(constraints.can_handle_symbols(3).is_ok());

    // 20 symbols: 6 * 20 * 50MB = 6GB < 8GB ✓
    assert!(constraints.can_handle_symbols(20).is_ok());

    // 30 symbols: 6 * 30 * 50MB = 9GB > 8GB ✗
    let result = constraints.can_handle_symbols(30);
    assert!(result.is_err());
    if let Err(ScalingError::ConstraintViolation(msg)) = result {
        assert!(msg.contains("Memory budget exceeded"));
    }
}

#[tokio::test]
async fn test_system_constraints_rebalance_limit() {
    let constraints = SystemConstraints {
        max_ml_latency: 10000, // Disable latency check
        max_memory_gb: 100.0, // Disable memory check
        ..SystemConstraints::default()
    };

    // Within limit
    assert!(constraints.can_handle_symbols(25).is_ok());

    // At limit
    assert!(constraints.can_handle_symbols(30).is_ok());

    // Over limit
    let result = constraints.can_handle_symbols(31);
    assert!(result.is_err());
    if let Err(ScalingError::ConstraintViolation(msg)) = result {
        assert!(msg.contains("Rebalance load exceeded"));
    }
}

#[tokio::test]
async fn test_select_optimal_universe_tier1() {
    let pool = create_test_pool().await;
    cleanup_test_data(&pool).await;

    let manager = AutonomousUniverseManager::new(pool.clone());

    // Tier 1: $25K → 3 symbols
    let instruments = manager.select_optimal_universe(25_000.0).await.unwrap();

    assert_eq!(instruments.len(), 3);
    assert!(instruments.iter().all(|i| i.liquidity_score >= 0.85));

    cleanup_test_data(&pool).await;
}

#[tokio::test]
async fn test_select_optimal_universe_tier2() {
    let pool = create_test_pool().await;
    cleanup_test_data(&pool).await;

    let manager = AutonomousUniverseManager::new(pool.clone());

    // Tier 2: $75K → 6 symbols
    let instruments = manager.select_optimal_universe(75_000.0).await.unwrap();

    assert_eq!(instruments.len(), 6);
    assert!(instruments.iter().all(|i| i.liquidity_score >= 0.8));

    cleanup_test_data(&pool).await;
}

#[tokio::test]
async fn test_select_optimal_universe_invalid_capital() {
    let pool = create_test_pool().await;
    let manager = AutonomousUniverseManager::new(pool.clone());

    // Zero capital
    let result = manager.select_optimal_universe(0.0).await;
    assert!(matches!(result, Err(ScalingError::InvalidCapital(_))));

    // Negative capital
    let result = manager.select_optimal_universe(-1000.0).await;
    assert!(matches!(result, Err(ScalingError::InvalidCapital(_))));

    // Below minimum tier
    let result = manager.select_optimal_universe(5_000.0).await;
    assert!(matches!(result, Err(ScalingError::InvalidCapital(_))));
}

#[tokio::test]
async fn test_config_creation_and_retrieval() {
    let pool = create_test_pool().await;
    cleanup_test_data(&pool).await;

    let manager = AutonomousUniverseManager::new(pool.clone());

    // Create config
    let config = manager.get_or_create_config().await.unwrap();
    assert_eq!(config.current_tier, 1);
    assert_eq!(config.current_capital, 10_000.0);
    assert!(config.enabled);

    // Retrieve same config
    let retrieved = manager.get_latest_config().await.unwrap().unwrap();
    assert_eq!(retrieved.config_id, config.config_id);
    assert_eq!(retrieved.current_tier, config.current_tier);

    cleanup_test_data(&pool).await;
}

#[tokio::test]
async fn test_capital_update_triggers_tier_change() {
    let pool = create_test_pool().await;
    cleanup_test_data(&pool).await;

    let manager = AutonomousUniverseManager::new(pool.clone());

    // Start at Tier 1 ($10K)
    let mut config = manager.get_or_create_config().await.unwrap();
    assert_eq!(config.current_tier, 1);

    // Update capital to Tier 2 threshold ($50K)
    config = manager.update_capital(50_000.0).await.unwrap();
    assert_eq!(config.current_tier, 2);
    assert_eq!(config.current_capital, 50_000.0);

    // Update capital to Tier 3 threshold ($100K)
    config = manager.update_capital(100_000.0).await.unwrap();
    assert_eq!(config.current_tier, 3);
    assert_eq!(config.current_capital, 100_000.0);

    // Verify tier change was recorded
    let history = sqlx::query_as::<_, (Option<i32>, i32, rust_decimal::Decimal, String)>(
        r#"
        SELECT from_tier, to_tier, capital, reason
        FROM scaling_tier_history
        WHERE capital::TEXT = $1
        ORDER BY timestamp DESC
        LIMIT 1
        "#,
    )
    .bind("100000.00")
    .fetch_one(&pool)
    .await
    .unwrap();

    assert_eq!(history.0, Some(2));
    assert_eq!(history.1, 3);

    cleanup_test_data(&pool).await;
}

#[tokio::test]
async fn test_performance_based_downgrade() {
    let pool = create_test_pool().await;
    cleanup_test_data(&pool).await;

    let manager = AutonomousUniverseManager::new(pool.clone());

    // Create Tier 2 config with poor performance
    let mut config = manager.get_or_create_config().await.unwrap();
    config.current_tier = 2;
    config.current_capital = 75_000.0;
    config.performance_30d = PerformanceMetrics {
        sharpe_ratio: 0.3, // Below Tier 2 threshold (0.7 * 0.8 = 0.56)
        total_return_pct: -5.0,
        max_drawdown_pct: 15.0,
        win_rate: 0.4,
        capital_growth_rate: -0.05,
        num_trades: 100,
        period_start: Utc::now(),
        period_end: Utc::now(),
    };

    // Manually store config to test monitoring
    sqlx::query(
        r#"
        INSERT INTO autonomous_scaling_config (
            config_id, enabled, current_tier, current_capital,
            current_symbols, last_rebalance, performance_30d,
            created_at, updated_at
        )
        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
        ON CONFLICT (config_id) DO UPDATE
        SET current_tier = EXCLUDED.current_tier,
            performance_30d = EXCLUDED.performance_30d,
            updated_at = EXCLUDED.updated_at
        "#,
    )
    .bind(config.config_id)
    .bind(config.enabled)
    .bind(config.current_tier as i32)
    .bind(BigDecimal::from_str(&config.current_capital.to_string()).unwrap())
    .bind(6i32)
    .bind(config.last_rebalance)
    .bind(serde_json::to_value(&config.performance_30d).unwrap())
    .bind(config.created_at)
    .bind(Utc::now())
    .execute(&pool)
    .await
    .unwrap();

    // Monitor should trigger downgrade
    let event = manager.monitor_and_adjust().await.unwrap();
    assert!(event.is_some());

    let event = event.unwrap();
    assert_eq!(event.from_tier, Some(2));
    assert_eq!(event.to_tier, 1);
    assert!(event.reason.contains("Performance degradation"));

    cleanup_test_data(&pool).await;
}

#[tokio::test]
async fn test_performance_based_upgrade() {
    let pool = create_test_pool().await;
    cleanup_test_data(&pool).await;

    let manager = AutonomousUniverseManager::new(pool.clone());

    // Create Tier 1 config with excellent performance
    let mut config = manager.get_or_create_config().await.unwrap();
    config.current_tier = 1;
    config.current_capital = 60_000.0; // Above Tier 2 threshold
    config.performance_30d = PerformanceMetrics {
        sharpe_ratio: 0.75, // Above Tier 1 threshold (0.5 * 1.2 = 0.6)
        total_return_pct: 15.0,
        max_drawdown_pct: 5.0,
        win_rate: 0.65,
        capital_growth_rate: 0.15, // 15% growth
        num_trades: 200,
        period_start: Utc::now(),
        period_end: Utc::now(),
    };

    // Manually store config
    sqlx::query(
        r#"
        INSERT INTO autonomous_scaling_config (
            config_id, enabled, current_tier, current_capital,
            current_symbols, last_rebalance, performance_30d,
            created_at, updated_at
        )
        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
        ON CONFLICT (config_id) DO UPDATE
        SET current_tier = EXCLUDED.current_tier,
            current_capital = EXCLUDED.current_capital,
            performance_30d = EXCLUDED.performance_30d,
            updated_at = EXCLUDED.updated_at
        "#,
    )
    .bind(config.config_id)
    .bind(config.enabled)
    .bind(config.current_tier as i32)
    .bind(BigDecimal::from_str(&config.current_capital.to_string()).unwrap())
    .bind(3i32)
    .bind(config.last_rebalance)
    .bind(serde_json::to_value(&config.performance_30d).unwrap())
    .bind(config.created_at)
    .bind(Utc::now())
    .execute(&pool)
    .await
    .unwrap();

    // Monitor should trigger upgrade
    let event = manager.monitor_and_adjust().await.unwrap();
    assert!(event.is_some());

    let event = event.unwrap();
    assert_eq!(event.from_tier, Some(1));
    assert_eq!(event.to_tier, 2);
    assert!(event.reason.contains("Strong performance"));

    cleanup_test_data(&pool).await;
}

#[tokio::test]
async fn test_monitor_disabled_config() {
    let pool = create_test_pool().await;
    cleanup_test_data(&pool).await;

    let manager = AutonomousUniverseManager::new(pool.clone());

    // Create disabled config
    let mut config = manager.get_or_create_config().await.unwrap();
    config.enabled = false;

    sqlx::query(
        r#"
        INSERT INTO autonomous_scaling_config (
            config_id, enabled, current_tier, current_capital,
            current_symbols, last_rebalance, performance_30d,
            created_at, updated_at
        )
        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
        ON CONFLICT (config_id) DO UPDATE
        SET enabled = EXCLUDED.enabled
        "#,
    )
    .bind(config.config_id)
    .bind(false)
    .bind(config.current_tier as i32)
    .bind(BigDecimal::from_str(&config.current_capital.to_string()).unwrap())
    .bind(3i32)
    .bind(config.last_rebalance)
    .bind(serde_json::to_value(&config.performance_30d).unwrap())
    .bind(config.created_at)
    .bind(Utc::now())
    .execute(&pool)
    .await
    .unwrap();

    // Monitor should return error
    let result = manager.monitor_and_adjust().await;
    assert!(matches!(result, Err(ScalingError::NotEnabled)));

    cleanup_test_data(&pool).await;
}

#[tokio::test]
async fn test_tier_history_persistence() {
    let pool = create_test_pool().await;
    cleanup_test_data(&pool).await;

    let manager = AutonomousUniverseManager::new(pool.clone());

    // Record tier changes
    manager
        .record_tier_change(Some(1), 2, 50_000.0, "TEST: Capital increase")
        .await
        .unwrap();
    manager
        .record_tier_change(Some(2), 3, 100_000.0, "TEST: Strong performance")
        .await
        .unwrap();
    manager
        .record_tier_change(Some(3), 2, 100_000.0, "TEST: Performance degradation")
        .await
        .unwrap();

    // Verify history
    let history = sqlx::query_as::<_, (Option<i32>, i32, String)>(
        r#"
        SELECT from_tier, to_tier, reason
        FROM scaling_tier_history
        WHERE reason LIKE 'TEST:%'
        ORDER BY timestamp ASC
        "#,
    )
    .fetch_all(&pool)
    .await
    .unwrap();

    assert_eq!(history.len(), 3);
    assert_eq!(history[0].0, Some(1));
    assert_eq!(history[0].1, 2);
    assert_eq!(history[1].0, Some(2));
    assert_eq!(history[1].1, 3);
    assert_eq!(history[2].0, Some(3));
    assert_eq!(history[2].1, 2);

    cleanup_test_data(&pool).await;
}

#[tokio::test]
async fn test_custom_constraints() {
    let pool = create_test_pool().await;

    // Create manager with tight constraints
    let constraints = SystemConstraints {
        max_ml_latency: 50,            // 50ms
        max_order_gen_time: 25,        // 25ms
        max_memory_gb: 4.0,            // 4GB
        max_concurrent_inferences: 18, // 3 models * 6 symbols
        max_db_connections: 25,
        max_rebalance_symbols: 10,
    };

    let manager = AutonomousUniverseManager::with_constraints(pool.clone(), constraints.clone());

    // 3 symbols: 45ms < 50ms ✓
    let instruments = manager.select_optimal_universe(25_000.0).await.unwrap();
    assert_eq!(instruments.len(), 3);

    // 4 symbols: 60ms > 50ms ✗
    // (Would be tier 2 with 6 symbols, but constraints prevent it)
    // For this test, we just verify constraints are enforced
    assert!(constraints.can_handle_symbols(4).is_err());
}

#[tokio::test]
async fn test_all_tiers_have_valid_parameters() {
    let tiers = CapitalScalingTier::all_tiers();

    for tier in &tiers {
        // Verify tier numbers are sequential
        assert!(tier.tier >= 1 && tier.tier <= 6);

        // Verify capital thresholds increase
        if tier.tier > 1 {
            let prev_tier = &tiers[tier.tier as usize - 2];
            assert!(tier.min_capital > prev_tier.min_capital);
        }

        // Verify max_symbols increases
        if tier.tier > 1 {
            let prev_tier = &tiers[tier.tier as usize - 2];
            assert!(tier.max_symbols > prev_tier.max_symbols);
        }

        // Verify correlation threshold is valid
        assert!(tier.max_correlation >= 0.0 && tier.max_correlation <= 1.0);

        // Verify Sharpe ratio threshold is positive
        assert!(tier.min_sharpe_ratio > 0.0);
    }
}

#[tokio::test]
async fn test_concurrent_config_updates() {
    let pool = create_test_pool().await;
    cleanup_test_data(&pool).await;

    let manager = AutonomousUniverseManager::new(pool.clone());

    // Create multiple concurrent update tasks
    let handles: Vec<_> = (1..=5)
        .map(|i| {
            let manager = AutonomousUniverseManager::new(pool.clone());
            tokio::spawn(
                async move { manager.update_capital(10_000.0 + i as f64 * 10_000.0).await },
            )
        })
        .collect();

    // Wait for all to complete
    for handle in handles {
        handle.await.unwrap().unwrap();
    }

    // Verify final state is consistent
    let config = manager.get_latest_config().await.unwrap().unwrap();
    assert!(config.current_capital >= 20_000.0 && config.current_capital <= 60_000.0);

    cleanup_test_data(&pool).await;
}
