//! Comprehensive tests for runtime configuration (config/src/runtime.rs)
//!
//! Tests cover:
//! - Environment detection and override
//! - Configuration validation
//! - Environment variable parsing
//! - Environment-specific defaults
//! - Cross-field validation
//! - Rollback on invalid config

use config::error::ConfigError;
use config::runtime::{
    CacheRuntimeConfig, DatabaseRuntimeConfig, Environment, LimitsConfig, RuntimeConfig,
    TimeoutConfig,
};
use serial_test::serial;
use std::env;
use std::time::Duration;

// ============================================================================
// Helper Functions
// ============================================================================

/// Sets an environment variable for the duration of a test
fn with_env_var<F>(key: &str, value: &str, test: F)
where
    F: FnOnce(),
{
    env::set_var(key, value);
    test();
    env::remove_var(key);
}

/// Sets multiple environment variables for a test
fn with_env_vars<F>(vars: Vec<(&str, &str)>, test: F)
where
    F: FnOnce(),
{
    for (key, value) in &vars {
        env::set_var(key, value);
    }
    test();
    for (key, _) in &vars {
        env::remove_var(key);
    }
}

// ============================================================================
// 1. Environment Detection and Configuration Tests (8 tests)
// ============================================================================

#[test]
#[serial]
fn test_environment_detect_production() {
    with_env_var("ENVIRONMENT", "production", || {
        let env = Environment::detect();
        assert_eq!(env, Environment::Production);
        assert!(env.is_production());
        assert!(!env.is_development());
    });
}

#[test]
#[serial]
fn test_environment_detect_prod_alias() {
    with_env_var("ENVIRONMENT", "prod", || {
        let env = Environment::detect();
        assert_eq!(env, Environment::Production);
        assert!(env.is_production());
    });
}

#[test]
#[serial]
fn test_environment_detect_staging() {
    with_env_var("ENVIRONMENT", "staging", || {
        let env = Environment::detect();
        assert_eq!(env, Environment::Staging);
        assert!(!env.is_production());
        assert!(!env.is_development());
    });
}

#[test]
#[serial]
fn test_environment_detect_stage_alias() {
    with_env_var("ENVIRONMENT", "stage", || {
        let env = Environment::detect();
        assert_eq!(env, Environment::Staging);
    });
}

#[test]
#[serial]
fn test_environment_detect_development_default() {
    env::remove_var("ENVIRONMENT");
    let env = Environment::detect();
    assert_eq!(env, Environment::Development);
    assert!(env.is_development());
    assert!(!env.is_production());
}

#[test]
#[serial]
fn test_environment_detect_unknown_fallback() {
    with_env_var("ENVIRONMENT", "invalid", || {
        let env = Environment::detect();
        assert_eq!(env, Environment::Development);
        assert!(env.is_development());
    });
}

#[test]
#[serial]
fn test_environment_case_insensitive() {
    with_env_var("ENVIRONMENT", "PRODUCTION", || {
        let env = Environment::detect();
        assert_eq!(env, Environment::Production);
    });

    with_env_var("ENVIRONMENT", "StAgInG", || {
        let env = Environment::detect();
        assert_eq!(env, Environment::Staging);
    });
}

#[test]
#[serial]
fn test_runtime_config_from_env_respects_environment() {
    with_env_var("ENVIRONMENT", "production", || {
        let config = RuntimeConfig::from_env().unwrap();
        assert_eq!(config.environment, Environment::Production);
        // Production should have tight timeouts
        assert!(config.database.query_timeout.as_millis() <= 1000);
        assert!(config.cache.position_ttl.as_secs() <= 60);
    });
}

// ============================================================================
// 2. Configuration Validation Tests (7 tests)
// ============================================================================

