//! Integration tests for Nickel (NCL) config file support
//! Tests that actual Nickel config files can be exported and loaded via
//! ConfigLoader

use std::process::Command;

use rustelo_config::ConfigLoader;

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

impl ConfigLoader for TestNickelConfig {
    fn service_name() -> &'static str {
        "test-nickel-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>> {
        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_nickel_cli_available() {
    let output = Command::new("nickel")
        .arg("--version")
        .output()
        .expect("Failed to run nickel --version");

    assert!(
        output.status.success(),
        "nickel CLI not available or failed to run"
    );

    let version_str = String::from_utf8_lossy(&output.stdout);
    assert!(
        version_str.contains("nickel"),
        "nickel version output doesn't contain 'nickel'"
    );
}

#[test]
fn test_simple_nickel_export_to_json() {
    let ncl_code = r#"
{
  name = "test-service",
  port = 8080,
  enabled = true,
}
"#;

    // Write NCL to temp file
    let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
    let temp_path = temp_file.path().to_str().unwrap().to_string();
    std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file");

    // Run nickel export
    let output = Command::new("nickel")
        .arg("export")
        .arg("--format")
        .arg("json")
        .arg(&temp_path)
        .output()
        .expect("Failed to run nickel export");

    assert!(
        output.status.success(),
        "nickel export failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    // Parse JSON output
    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 structure
    assert_eq!(json_value["name"].as_str(), Some("test-service"));
    assert_eq!(json_value["port"].as_u64(), Some(8080));
    assert_eq!(json_value["enabled"].as_bool(), Some(true));
}

#[test]
fn test_nested_nickel_structure_export() {
    let ncl_code = r#"
{
  server = {
    host = "127.0.0.1",
    port = 8080,
    workers = 4,
  },
  database = {
    url = "postgres://localhost",
    pool_size = 10,
  },
  logging = {
    level = "info",
    format = "json",
  },
}
"#;

    let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
    let temp_path = temp_file.path().to_str().unwrap().to_string();
    std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file");

    let output = Command::new("nickel")
        .arg("export")
        .arg("--format")
        .arg("json")
        .arg(&temp_path)
        .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 nested structure
    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["database"]["pool_size"].as_u64(), Some(10));
    assert_eq!(json_value["logging"]["format"].as_str(), Some("json"));
}

#[test]
fn test_nickel_array_support() {
    let ncl_code = r#"
{
  endpoints = {
    resources = "/resources",
    tools = "/tools",
    prompts = "/prompts",
  },
  features = {
    enabled_modules = ["resources", "tools", "prompts"],
    cache_ttl_seconds = 300,
  },
}
"#;

    let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
    let temp_path = temp_file.path().to_str().unwrap().to_string();
    std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file");

    let output = Command::new("nickel")
        .arg("export")
        .arg("--format")
        .arg("json")
        .arg(&temp_path)
        .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 array
    let modules = json_value["features"]["enabled_modules"]
        .as_array()
        .expect("enabled_modules should be an array");
    assert_eq!(modules.len(), 3);
    assert_eq!(modules[0].as_str(), Some("resources"));
    assert_eq!(modules[1].as_str(), Some("tools"));
}

#[test]
fn test_nickel_with_comments() {
    let ncl_code = r#"
# Configuration file with comments
# This tests that comments are properly ignored

{
  # Server section
  server = {
    # Bind address
    host = "127.0.0.1",
    # Port number
    port = 8080,
  },
  # Enable/disable
  enabled = true,
}
"#;

    let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
    let temp_path = temp_file.path().to_str().unwrap().to_string();
    std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file");

    let output = Command::new("nickel")
        .arg("export")
        .arg("--format")
        .arg("json")
        .arg(&temp_path)
        .output()
        .expect("Failed to run nickel export");

    assert!(
        output.status.success(),
        "nickel export failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    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");

    assert_eq!(json_value["server"]["host"].as_str(), Some("127.0.0.1"));
    assert_eq!(json_value["enabled"].as_bool(), Some(true));
}

#[test]
fn test_nickel_with_null_values() {
    let ncl_code = r#"
{
  name = "service",
  description = null,
  optional_port = null,
  enabled = true,
}
"#;

    let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
    let temp_path = temp_file.path().to_str().unwrap().to_string();
    std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file");

    let output = Command::new("nickel")
        .arg("export")
        .arg("--format")
        .arg("json")
        .arg(&temp_path)
        .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");

    assert!(json_value["description"].is_null());
    assert!(json_value["optional_port"].is_null());
    assert_eq!(json_value["name"].as_str(), Some("service"));
}

#[test]
fn test_nickel_with_numeric_types() {
    let ncl_code = r#"
{
  port = 8080,
  workers = 4,
  timeout_seconds = 30,
  cache_ttl_ms = 300000,
  threshold = 0.95,
  retries = 3,
}
"#;

    let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
    let temp_path = temp_file.path().to_str().unwrap().to_string();
    std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file");

    let output = Command::new("nickel")
        .arg("export")
        .arg("--format")
        .arg("json")
        .arg(&temp_path)
        .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");

    assert_eq!(json_value["port"].as_u64(), Some(8080));
    assert_eq!(json_value["workers"].as_u64(), Some(4));
    assert_eq!(json_value["timeout_seconds"].as_u64(), Some(30));
    assert_eq!(json_value["cache_ttl_ms"].as_u64(), Some(300000));
    assert!(
        (json_value["threshold"].as_f64().unwrap() - 0.95).abs() < 0.001,
        "threshold should be 0.95"
    );
}

#[test]
fn test_nickel_with_multiline_strings() {
    let ncl_code = r#"
{
  name = "service",
  description = "This is a multiline description
that spans multiple lines
in the configuration",
  version = "1.0.0",
}
"#;

    let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
    let temp_path = temp_file.path().to_str().unwrap().to_string();
    std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file");

    let output = Command::new("nickel")
        .arg("export")
        .arg("--format")
        .arg("json")
        .arg(&temp_path)
        .output()
        .expect("Failed to run nickel export");

    assert!(
        output.status.success(),
        "nickel export failed: {}",
        String::from_utf8_lossy(&output.stderr)
    );

    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 multiline strings are handled correctly
    assert!(json_str.contains("multiline"));
}

#[test]
fn test_nickel_invalid_syntax_error() {
    let ncl_code = r#"
{
  name = "service",
  port = invalid_value,  # This is invalid syntax
}
"#;

    let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
    let temp_path = temp_file.path().to_str().unwrap().to_string();
    std::fs::write(&temp_path, ncl_code).expect("Failed to write NCL file");

    let output = Command::new("nickel")
        .arg("export")
        .arg("--format")
        .arg("json")
        .arg(&temp_path)
        .output()
        .expect("Failed to run nickel export");

    // Should fail due to invalid syntax
    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        !stderr.is_empty(),
        "Expected error message for invalid syntax"
    );
}

#[test]
fn test_nickel_via_configloader_from_path() {
    let ncl_code = r#"
{
  name = "configloader-test",
  port = 9999,
  enabled = true,
}
"#;

    let temp_file = tempfile::NamedTempFile::new().expect("Failed to create temp file");
    let temp_path = temp_file.path().to_path_buf();

    // Change extension to .ncl for ConfigLoader to recognize it
    let ncl_path = temp_path.with_extension("ncl");
    std::fs::write(&ncl_path, ncl_code).expect("Failed to write NCL file");

    // Use ConfigLoader to load the NCL file
    let config =
        TestNickelConfig::from_path(&ncl_path).expect("Failed to load NCL config via ConfigLoader");

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