//! Integration tests for ConfigLoader system
//! Tests config loading across all platform services

use std::env;
use std::fs;
use std::path::PathBuf;

use rustelo_config::ConfigLoader;
use tempfile::TempDir;

/// Test config struct for integration testing
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
struct TestConfig {
    #[serde(default)]
    pub name: String,
    #[serde(default)]
    pub port: u16,
    #[serde(default)]
    pub enabled: bool,
}

impl ConfigLoader for TestConfig {
    fn service_name() -> &'static str {
        "test-service"
    }

    fn load_from_hierarchy() -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>>
    {
        let service = Self::service_name();

        if let Some(path) = rustelo_config::resolve_config_path(service) {
            return Self::from_path(&path);
        }

        Ok(Self::default())
    }

    fn apply_env_overrides(
        &mut self,
    ) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
        if let Ok(val) = env::var("TEST_SERVICE_NAME") {
            self.name = val;
        }
        if let Ok(val) = env::var("TEST_SERVICE_PORT") {
            if let Ok(port) = val.parse() {
                self.port = port;
            }
        }
        if let Ok(val) = env::var("TEST_SERVICE_ENABLED") {
            self.enabled = val.parse().unwrap_or(self.enabled);
        }
        Ok(())
    }

    fn from_path<P: AsRef<std::path::Path>>(
        path: P,
    ) -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>> {
        let path = path.as_ref();
        let json_value = rustelo_config::format::load_config(path).map_err(|e| {
            let err: Box<dyn std::error::Error + Send + Sync> = Box::new(e);
            err
        })?;

        serde_json::from_value(json_value).map_err(|e| {
            let err_msg = format!("Failed to deserialize test config from {:?}: {}", path, e);
            Box::new(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                err_msg,
            )) as Box<dyn std::error::Error + Send + Sync>
        })
    }
}

#[test]
fn test_default_config_loading() {
    let config = TestConfig::default();
    assert_eq!(config.name, "");
    assert_eq!(config.port, 0);
    assert!(!config.enabled);
}

#[test]
fn test_config_serialization_toml() {
    let config = TestConfig {
        name: "test".to_string(),
        port: 8080,
        enabled: true,
    };

    let toml_str = toml::to_string(&config).expect("Failed to serialize to TOML");
    assert!(toml_str.contains("test"));
    assert!(toml_str.contains("8080"));
}

#[test]
fn test_config_serialization_json() {
    let config = TestConfig {
        name: "test".to_string(),
        port: 8080,
        enabled: true,
    };

    // Test that JSON serialization works (even though load_config only supports
    // TOML/NCL)
    let json_str = serde_json::to_string(&config).expect("Failed to serialize to JSON");
    assert!(json_str.contains("test"));
    assert!(json_str.contains("8080"));

    // Deserialize back from JSON
    let deserialized: TestConfig =
        serde_json::from_str(&json_str).expect("Failed to deserialize from JSON");
    assert_eq!(deserialized.name, "test");
    assert_eq!(deserialized.port, 8080);
    assert!(deserialized.enabled);
}

#[test]
fn test_toml_file_loading() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let config_path = temp_dir.path().join("test.toml");

    let toml_content = r#"
name = "test-service"
port = 9090
enabled = true
"#;

    fs::write(&config_path, toml_content).expect("Failed to write TOML file");

    let config = TestConfig::from_path(&config_path).expect("Failed to load config from TOML file");

    assert_eq!(config.name, "test-service");
    assert_eq!(config.port, 9090);
    assert!(config.enabled);
}

#[test]
fn test_environment_variable_overrides() {
    env::set_var("TEST_SERVICE_NAME", "env-service");
    env::set_var("TEST_SERVICE_PORT", "7777");
    env::set_var("TEST_SERVICE_ENABLED", "false");

    let mut config = TestConfig {
        name: "original".to_string(),
        port: 8080,
        enabled: true,
    };

    config
        .apply_env_overrides()
        .expect("Failed to apply env overrides");

    assert_eq!(config.name, "env-service");
    assert_eq!(config.port, 7777);
    assert!(!config.enabled);

    // Cleanup
    env::remove_var("TEST_SERVICE_NAME");
    env::remove_var("TEST_SERVICE_PORT");
    env::remove_var("TEST_SERVICE_ENABLED");
}

#[test]
fn test_invalid_toml_file() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let config_path = temp_dir.path().join("invalid.toml");

    let invalid_toml = r#"
this is not valid [[ toml
"#;

    fs::write(&config_path, invalid_toml).expect("Failed to write invalid TOML file");

    let result = TestConfig::from_path(&config_path);
    assert!(
        result.is_err(),
        "Expected error loading invalid TOML, but got success"
    );
}

#[test]
fn test_invalid_json_file() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let config_path = temp_dir.path().join("invalid.json");

    let invalid_json = r#"{ this is not valid json"#;

    fs::write(&config_path, invalid_json).expect("Failed to write invalid JSON file");

    let result = TestConfig::from_path(&config_path);
    assert!(
        result.is_err(),
        "Expected error loading invalid JSON, but got success"
    );
}

#[test]
fn test_missing_file_error() {
    let config_path = PathBuf::from("/nonexistent/path/to/config.toml");
    let result = TestConfig::from_path(&config_path);
    assert!(
        result.is_err(),
        "Expected error loading nonexistent file, but got success"
    );
}

