//! Comprehensive tests for configuration loading and validation.
//!
//! Tests cover:
//! - Configuration loading from multiple sources (YAML, environment, Vault)
//! - Configuration validation (required fields, ranges)
//! - Default values and precedence (CLI > ENV > DEFAULT)
//! - Schema validation
//! - Edge cases and error handling

use config::manager::{ConfigManager, ConfigManagerBuilder, ServiceConfig};
use config::runtime::{Environment, RuntimeConfig};
use config::vault::VaultConfig;
use serde_json::json;
use std::env;

#[test]
fn test_service_config_required_fields() {
    // Valid config with all required fields
    let valid_config = ServiceConfig {
        name: "test_service".to_owned(),
        environment: "production".to_owned(),
        version: "1.0.0".to_owned(),
        settings: json!({"key": "value"}),
    };

    assert_eq!(valid_config.name, "test_service");
    assert!(!valid_config.name.is_empty());
    assert!(!valid_config.environment.is_empty());
}

#[test]
fn test_service_config_empty_name_validation() {
    // Config with empty name (should be caught by validation)
    let config = ServiceConfig {
        name: String::new(),
        environment: "production".to_owned(),
        version: "1.0.0".to_owned(),
        settings: json!({}),
    };

    assert!(config.name.is_empty());
}

#[test]
fn test_service_config_empty_environment_validation() {
    // Config with empty environment
    let config = ServiceConfig {
        name: "service".to_owned(),
        environment: String::new(),
        version: "1.0.0".to_owned(),
        settings: json!({}),
    };

    assert!(config.environment.is_empty());
}

#[test]
fn test_vault_config_required_fields_validation() {
    // Valid Vault config
    let valid_config = VaultConfig::new(
        "https://vault.example.com:8200".to_owned(),
        "token-12345".to_owned(),
        "secret/".to_owned(),
    );

    assert!(valid_config.validate().is_ok());
}

#[test]
fn test_vault_config_empty_url_validation() {
    // Vault config with empty URL
    let invalid_config = VaultConfig::new(
        String::new(),
        "token-12345".to_owned(),
        "secret/".to_owned(),
    );

    let result = invalid_config.validate();
    assert!(result.is_err());
    assert_eq!(result.unwrap_err(), "Vault URL cannot be empty");
}

#[test]
fn test_vault_config_empty_token_validation() {
    // Vault config with empty token
    let invalid_config = VaultConfig::new(
        "https://vault.example.com:8200".to_owned(),
        String::new(),
        "secret/".to_owned(),
    );

    let result = invalid_config.validate();
    assert!(result.is_err());
    assert_eq!(result.unwrap_err(), "Vault token cannot be empty");
}

#[test]
fn test_vault_config_empty_mount_path_validation() {
    // Vault config with empty mount path
    let invalid_config = VaultConfig::new(
        "https://vault.example.com:8200".to_owned(),
        "token-12345".to_owned(),
        String::new(),
    );

    let result = invalid_config.validate();
    assert!(result.is_err());
    assert_eq!(result.unwrap_err(), "Vault mount path cannot be empty");
}

#[test]
#[serial_test::serial]
fn test_environment_detection_development() {
    // Set environment variable
    env::set_var("ENVIRONMENT", "development");

    let detected = Environment::detect();
    assert_eq!(detected, Environment::Development);
    assert!(detected.is_development());
    assert!(!detected.is_production());

    // Clean up
    env::remove_var("ENVIRONMENT");
}

#[test]
#[serial_test::serial]
fn test_environment_detection_production() {
    // Test both "production" and "prod" variants
    env::set_var("ENVIRONMENT", "production");
    let detected = Environment::detect();
    assert_eq!(detected, Environment::Production);
    assert!(detected.is_production());

    env::set_var("ENVIRONMENT", "prod");
    let detected = Environment::detect();
    assert_eq!(detected, Environment::Production);

    env::remove_var("ENVIRONMENT");
}

#[test]
#[serial_test::serial]
fn test_environment_detection_staging() {
    env::set_var("ENVIRONMENT", "staging");
    let detected = Environment::detect();
    assert_eq!(detected, Environment::Staging);

    env::set_var("ENVIRONMENT", "stage");
    let detected = Environment::detect();
    assert_eq!(detected, Environment::Staging);

    env::remove_var("ENVIRONMENT");
}

