//! Integration tests for strategy coordination module

use std::collections::HashMap;
use trading_agent_service::strategies::{
    StrategyConfig, StrategyCoordinator, StrategyStatus, StrategyType,
};

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

async fn setup_test_db() -> sqlx::PgPool {
    let pool = sqlx::PgPool::connect(&get_test_database_url())
        .await
        .expect("Failed to connect to database");

    // Run migrations
    sqlx::migrate!("../../migrations")
        .run(&pool)
        .await
        .expect("Failed to run migrations");

    pool
}

#[tokio::test]
async fn test_register_strategy() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    // Create a strategy config
    let mut parameters = HashMap::new();
    parameters.insert("risk_limit".to_string(), 0.05);
    parameters.insert("rebalance_threshold".to_string(), 0.02);

    let config = StrategyConfig {
        strategy_id: String::new(), // Will be generated
        strategy_name: "test_equal_weight_001".to_string(),
        strategy_type: StrategyType::EqualWeight,
        parameters,
        status: StrategyStatus::Active,
        created_at: chrono::Utc::now(),
        updated_at: chrono::Utc::now(),
    };

    let result = coordinator.register_strategy(config).await;

    assert!(result.is_ok(), "Strategy registration should succeed");
    let strategy_id = result.expect("Strategy ID should be returned");
    assert!(!strategy_id.is_empty(), "Strategy ID should not be empty");

    // Verify it's a valid UUID
    assert!(
        uuid::Uuid::parse_str(&strategy_id).is_ok(),
        "Strategy ID should be valid UUID"
    );
}

#[tokio::test]
async fn test_register_duplicate_strategy_name() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    let mut parameters = HashMap::new();
    parameters.insert("risk_limit".to_string(), 0.05);

    let config1 = StrategyConfig {
        strategy_id: String::new(),
        strategy_name: "duplicate_test_strategy".to_string(),
        strategy_type: StrategyType::RiskParity,
        parameters: parameters.clone(),
        status: StrategyStatus::Active,
        created_at: chrono::Utc::now(),
        updated_at: chrono::Utc::now(),
    };

    // Register first strategy
    let result1 = coordinator.register_strategy(config1).await;
    assert!(result1.is_ok());

    // Try to register duplicate name
    let config2 = StrategyConfig {
        strategy_id: String::new(),
        strategy_name: "duplicate_test_strategy".to_string(),
        strategy_type: StrategyType::MLOptimized,
        parameters,
        status: StrategyStatus::Active,
        created_at: chrono::Utc::now(),
        updated_at: chrono::Utc::now(),
    };

    let result2 = coordinator.register_strategy(config2).await;
    assert!(result2.is_err(), "Duplicate strategy name should fail");
}

#[tokio::test]
async fn test_list_strategies() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    // Register multiple strategies
    for i in 0..3 {
        let mut parameters = HashMap::new();
        parameters.insert("risk_limit".to_string(), 0.05);

        let config = StrategyConfig {
            strategy_id: String::new(),
            strategy_name: format!("test_strategy_list_{}", i),
            strategy_type: StrategyType::EqualWeight,
            parameters,
            status: StrategyStatus::Active,
            created_at: chrono::Utc::now(),
            updated_at: chrono::Utc::now(),
        };

        coordinator
            .register_strategy(config)
            .await
            .expect("Failed to register strategy");
    }

    // List strategies
    let result = coordinator.list_strategies().await;
    assert!(result.is_ok(), "List strategies should succeed");

    let strategies = result.expect("Strategies list should be returned");
    assert!(strategies.len() >= 3, "Should have at least 3 strategies");

    // Verify all strategies have required fields
    for strategy in &strategies {
        assert!(!strategy.strategy_id.is_empty());
        assert!(!strategy.strategy_name.is_empty());
    }
}

#[tokio::test]
async fn test_update_strategy_status() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    // Register a strategy
    let mut parameters = HashMap::new();
    parameters.insert("risk_limit".to_string(), 0.05);

    let config = StrategyConfig {
        strategy_id: String::new(),
        strategy_name: "test_status_update".to_string(),
        strategy_type: StrategyType::Momentum,
        parameters,
        status: StrategyStatus::Active,
        created_at: chrono::Utc::now(),
        updated_at: chrono::Utc::now(),
    };

    let strategy_id = coordinator
        .register_strategy(config)
        .await
        .expect("Failed to register");

    // Update status to Paused
    let result = coordinator
        .update_status(&strategy_id, StrategyStatus::Paused)
        .await;
    assert!(result.is_ok(), "Status update should succeed");

    // Verify status changed
    let strategies = coordinator.list_strategies().await.expect("Failed to list");
    let updated_strategy = strategies.iter().find(|s| s.strategy_id == strategy_id);
    assert!(updated_strategy.is_some());
    assert_eq!(updated_strategy.unwrap().status, StrategyStatus::Paused);
}