#[test]
fn test_nested_config_loading() {
    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
    struct NestedConfig {
        pub service: ServiceSection,
        pub database: DatabaseSection,
    }

    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
    struct ServiceSection {
        pub name: String,
        pub port: u16,
    }

    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
    struct DatabaseSection {
        pub url: String,
        pub pool_size: usize,
    }

    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let config_path = temp_dir.path().join("nested.toml");

    let toml_content = r#"
[service]
name = "test"
port = 8080

[database]
url = "postgres://localhost"
pool_size = 10
"#;

    fs::write(&config_path, toml_content).expect("Failed to write TOML file");

    let content = fs::read_to_string(&config_path).expect("Failed to read file");
    let parsed: NestedConfig = toml::from_str(&content).expect("Failed to parse nested TOML");

    assert_eq!(parsed.service.name, "test");
    assert_eq!(parsed.service.port, 8080);
    assert_eq!(parsed.database.url, "postgres://localhost");
    assert_eq!(parsed.database.pool_size, 10);
}

#[test]
fn test_toml_to_json_conversion() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");

    // Create TOML version
    let toml_path = temp_dir.path().join("config1.toml");
    let toml_content = r#"
name = "toml-service"
port = 8080
enabled = true
"#;
    fs::write(&toml_path, toml_content).expect("Failed to write TOML");

    // Create another TOML version with different values
    let toml_path2 = temp_dir.path().join("config2.toml");
    let toml_content2 = r#"
name = "toml-service-2"
port = 9090
enabled = false
"#;
    fs::write(&toml_path2, toml_content2).expect("Failed to write TOML 2");

    // Load both via ConfigLoader (which converts TOML to JSON internally)
    let config1 = TestConfig::from_path(&toml_path).expect("Failed to load TOML config 1");
    let config2 = TestConfig::from_path(&toml_path2).expect("Failed to load TOML config 2");

    // Verify they deserialized correctly with different values
    assert_eq!(config1.name, "toml-service");
    assert_eq!(config2.name, "toml-service-2");
    assert_eq!(config1.port, 8080);
    assert_eq!(config2.port, 9090);
}

#[test]
fn test_config_service_name() {
    assert_eq!(TestConfig::service_name(), "test-service");
}

#[test]
fn test_partial_config_with_defaults() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let config_path = temp_dir.path().join("partial.toml");

    // Only specify some fields
    let toml_content = r#"
name = "partial-service"
"#;

    fs::write(&config_path, toml_content).expect("Failed to write TOML file");

    let config = TestConfig::from_path(&config_path).expect("Failed to load partial config");

    assert_eq!(config.name, "partial-service");
    assert_eq!(config.port, 0); // default value
    assert!(!config.enabled); // default value
}

#[test]
fn test_config_roundtrip_toml() {
    let original = TestConfig {
        name: "roundtrip-test".to_string(),
        port: 6789,
        enabled: true,
    };

    // Serialize to TOML
    let toml_str = toml::to_string(&original).expect("Failed to serialize to TOML");

    // Deserialize back
    let deserialized: TestConfig =
        toml::from_str(&toml_str).expect("Failed to deserialize from TOML");

    assert_eq!(deserialized.name, original.name);
    assert_eq!(deserialized.port, original.port);
    assert_eq!(deserialized.enabled, original.enabled);
}

#[test]
fn test_config_roundtrip_json() {
    let original = TestConfig {
        name: "roundtrip-test".to_string(),
        port: 6789,
        enabled: true,
    };

    // Serialize to JSON
    let json_str = serde_json::to_string(&original).expect("Failed to serialize to JSON");

    // Deserialize back
    let deserialized: TestConfig =
        serde_json::from_str(&json_str).expect("Failed to deserialize from JSON");

    assert_eq!(deserialized.name, original.name);
    assert_eq!(deserialized.port, original.port);
    assert_eq!(deserialized.enabled, original.enabled);
}

#[test]
fn test_empty_config_file_toml() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let config_path = temp_dir.path().join("empty.toml");

    // Write empty TOML
    fs::write(&config_path, "").expect("Failed to write empty TOML");

    let config = TestConfig::from_path(&config_path).expect("Failed to load empty TOML file");

    // Should get defaults
    assert_eq!(config.name, "");
    assert_eq!(config.port, 0);
    assert!(!config.enabled);
}

#[test]
fn test_environment_partial_override() {
    // Clean up all relevant environment variables to ensure test isolation
    env::remove_var("TEST_SERVICE_NAME");
    env::remove_var("TEST_SERVICE_PORT");
    env::remove_var("TEST_SERVICE_ENABLED");

    // Only override one field
    env::set_var("TEST_SERVICE_PORT", "5555");

    let mut config = TestConfig {
        name: "original".to_string(),
        port: 8080,
        enabled: true,
    };

    config
        .apply_env_overrides()
        .expect("Failed to apply env overrides");

    assert_eq!(config.name, "original"); // unchanged
    assert_eq!(config.port, 5555); // overridden
    assert!(config.enabled); // unchanged

    // Clean up after test
    env::remove_var("TEST_SERVICE_PORT");
}

#[test]
fn test_invalid_port_environment_variable() {
    env::set_var("TEST_SERVICE_PORT", "not_a_number");

    let mut config = TestConfig {
        name: "original".to_string(),
        port: 8080,
        enabled: true,
    };

    // Should not panic, port should remain unchanged
    let _ = config.apply_env_overrides();
    assert_eq!(config.port, 8080);

    env::remove_var("TEST_SERVICE_PORT");
}

#[test]
fn test_enabled_parsing_variants() {
    // Rust's str::parse() for bool only accepts lowercase "true" and "false"
    let test_cases = vec![("true", true), ("false", false)];

    for (input, expected) in test_cases {
        env::set_var("TEST_SERVICE_ENABLED", input);

        let mut config = TestConfig::default();
        config
            .apply_env_overrides()
            .expect("Failed to apply env overrides");

        assert_eq!(config.enabled, expected, "Failed for input: {}", input);

        env::remove_var("TEST_SERVICE_ENABLED");
    }
}
