//! Integration tests for loading actual service configurations from Nickel
//! files These tests verify that real service configs can be loaded from NCL
//! files

use platform_config::ConfigLoader;

#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
struct McpServerConfig {
    #[serde(default)]
    pub name: String,
    #[serde(default)]
    pub version: String,
    #[serde(default)]
    pub server: ServerConfig,
    #[serde(default)]
    pub logging: LoggingConfig,
    #[serde(default)]
    pub features: FeaturesConfig,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
struct ServerConfig {
    #[serde(default)]
    pub host: String,
    #[serde(default)]
    pub port: u16,
    #[serde(default)]
    pub workers: u16,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
struct LoggingConfig {
    #[serde(default)]
    pub level: String,
    #[serde(default)]
    pub format: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
struct FeaturesConfig {
    #[serde(default)]
    pub cache_enabled: bool,
    #[serde(default)]
    pub cache_ttl_seconds: u32,
}

impl ConfigLoader for McpServerConfig {
    fn service_name() -> &'static str {
        "mcp-server"
    }

    fn load_from_hierarchy() -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>>
    {
        Ok(Self::default())
    }

    fn apply_env_overrides(
        &mut self,
    ) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
        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 = platform_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 MCP 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]
#[ignore] // Requires Nickel files to exist in /tmp/
fn test_load_mcp_server_from_nickel() {
    let ncl_file = "/tmp/mcp-server.solo.ncl";

    // Verify the file exists
    assert!(
        std::path::Path::new(ncl_file).exists(),
        "NCL file does not exist: {}",
        ncl_file
    );

    let config = McpServerConfig::from_path(ncl_file)
        .expect("Failed to load MCP server config from Nickel file");

    assert_eq!(config.name, "mcp-server");
    assert_eq!(config.version, "0.1.0");
    assert_eq!(config.server.host, "127.0.0.1");
    assert_eq!(config.server.port, 8080);
    assert_eq!(config.server.workers, 4);
    assert_eq!(config.logging.level, "info");
    assert_eq!(config.logging.format, "json");
    assert!(config.features.cache_enabled);
    assert_eq!(config.features.cache_ttl_seconds, 300);
}

#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
struct RagConfig {
    #[serde(default)]
    pub enabled: bool,
    #[serde(default)]
    pub embeddings: EmbeddingConfig,
    #[serde(default)]
    pub vector_db: VectorDbConfig,
    #[serde(default)]
    pub llm: LlmConfig,
    #[serde(default)]
    pub retrieval: RetrievalConfig,
    #[serde(default)]
    pub ingestion: IngestionConfig,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
struct EmbeddingConfig {
    #[serde(default)]
    pub provider: String,
    #[serde(default)]
    pub model: String,
    #[serde(default)]
    pub dimension: u16,
    #[serde(default)]
    pub batch_size: u16,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
struct VectorDbConfig {
    #[serde(default)]
    pub db_type: String,
    #[serde(default)]
    pub url: String,
    #[serde(default)]
    pub namespace: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
struct LlmConfig {
    #[serde(default)]
    pub provider: String,
    #[serde(default)]
    pub model: String,
    #[serde(default)]
    pub temperature: f64,
    #[serde(default)]
    pub max_tokens: u32,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
struct RetrievalConfig {
    #[serde(default)]
    pub top_k: u8,
    #[serde(default)]
    pub similarity_threshold: f64,
    #[serde(default)]
    pub use_reranking: bool,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
struct IngestionConfig {
    #[serde(default)]
    pub auto_ingest: bool,
    #[serde(default)]
    pub chunk_size: u16,
    #[serde(default)]
    pub chunk_overlap: u16,
}

impl ConfigLoader for RagConfig {
    fn service_name() -> &'static str {
        "rag"
    }

    fn load_from_hierarchy() -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>>
    {
        Ok(Self::default())
    }

    fn apply_env_overrides(
        &mut self,
    ) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
        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 = platform_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 RAG 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]
#[ignore] // Requires Nickel files to exist in /tmp/
fn test_load_rag_from_nickel() {
    let ncl_file = "/tmp/rag.solo.ncl";

    assert!(
        std::path::Path::new(ncl_file).exists(),
        "NCL file does not exist: {}",
        ncl_file
    );

    let config =
        RagConfig::from_path(ncl_file).expect("Failed to load RAG config from Nickel file");

    assert!(config.enabled);
    assert_eq!(config.embeddings.provider, "openai");
    assert_eq!(config.embeddings.model, "text-embedding-3-small");
    assert_eq!(config.embeddings.dimension, 1536);
    assert_eq!(config.embeddings.batch_size, 32);

    assert_eq!(config.vector_db.db_type, "surrealdb");
    assert_eq!(config.vector_db.url, "ws://127.0.0.1:8000");
    assert_eq!(config.vector_db.namespace, "rag_solo");

    assert_eq!(config.llm.provider, "openai");
    assert_eq!(config.llm.model, "gpt-4-turbo");
    assert!((config.llm.temperature - 0.7).abs() < 0.01);
    assert_eq!(config.llm.max_tokens, 2048);

    assert_eq!(config.retrieval.top_k, 5);
    assert!((config.retrieval.similarity_threshold - 0.7).abs() < 0.01);
    assert!(config.retrieval.use_reranking);

    assert!(config.ingestion.auto_ingest);
    assert_eq!(config.ingestion.chunk_size, 1024);
    assert_eq!(config.ingestion.chunk_overlap, 100);
}

#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
struct ExtensionRegistryConfig {
    #[serde(default)]
    pub server: ExtRegServerConfig,
    #[serde(default)]
    pub cache: CacheConfig,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
struct ExtRegServerConfig {
    #[serde(default)]
    pub host: String,
    #[serde(default)]
    pub port: u16,
    #[serde(default)]
    pub workers: u16,
    #[serde(default)]
    pub enable_cors: bool,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
struct CacheConfig {
    #[serde(default)]
    pub capacity: usize,
    #[serde(default)]
    pub ttl_seconds: u64,
    #[serde(default)]
    pub enable_metadata_cache: bool,
}

impl ConfigLoader for ExtensionRegistryConfig {
    fn service_name() -> &'static str {
        "extension-registry"
    }