#[tokio::test]
async fn test_update_nonexistent_strategy_status() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    // Try to update non-existent strategy
    let fake_id = uuid::Uuid::new_v4().to_string();
    let result = coordinator
        .update_status(&fake_id, StrategyStatus::Stopped)
        .await;

    assert!(
        result.is_err(),
        "Updating non-existent strategy should fail"
    );
}

#[tokio::test]
async fn test_get_active_strategies() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    // Register strategies with different statuses
    let statuses = [
        StrategyStatus::Active,
        StrategyStatus::Paused,
        StrategyStatus::Active,
        StrategyStatus::Stopped,
        StrategyStatus::Active,
    ];

    for (i, status) in statuses.iter().enumerate() {
        let mut parameters = HashMap::new();
        parameters.insert("risk_limit".to_string(), 0.05);

        let config = StrategyConfig {
            strategy_id: String::new(),
            strategy_name: format!("test_active_filter_{}", i),
            strategy_type: StrategyType::EqualWeight,
            parameters,
            status: status.clone(),
            created_at: chrono::Utc::now(),
            updated_at: chrono::Utc::now(),
        };

        coordinator
            .register_strategy(config)
            .await
            .expect("Failed to register");
    }

    // Get only active strategies
    let result = coordinator.get_active_strategies().await;
    assert!(result.is_ok(), "Get active strategies should succeed");

    let active_strategies = result.expect("Active strategies should be returned");

    // Should have at least 3 active strategies from this test
    let test_active_count = active_strategies
        .iter()
        .filter(|s| s.strategy_name.starts_with("test_active_filter_"))
        .count();
    assert_eq!(
        test_active_count, 3,
        "Should have exactly 3 active strategies from this test"
    );

    // All returned strategies should be active
    for strategy in &active_strategies {
        assert_eq!(strategy.status, StrategyStatus::Active);
    }
}

#[tokio::test]
async fn test_get_strategy_by_id() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    // Register a strategy
    let mut parameters = HashMap::new();
    parameters.insert("risk_limit".to_string(), 0.08);
    parameters.insert("lookback_period".to_string(), 20.0);

    let config = StrategyConfig {
        strategy_id: String::new(),
        strategy_name: "test_get_by_id".to_string(),
        strategy_type: StrategyType::MeanReversion,
        parameters: parameters.clone(),
        status: StrategyStatus::Active,
        created_at: chrono::Utc::now(),
        updated_at: chrono::Utc::now(),
    };

    let strategy_id = coordinator
        .register_strategy(config)
        .await
        .expect("Failed to register");

    // Get strategy by ID
    let result = coordinator.get_strategy(&strategy_id).await;
    assert!(result.is_ok(), "Get strategy by ID should succeed");

    let strategy = result.expect("Strategy should be returned");
    assert_eq!(strategy.strategy_id, strategy_id);
    assert_eq!(strategy.strategy_name, "test_get_by_id");
    assert_eq!(strategy.strategy_type, StrategyType::MeanReversion);
    assert_eq!(strategy.parameters.get("risk_limit"), Some(&0.08));
}

#[tokio::test]
async fn test_get_nonexistent_strategy() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    let fake_id = uuid::Uuid::new_v4().to_string();
    let result = coordinator.get_strategy(&fake_id).await;

    assert!(result.is_err(), "Getting non-existent strategy should fail");
}

#[tokio::test]
async fn test_strategy_performance() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    let mut parameters = HashMap::new();
    parameters.insert("risk_limit".to_string(), 0.05);

    let config = StrategyConfig {
        strategy_id: String::new(),
        strategy_name: "test_performance".to_string(),
        strategy_type: StrategyType::EqualWeight,
        parameters,
        status: StrategyStatus::Active,
        created_at: chrono::Utc::now(),
        updated_at: chrono::Utc::now(),
    };

    let start = std::time::Instant::now();
    let result = coordinator.register_strategy(config).await;
    let duration = start.elapsed();

    assert!(result.is_ok(), "Strategy registration should succeed");

    // Performance target: < 50ms
    assert!(
        duration.as_millis() < 50,
        "Strategy registration took {}ms (target: <50ms)",
        duration.as_millis()
    );

    println!(
        "Strategy registration completed in {}ms",
        duration.as_millis()
    );
}