#[test]
fn test_database_config_validation_zero_query_timeout() {
    let mut config = DatabaseRuntimeConfig::with_defaults(Environment::Production);
    config.query_timeout = Duration::from_millis(0);

    let result = config.validate();
    assert!(result.is_err());
    match result {
        Err(ConfigError::Invalid(msg)) => {
            assert!(msg.contains("Query timeout"));
            assert!(msg.contains("positive"));
        }
        _ => panic!("Expected Invalid error for zero query timeout"),
    }
}

#[test]
fn test_database_config_validation_zero_pool_size() {
    let mut config = DatabaseRuntimeConfig::with_defaults(Environment::Production);
    config.pool_size = 0;

    let result = config.validate();
    assert!(result.is_err());
    match result {
        Err(ConfigError::Invalid(msg)) => {
            assert!(msg.contains("Pool size"));
            assert!(msg.contains("positive"));
        }
        _ => panic!("Expected Invalid error for zero pool size"),
    }
}

#[test]
fn test_database_config_validation_pool_size_exceeds_max() {
    let mut config = DatabaseRuntimeConfig::with_defaults(Environment::Production);
    config.pool_size = 200;
    config.max_pool_size = 100;

    let result = config.validate();
    assert!(result.is_err());
    match result {
        Err(ConfigError::Invalid(msg)) => {
            assert!(msg.contains("Pool size"));
            assert!(msg.contains("max pool size"));
        }
        _ => panic!("Expected Invalid error for pool size > max"),
    }
}

#[test]
fn test_cache_config_validation_zero_ttl() {
    let mut config = CacheRuntimeConfig::with_defaults(Environment::Production);
    config.position_ttl = Duration::from_secs(0);

    let result = config.validate();
    assert!(result.is_err());
    match result {
        Err(ConfigError::Invalid(msg)) => {
            assert!(msg.contains("TTL"));
            assert!(msg.contains("positive"));
        }
        _ => panic!("Expected Invalid error for zero TTL"),
    }
}

#[test]
fn test_limits_config_validation_zero_retry_attempts() {
    let mut config = LimitsConfig::with_defaults(Environment::Production);
    config.retry_max_attempts = 0;

    let result = config.validate();
    assert!(result.is_err());
    match result {
        Err(ConfigError::Invalid(msg)) => {
            assert!(msg.contains("Retry"));
            assert!(msg.contains("positive"));
        }
        _ => panic!("Expected Invalid error for zero retry attempts"),
    }
}

#[test]
fn test_limits_config_validation_invalid_backoff_multiplier() {
    let mut config = LimitsConfig::with_defaults(Environment::Production);
    config.retry_backoff_multiplier = 0.5;

    let result = config.validate();
    assert!(result.is_err());
    match result {
        Err(ConfigError::Invalid(msg)) => {
            assert!(msg.contains("Backoff multiplier"));
            assert!(msg.contains("> 1.0"));
        }
        _ => panic!("Expected Invalid error for backoff multiplier <= 1.0"),
    }
}

#[test]
fn test_limits_config_validation_var_confidence_out_of_range() {
    let mut config = LimitsConfig::with_defaults(Environment::Production);

    // Test confidence > 1.0
    config.risk_var_confidence = 1.5;
    let result = config.validate();
    assert!(result.is_err());
    match result {
        Err(ConfigError::Invalid(msg)) => {
            assert!(msg.contains("VaR confidence"));
            assert!(msg.contains("0.0") && msg.contains("1.0"));
        }
        _ => panic!("Expected Invalid error for VaR confidence > 1.0"),
    }

    // Test confidence < 0.0
    config.risk_var_confidence = -0.5;
    let result = config.validate();
    assert!(result.is_err());
}

// ============================================================================
// 3. Environment Variable Parsing Tests (5 tests)
// ============================================================================