#[test]
#[serial_test::serial]
fn test_environment_detection_fallback() {
    // Test fallback to development for invalid values
    env::set_var("ENVIRONMENT", "invalid");
    let detected = Environment::detect();
    assert_eq!(detected, Environment::Development);

    // Test fallback when environment variable is not set
    env::remove_var("ENVIRONMENT");
    let detected = Environment::detect();
    assert_eq!(detected, Environment::Development);
}

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

    // Verify development has relaxed timeouts
    assert!(config.database.query_timeout.as_millis() >= 1000);
    assert!(config.database.pool_size >= 10);
}

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

    // Verify production has tight timeouts for HFT
    assert!(config.database.query_timeout.as_millis() <= 1000);
    assert!(config.database.pool_size >= 15);
}

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

    // Verify staging is between development and production
    let dev = RuntimeConfig::with_defaults(Environment::Development);
    let prod = RuntimeConfig::with_defaults(Environment::Production);

    assert!(config.database.query_timeout >= prod.database.query_timeout);
    assert!(config.database.query_timeout <= dev.database.query_timeout);
}

#[test]
fn test_config_manager_builder_pattern() {
    // Test fluent builder pattern
    let config = ServiceConfig {
        name: "test_service".to_owned(),
        environment: "test".to_owned(),
        version: "1.0.0".to_owned(),
        settings: json!({"test": "value"}),
    };

    let manager = ConfigManagerBuilder::new(config)
        .with_cache_timeout(std::time::Duration::from_secs(120))
        .build();

    let retrieved = manager.get_config();
    assert_eq!(retrieved.name, "test_service");
}

#[test]
fn test_config_manager_cache_timeout_configuration() {
    let config = ServiceConfig {
        name: "test".to_owned(),
        environment: "test".to_owned(),
        version: "1.0.0".to_owned(),
        settings: json!({}),
    };

    let custom_timeout = std::time::Duration::from_secs(600);
    let manager = ConfigManagerBuilder::new(config)
        .with_cache_timeout(custom_timeout)
        .build();

    // Verify manager was created (cache_timeout is private, can't test directly)
    let retrieved = manager.get_config();
    assert_eq!(retrieved.name, "test");
}

#[test]
fn test_service_config_settings_json_validation() {
    // Test valid JSON settings
    let config = ServiceConfig {
        name: "test".to_owned(),
        environment: "test".to_owned(),
        version: "1.0.0".to_owned(),
        settings: json!({
            "database": {
                "host": "localhost",
                "port": 5432
            },
            "features": ["trading", "ml"]
        }),
    };

    // Verify JSON structure
    assert!(config.settings.is_object());
    assert!(config.settings.get("database").is_some());
    assert!(config.settings.get("features").is_some());
}

#[test]
fn test_service_config_version_format() {
    // Test various version formats
    let versions = vec!["1.0.0", "2.1.3", "0.0.1-alpha", "1.2.3-beta.1"];

    for version in versions {
        let config = ServiceConfig {
            name: "test".to_owned(),
            environment: "test".to_owned(),
            version: version.to_owned(),
            settings: json!({}),
        };

        assert_eq!(config.version, version);
    }
}

#[test]
fn test_config_manager_multiple_instances() {
    // Test that multiple ConfigManager instances can coexist
    let config1 = ServiceConfig {
        name: "service1".to_owned(),
        environment: "prod".to_owned(),
        version: "1.0.0".to_owned(),
        settings: json!({}),
    };

    let config2 = ServiceConfig {
        name: "service2".to_owned(),
        environment: "dev".to_owned(),
        version: "2.0.0".to_owned(),
        settings: json!({}),
    };

    let manager1 = ConfigManager::new(config1);
    let manager2 = ConfigManager::new(config2);

    assert_eq!(manager1.get_config().name, "service1");
    assert_eq!(manager2.get_config().name, "service2");
}

#[test]
fn test_vault_config_namespace_optional() {
    // Test Vault config without namespace
    let config_without = VaultConfig::new(
        "https://vault.example.com".to_owned(),
        "token".to_owned(),
        "secret/".to_owned(),
    );
    assert!(config_without.namespace.is_none());

    // Test Vault config with namespace
    let config_with = VaultConfig::new(
        "https://vault.example.com".to_owned(),
        "token".to_owned(),
        "secret/".to_owned(),
    )
    .with_namespace("production".to_owned());

    assert!(config_with.namespace.is_some());
    assert_eq!(config_with.namespace.as_ref().unwrap(), "production");
}

#[test]
#[serial_test::serial]
fn test_runtime_config_precedence() {
    // Test that environment variables override defaults
    env::set_var("DATABASE_POOL_SIZE", "25");

    let config = RuntimeConfig::from_env().unwrap();
    assert_eq!(config.database.pool_size, 25);

    env::remove_var("DATABASE_POOL_SIZE");
}