#[tokio::test]
async fn test_list_strategies_performance() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    let start = std::time::Instant::now();
    let result = coordinator.list_strategies().await;
    let duration = start.elapsed();

    assert!(result.is_ok(), "List strategies should succeed");

    // Performance target: < 50ms
    assert!(
        duration.as_millis() < 50,
        "List strategies took {}ms (target: <50ms)",
        duration.as_millis()
    );

    println!("List strategies completed in {}ms", duration.as_millis());
}

#[tokio::test]
async fn test_update_status_performance() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    // Register a strategy first
    let mut parameters = HashMap::new();
    parameters.insert("risk_limit".to_string(), 0.05);

    let config = StrategyConfig {
        strategy_id: String::new(),
        strategy_name: "test_update_performance".to_string(),
        strategy_type: StrategyType::RiskParity,
        parameters,
        status: StrategyStatus::Active,
        created_at: chrono::Utc::now(),
        updated_at: chrono::Utc::now(),
    };

    let strategy_id = coordinator
        .register_strategy(config)
        .await
        .expect("Failed to register");

    // Measure update performance
    let start = std::time::Instant::now();
    let result = coordinator
        .update_status(&strategy_id, StrategyStatus::Paused)
        .await;
    let duration = start.elapsed();

    assert!(result.is_ok(), "Status update should succeed");

    // Performance target: < 50ms
    assert!(
        duration.as_millis() < 50,
        "Status update took {}ms (target: <50ms)",
        duration.as_millis()
    );

    println!("Status update completed in {}ms", duration.as_millis());
}

#[tokio::test]
async fn test_all_strategy_types() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    let strategy_types = [
        StrategyType::EqualWeight,
        StrategyType::RiskParity,
        StrategyType::MLOptimized,
        StrategyType::MeanVariance,
        StrategyType::Momentum,
        StrategyType::MeanReversion,
    ];

    for (i, strategy_type) in strategy_types.iter().enumerate() {
        let mut parameters = HashMap::new();
        parameters.insert("risk_limit".to_string(), 0.05);

        let config = StrategyConfig {
            strategy_id: String::new(),
            strategy_name: format!("test_type_{}_{:?}", i, strategy_type),
            strategy_type: strategy_type.clone(),
            parameters,
            status: StrategyStatus::Active,
            created_at: chrono::Utc::now(),
            updated_at: chrono::Utc::now(),
        };

        let result = coordinator.register_strategy(config).await;
        assert!(
            result.is_ok(),
            "Should be able to register {:?} strategy",
            strategy_type
        );
    }
}

#[tokio::test]
async fn test_empty_parameters() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    let config = StrategyConfig {
        strategy_id: String::new(),
        strategy_name: "test_empty_params".to_string(),
        strategy_type: StrategyType::EqualWeight,
        parameters: HashMap::new(), // Empty parameters
        status: StrategyStatus::Active,
        created_at: chrono::Utc::now(),
        updated_at: chrono::Utc::now(),
    };

    let result = coordinator.register_strategy(config).await;
    assert!(result.is_ok(), "Empty parameters should be allowed");
}

#[tokio::test]
async fn test_complex_parameters() {
    let pool = setup_test_db().await;
    let coordinator = StrategyCoordinator::new(pool);

    let mut parameters = HashMap::new();
    parameters.insert("risk_limit".to_string(), 0.05);
    parameters.insert("rebalance_threshold".to_string(), 0.02);
    parameters.insert("lookback_period".to_string(), 20.0);
    parameters.insert("volatility_target".to_string(), 0.15);
    parameters.insert("max_position_size".to_string(), 0.10);

    let config = StrategyConfig {
        strategy_id: String::new(),
        strategy_name: "test_complex_params".to_string(),
        strategy_type: StrategyType::MLOptimized,
        parameters: parameters.clone(),
        status: StrategyStatus::Active,
        created_at: chrono::Utc::now(),
        updated_at: chrono::Utc::now(),
    };

    let strategy_id = coordinator
        .register_strategy(config)
        .await
        .expect("Failed to register");

    // Verify parameters were stored correctly
    let strategy = coordinator
        .get_strategy(&strategy_id)
        .await
        .expect("Failed to get strategy");
    assert_eq!(strategy.parameters.len(), 5);
    assert_eq!(strategy.parameters.get("risk_limit"), Some(&0.05));
    assert_eq!(strategy.parameters.get("lookback_period"), Some(&20.0));
}