#[test]
#[serial]
fn test_parse_database_config_from_env_vars() {
    let vars = vec![
        ("DATABASE_QUERY_TIMEOUT_MS", "2500"),
        ("DATABASE_CONNECTION_TIMEOUT_MS", "300"),
        ("DATABASE_POOL_SIZE", "25"),
        ("DATABASE_MAX_POOL_SIZE", "150"),
    ];

    with_env_vars(vars, || {
        let config = DatabaseRuntimeConfig::from_env(Environment::Development).unwrap();

        assert_eq!(config.query_timeout.as_millis(), 2500);
        assert_eq!(config.connection_timeout.as_millis(), 300);
        assert_eq!(config.pool_size, 25);
        assert_eq!(config.max_pool_size, 150);
    });
}

#[test]
#[serial]
fn test_parse_cache_config_from_env_vars() {
    let vars = vec![
        ("CACHE_POSITION_TTL_SECS", "45"),
        ("CACHE_VAR_TTL_SECS", "1800"),
        ("CACHE_MARKET_DATA_TTL_SECS", "180"),
    ];

    with_env_vars(vars, || {
        let config = CacheRuntimeConfig::from_env(Environment::Development).unwrap();

        assert_eq!(config.position_ttl.as_secs(), 45);
        assert_eq!(config.var_ttl.as_secs(), 1800);
        assert_eq!(config.market_data_ttl.as_secs(), 180);
    });
}

#[test]
#[serial]
fn test_parse_timeout_config_from_env_vars() {
    let vars = vec![
        ("NETWORK_GRPC_CONNECT_TIMEOUT_SECS", "8"),
        ("NETWORK_GRPC_REQUEST_TIMEOUT_SECS", "15"),
        ("NETWORK_MAX_CONCURRENT_CONNECTIONS", "200"),
    ];

    with_env_vars(vars, || {
        let config = TimeoutConfig::from_env(Environment::Development).unwrap();

        assert_eq!(config.grpc_connect_timeout.as_secs(), 8);
        assert_eq!(config.grpc_request_timeout.as_secs(), 15);
        assert_eq!(config.max_concurrent_connections, 200);
    });
}

#[test]
#[serial]
fn test_parse_limits_config_from_env_vars() {
    let vars = vec![
        ("RETRY_INITIAL_DELAY_MS", "150"),
        ("RETRY_MAX_ATTEMPTS", "5"),
        ("RETRY_BACKOFF_MULTIPLIER", "2.5"),
        ("ML_MAX_BATCH_SIZE", "2048"),
        ("RISK_VAR_CONFIDENCE", "0.99"),
    ];

    with_env_vars(vars, || {
        let config = LimitsConfig::from_env(Environment::Development).unwrap();

        assert_eq!(config.retry_initial_delay.as_millis(), 150);
        assert_eq!(config.retry_max_attempts, 5);
        assert_eq!(config.retry_backoff_multiplier, 2.5);
        assert_eq!(config.ml_max_batch_size, 2048);
        assert_eq!(config.risk_var_confidence, 0.99);
    });
}

#[test]
#[serial]
fn test_parse_invalid_env_var_returns_error() {
    with_env_var("DATABASE_POOL_SIZE", "invalid", || {
        let result = DatabaseRuntimeConfig::from_env(Environment::Development);
        assert!(result.is_err());
        match result {
            Err(ConfigError::Invalid(msg)) => {
                assert!(msg.contains("Invalid u32"));
                assert!(msg.contains("DATABASE_POOL_SIZE"));
            }
            _ => panic!("Expected Invalid error for invalid env var"),
        }
    });
}

// ============================================================================
// 4. Environment-Specific Defaults Tests (5 tests)
// ============================================================================

#[test]
fn test_development_has_relaxed_timeouts() {
    let dev_config = RuntimeConfig::with_defaults(Environment::Development);
    let prod_config = RuntimeConfig::with_defaults(Environment::Production);

    // Development should have more relaxed timeouts for debugging
    assert!(dev_config.database.query_timeout > prod_config.database.query_timeout);
    assert!(dev_config.cache.position_ttl > prod_config.cache.position_ttl);
    assert!(dev_config.timeouts.grpc_request_timeout > prod_config.timeouts.grpc_request_timeout);
    assert!(dev_config.limits.safety_check_timeout > prod_config.limits.safety_check_timeout);
}