#[test]
#[serial_test::serial]
fn test_runtime_config_invalid_env_var_fallback() {
    // Test that invalid environment variable falls back to default
    env::set_var("DATABASE_POOL_SIZE", "invalid");

    // Should fall back to default instead of panicking
    let _result = RuntimeConfig::from_env();
    // Invalid value should cause error or use default

    env::remove_var("DATABASE_POOL_SIZE");
}

#[test]
fn test_service_config_serialization_roundtrip() {
    let original = ServiceConfig {
        name: "test_service".to_owned(),
        environment: "production".to_owned(),
        version: "1.2.3".to_owned(),
        settings: json!({
            "key1": "value1",
            "key2": 42,
            "nested": {"inner": "data"}
        }),
    };

    // Serialize to JSON
    let serialized = serde_json::to_string(&original).unwrap();

    // Deserialize back
    let deserialized: ServiceConfig = serde_json::from_str(&serialized).unwrap();

    // Verify round-trip
    assert_eq!(original.name, deserialized.name);
    assert_eq!(original.environment, deserialized.environment);
    assert_eq!(original.version, deserialized.version);
    assert_eq!(original.settings, deserialized.settings);
}

#[test]
fn test_config_manager_concurrent_cache_access() {
    use std::sync::Arc;
    use std::thread;

    let config = ServiceConfig {
        name: "concurrent_test".to_owned(),
        environment: "test".to_owned(),
        version: "1.0.0".to_owned(),
        settings: json!({}),
    };

    let manager = Arc::new(ConfigManager::new(config));
    let mut handles = vec![];

    // Spawn 10 threads that write and read cache concurrently
    for i in 0..10 {
        let manager_clone = Arc::clone(&manager);
        let handle = thread::spawn(move || {
            let key = format!("thread_key_{}", i);
            let value = json!({"thread_id": i, "data": format!("data_{}", i)});

            manager_clone.set_cached_config(key.clone(), value);
            let retrieved = manager_clone.get_cached_config(&key);

            assert!(retrieved.is_some());
            retrieved.unwrap()
        });
        handles.push(handle);
    }

    // Verify all threads completed successfully
    for handle in handles {
        let result = handle.join();
        assert!(result.is_ok());
    }
}

#[test]
#[serial_test::serial]
fn test_environment_case_insensitivity() {
    // Test case-insensitive environment detection
    let test_cases = vec![
        ("PRODUCTION", Environment::Production),
        ("Production", Environment::Production),
        ("production", Environment::Production),
        ("PROD", Environment::Production),
        ("Prod", Environment::Production),
        ("STAGING", Environment::Staging),
        ("staging", Environment::Staging),
        ("STAGE", Environment::Staging),
        ("DEVELOPMENT", Environment::Development),
        ("development", Environment::Development),
    ];

    for (input, expected) in test_cases {
        env::set_var("ENVIRONMENT", input);
        let detected = Environment::detect();
        assert_eq!(detected, expected, "Failed for input: {}", input);
    }

    env::remove_var("ENVIRONMENT");
}

#[test]
fn test_config_manager_cache_expiration() {
    use std::thread;
    use std::time::Duration;

    let config = ServiceConfig {
        name: "cache_test".to_owned(),
        environment: "test".to_owned(),
        version: "1.0.0".to_owned(),
        settings: json!({}),
    };

    // Create manager with very short cache timeout (100ms)
    let manager = ConfigManagerBuilder::new(config)
        .with_cache_timeout(Duration::from_millis(100))
        .build();

    // Set cache value
    let value = json!({"data": "test"});
    manager.set_cached_config("test_key".to_owned(), value);

    // Should be available immediately
    assert!(manager.get_cached_config("test_key").is_some());

    // Wait for cache to expire
    thread::sleep(Duration::from_millis(150));

    // Should be expired now (might be None or Some depending on cleanup timing)
    let _retrieved = manager.get_cached_config("test_key");
    // Note: The cache might still exist but be expired, depends on implementation
}

#[test]
fn test_vault_config_debug_redaction() {
    let config = VaultConfig::new(
        "https://vault.example.com:8200".to_owned(),
        "super-secret-token".to_owned(),
        "secret/".to_owned(),
    );

    // Debug output should redact the token
    let debug_output = format!("{:?}", config);
    assert!(debug_output.contains("***REDACTED***"));
    assert!(!debug_output.contains("super-secret-token"));
}

#[test]
fn test_vault_config_serialization_redaction() {
    let config = VaultConfig::new(
        "https://vault.example.com:8200".to_owned(),
        "super-secret-token".to_owned(),
        "secret/".to_owned(),
    );

    // Serialized output should redact the token
    let serialized = serde_json::to_string(&config).unwrap();
    assert!(serialized.contains("***REDACTED***"));
    assert!(!serialized.contains("super-secret-token"));
}