    fn load_from_hierarchy() -> std::result::Result<Self, Box<dyn std::error::Error + Send + Sync>>
    {
        Ok(Self::default())
    }

    fn apply_env_overrides(
        &mut self,
    ) -> std::result::Result<(), Box<dyn std::error::Error + Send + Sync>> {
        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 = platform_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 extension-registry 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]
#[ignore] // Requires Nickel files to exist in /tmp/
fn test_load_extension_registry_from_nickel() {
    let ncl_file = "/tmp/extension-registry.solo.ncl";

    assert!(
        std::path::Path::new(ncl_file).exists(),
        "NCL file does not exist: {}",
        ncl_file
    );

    let config = ExtensionRegistryConfig::from_path(ncl_file)
        .expect("Failed to load extension-registry config from Nickel file");

    assert_eq!(config.server.host, "0.0.0.0");
    assert_eq!(config.server.port, 8082);
    assert_eq!(config.server.workers, 4);
    assert!(config.server.enable_cors);

    assert_eq!(config.cache.capacity, 1000);
    assert_eq!(config.cache.ttl_seconds, 300);
    assert!(config.cache.enable_metadata_cache);
}

#[test]
#[ignore] // Requires Nickel files to exist in /tmp/
fn test_load_control_center_from_nickel() {
    let ncl_file = "/tmp/control-center.solo.ncl";

    assert!(
        std::path::Path::new(ncl_file).exists(),
        "NCL file does not exist: {}",
        ncl_file
    );

    // Just verify it can be exported and parsed
    let output = std::process::Command::new("nickel")
        .arg("export")
        .arg("--format")
        .arg("json")
        .arg(ncl_file)
        .output()
        .expect("Failed to run nickel export");

    assert!(output.status.success());

    let json_str = String::from_utf8(output.stdout).expect("Failed to read JSON output");
    let json_value: serde_json::Value =
        serde_json::from_str(&json_str).expect("Failed to parse JSON");

    // Verify key fields exist and have correct values
    assert_eq!(json_value["server"]["host"].as_str(), Some("127.0.0.1"));
    assert_eq!(json_value["server"]["port"].as_u64(), Some(8080));
    assert_eq!(json_value["auth"]["require_mfa"].as_bool(), Some(false));
    assert_eq!(json_value["anomaly"]["enabled"].as_bool(), Some(true));
}

#[test]
fn test_nickel_vs_toml_equivalence() {
    // Create equivalent TOML file
    let toml_file = "/tmp/test-equiv.toml";
    let toml_content = r#"
name = "test-service"
port = 8080
enabled = true
"#;
    std::fs::write(toml_file, toml_content).expect("Failed to write TOML file");

    // Create equivalent NCL file
    let ncl_file = "/tmp/test-equiv.ncl";
    let ncl_content = r#"
{
  name = "test-service",
  port = 8080,
  enabled = true,
}
"#;
    std::fs::write(ncl_file, ncl_content).expect("Failed to write NCL file");

    // Export NCL to JSON
    let output = std::process::Command::new("nickel")
        .arg("export")
        .arg("--format")
        .arg("json")
        .arg(ncl_file)
        .output()
        .expect("Failed to run nickel export");

    assert!(output.status.success());
    let ncl_json_str = String::from_utf8(output.stdout).expect("Failed to read JSON output");

    // Load TOML via platform_config
    let toml_json_value =
        platform_config::format::load_config(toml_file).expect("Failed to load TOML config");

    // Parse NCL JSON
    let ncl_json_value: serde_json::Value =
        serde_json::from_str(&ncl_json_str).expect("Failed to parse NCL JSON");

    // Verify equivalent values
    assert_eq!(
        ncl_json_value["name"].as_str(),
        toml_json_value["name"].as_str()
    );
    assert_eq!(ncl_json_value["port"], toml_json_value["port"]);
    assert_eq!(ncl_json_value["enabled"], toml_json_value["enabled"]);
}