#[test]
fn test_production_has_tight_timeouts_for_hft() {
    let prod_config = RuntimeConfig::with_defaults(Environment::Production);

    // Production should have tight timeouts for HFT
    assert!(prod_config.database.query_timeout.as_millis() <= 1000);
    assert!(prod_config.cache.position_ttl.as_secs() <= 60);
    assert!(prod_config.timeouts.grpc_request_timeout.as_secs() <= 10);
    assert!(prod_config.limits.safety_check_timeout.as_millis() <= 5);
    assert!(prod_config.limits.ml_inference_timeout.as_millis() <= 100);
}

#[test]
fn test_staging_is_between_dev_and_prod() {
    let dev_config = RuntimeConfig::with_defaults(Environment::Development);
    let staging_config = RuntimeConfig::with_defaults(Environment::Staging);
    let prod_config = RuntimeConfig::with_defaults(Environment::Production);

    // Staging should have settings between dev and prod
    assert!(staging_config.database.query_timeout > prod_config.database.query_timeout);
    assert!(staging_config.database.query_timeout < dev_config.database.query_timeout);

    assert!(staging_config.cache.position_ttl > prod_config.cache.position_ttl);
    assert!(staging_config.cache.position_ttl < dev_config.cache.position_ttl);

    assert!(staging_config.limits.retry_max_attempts >= prod_config.limits.retry_max_attempts);
    assert!(staging_config.limits.retry_max_attempts <= dev_config.limits.retry_max_attempts);
}

#[test]
fn test_production_pool_sizes_are_largest() {
    let dev_config = DatabaseRuntimeConfig::with_defaults(Environment::Development);
    let staging_config = DatabaseRuntimeConfig::with_defaults(Environment::Staging);
    let prod_config = DatabaseRuntimeConfig::with_defaults(Environment::Production);

    // Production should have the largest pool sizes for throughput
    assert!(prod_config.pool_size >= staging_config.pool_size);
    assert!(staging_config.pool_size >= dev_config.pool_size);
    assert!(prod_config.max_pool_size >= staging_config.max_pool_size);
    assert!(staging_config.max_pool_size >= dev_config.max_pool_size);
}

#[test]
fn test_production_ml_batch_sizes_are_largest() {
    let dev_config = LimitsConfig::with_defaults(Environment::Development);
    let staging_config = LimitsConfig::with_defaults(Environment::Staging);
    let prod_config = LimitsConfig::with_defaults(Environment::Production);

    // Production should have largest batch sizes for throughput
    assert!(prod_config.ml_max_batch_size >= staging_config.ml_max_batch_size);
    assert!(staging_config.ml_max_batch_size >= dev_config.ml_max_batch_size);
}

// ============================================================================
// 5. Cross-Field Validation Tests (5 tests)
// ============================================================================

#[test]
fn test_database_pool_size_cross_validation() {
    let mut config = DatabaseRuntimeConfig::with_defaults(Environment::Production);

    // Valid: pool_size < max_pool_size
    config.pool_size = 50;
    config.max_pool_size = 100;
    assert!(config.validate().is_ok());

    // Valid: pool_size == max_pool_size
    config.pool_size = 100;
    config.max_pool_size = 100;
    assert!(config.validate().is_ok());

    // Invalid: pool_size > max_pool_size
    config.pool_size = 150;
    config.max_pool_size = 100;
    assert!(config.validate().is_err());
}

#[test]
fn test_limits_config_ml_batch_size_validation() {
    let mut config = LimitsConfig::with_defaults(Environment::Production);

    // Valid: positive batch size
    config.ml_max_batch_size = 1024;
    assert!(config.validate().is_ok());

    // Invalid: zero batch size
    config.ml_max_batch_size = 0;
    assert!(config.validate().is_err());
}

#[test]
fn test_limits_config_var_lookback_days_validation() {
    let mut config = LimitsConfig::with_defaults(Environment::Production);

    // Valid: positive lookback
    config.risk_var_lookback_days = 252;
    assert!(config.validate().is_ok());

    // Invalid: zero lookback
    config.risk_var_lookback_days = 0;
    let result = config.validate();
    assert!(result.is_err());
    match result {
        Err(ConfigError::Invalid(msg)) => {
            assert!(msg.contains("VaR lookback"));
            assert!(msg.contains("positive"));
        }
        _ => panic!("Expected Invalid error for zero lookback days"),
    }
}

#[test]
fn test_runtime_config_validates_all_subconfigs() {
    let mut config = RuntimeConfig::with_defaults(Environment::Production);

    // Valid configuration
    assert!(config.validate().is_ok());

    // Invalid database config should fail validation
    config.database.pool_size = 200;
    config.database.max_pool_size = 100;
    assert!(config.validate().is_err());

    // Reset and test cache validation
    config = RuntimeConfig::with_defaults(Environment::Production);
    config.cache.position_ttl = Duration::from_secs(0);
    assert!(config.validate().is_err());

    // Reset and test limits validation
    config = RuntimeConfig::with_defaults(Environment::Production);
    config.limits.retry_max_attempts = 0;
    assert!(config.validate().is_err());
}

#[test]
fn test_timeout_config_concurrent_connections_validation() {
    let mut config = TimeoutConfig::with_defaults(Environment::Production);

    // Valid: positive connections
    config.max_concurrent_connections = 100;
    assert!(config.validate().is_ok());

    // Invalid: zero connections
    config.max_concurrent_connections = 0;
    let result = config.validate();
    assert!(result.is_err());
    match result {
        Err(ConfigError::Invalid(msg)) => {
            assert!(msg.contains("concurrent connections"));
            assert!(msg.contains("positive"));
        }
        _ => panic!("Expected Invalid error for zero connections"),
    }
}

// ============================================================================
// 6. Rollback on Invalid Config Tests (3 tests)
// ============================================================================

#[test]
#[serial]
fn test_from_env_with_invalid_config_returns_error() {
    // Set invalid environment variables that will fail validation
    let vars = vec![
        ("DATABASE_POOL_SIZE", "200"),
        ("DATABASE_MAX_POOL_SIZE", "100"), // pool_size > max_pool_size
    ];

    with_env_vars(vars, || {
        let result = RuntimeConfig::from_env_with_environment(Environment::Production);
        assert!(result.is_err());
        // Ensure no partial state is created - error should be returned
        match result {
            Err(ConfigError::Invalid(_)) => { /* Expected */ }
            _ => panic!("Expected Invalid error for pool_size > max_pool_size"),
        }
    });
}

#[test]
#[serial]
fn test_from_env_falls_back_to_defaults_on_missing_vars() {
    // Remove all config env vars
    env::remove_var("DATABASE_QUERY_TIMEOUT_MS");
    env::remove_var("CACHE_POSITION_TTL_SECS");
    env::remove_var("RETRY_MAX_ATTEMPTS");

    let config = RuntimeConfig::from_env_with_environment(Environment::Production).unwrap();

    // Should use defaults
    let defaults = RuntimeConfig::with_defaults(Environment::Production);
    assert_eq!(
        config.database.query_timeout,
        defaults.database.query_timeout
    );
    assert_eq!(config.cache.position_ttl, defaults.cache.position_ttl);
    assert_eq!(
        config.limits.retry_max_attempts,
        defaults.limits.retry_max_attempts
    );
}

#[test]
fn test_with_defaults_never_fails_validation() {
    // All default configurations should be valid
    let dev_config = RuntimeConfig::with_defaults(Environment::Development);
    assert!(dev_config.validate().is_ok());

    let staging_config = RuntimeConfig::with_defaults(Environment::Staging);
    assert!(staging_config.validate().is_ok());

    let prod_config = RuntimeConfig::with_defaults(Environment::Production);
    assert!(prod_config.validate().is_ok());
}

// ============================================================================
// 7. Dynamic Parameter Updates Tests (3 tests)
// ============================================================================

#[test]
fn test_runtime_config_can_be_cloned_for_updates() {
    let original = RuntimeConfig::with_defaults(Environment::Production);
    let mut updated = original.clone();

    // Modify cloned config
    updated.database.pool_size = 30;
    updated.cache.position_ttl = Duration::from_secs(45);

    // Original should be unchanged
    assert_ne!(original.database.pool_size, updated.database.pool_size);
    assert_ne!(original.cache.position_ttl, updated.cache.position_ttl);

    // Both should still be valid
    assert!(original.validate().is_ok());
    assert!(updated.validate().is_ok());
}

#[test]
fn test_runtime_config_serialization_for_hot_reload() {
    let config = RuntimeConfig::with_defaults(Environment::Production);

    // Should be serializable for persistence
    let json = serde_json::to_string(&config).unwrap();
    assert!(json.contains("Production"));
    assert!(json.len() > 100); // Non-trivial serialization

    // Should be deserializable for hot reload
    let deserialized: RuntimeConfig = serde_json::from_str(&json).unwrap();
    assert_eq!(deserialized.environment, Environment::Production);
    assert!(deserialized.validate().is_ok());
}

#[test]
fn test_partial_config_update_preserves_other_fields() {
    let mut config = RuntimeConfig::with_defaults(Environment::Production);
    let original_cache_ttl = config.cache.position_ttl;
    let original_timeout = config.timeouts.grpc_request_timeout;

    // Update only database config
    config.database.pool_size = 35;
    config.database.max_pool_size = 120;

    // Other configs should be unchanged
    assert_eq!(config.cache.position_ttl, original_cache_ttl);
    assert_eq!(config.timeouts.grpc_request_timeout, original_timeout);

    // Updated config should still be valid
    assert!(config.validate().is_ok());
}

// ============================================================================
// 8. Edge Cases and Error Handling Tests (3 tests)
// ============================================================================

#[test]
fn test_extreme_timeout_values() {
    let mut config = DatabaseRuntimeConfig::with_defaults(Environment::Production);

    // Very small timeout (still > 0)
    config.query_timeout = Duration::from_millis(1);
    assert!(config.validate().is_ok());

    // Very large timeout
    config.query_timeout = Duration::from_secs(3600); // 1 hour
    assert!(config.validate().is_ok());
}

#[test]
fn test_var_confidence_edge_values() {
    let mut config = LimitsConfig::with_defaults(Environment::Production);

    // Minimum valid confidence
    config.risk_var_confidence = 0.0;
    assert!(config.validate().is_ok());

    // Maximum valid confidence
    config.risk_var_confidence = 1.0;
    assert!(config.validate().is_ok());

    // Just below minimum
    config.risk_var_confidence = -0.001;
    assert!(config.validate().is_err());

    // Just above maximum
    config.risk_var_confidence = 1.001;
    assert!(config.validate().is_err());
}

#[test]
fn test_cache_ttl_extreme_values() {
    let mut config = CacheRuntimeConfig::with_defaults(Environment::Production);

    // Very short TTL (still > 0)
    config.position_ttl = Duration::from_secs(1);
    config.var_ttl = Duration::from_secs(1);
    assert!(config.validate().is_ok());

    // Very long TTL
    config.position_ttl = Duration::from_secs(86400 * 7); // 1 week
    config.var_ttl = Duration::from_secs(86400 * 30); // 30 days
    assert!(config.validate().is_ok());
}
