//! CLI configuration loader helper for all backends
//!
//! Provides a unified pattern for loading backend configuration files
//! with support for both explicit `-c FILE` and environment-based search.
//! Supports both `.ncl` (via `nickel export`) and `.toml` formats.

use crate::error::{Error, Result};
use std::path::Path;

/// Load backend-specific configuration file
///
/// If `cli_config_path` is provided, uses that file exclusively.
/// Otherwise, searches in order (NCL preferred over TOML):
/// 1. `~/.config/typedialog/{backend_name}/{TYPEDIALOG_ENV}.ncl`
/// 2. `~/.config/typedialog/{backend_name}/{TYPEDIALOG_ENV}.toml`
/// 3. `~/.config/typedialog/{backend_name}/config.ncl`
/// 4. `~/.config/typedialog/{backend_name}/config.toml`
/// 5. Returns default value
///
/// # Arguments
/// * `backend_name` - Name of backend (cli, tui, web, ai)
/// * `cli_config_path` - Optional explicit config file path from `-c` flag
/// * `default` - Default config to return if file not found
///
/// # Example
///
/// ```no_run
/// use typedialog_core::config::load_backend_config;
/// use serde::{Deserialize, Serialize};
/// use std::path::PathBuf;
///
/// #[derive(Serialize, Deserialize, Default)]
/// struct CliConfig {
///     timeout: u32,
///     verbose: bool,
/// }
///
/// # fn main() -> typedialog_core::error::Result<()> {
/// // Without explicit path: searches ~/.config/typedialog/cli/
/// let config = load_backend_config::<CliConfig>("cli", None, CliConfig::default())?;
///
/// // With explicit path
/// let path = PathBuf::from("custom.ncl");
/// let config = load_backend_config::<CliConfig>("cli", Some(path.as_path()), CliConfig::default())?;
/// # Ok(())
/// # }
/// ```
pub fn load_backend_config<T>(
    backend_name: &str,
    cli_config_path: Option<&Path>,
    default: T,
) -> Result<T>
where
    T: serde::de::DeserializeOwned + Default,
{
    if let Some(path) = cli_config_path {
        return load_from_file(path);
    }

    let config_dir = dirs::config_dir()
        .unwrap_or_else(|| {
            std::path::PathBuf::from(std::env::var("HOME").unwrap_or_else(|_| ".".to_string()))
        })
        .join("typedialog")
        .join(backend_name);

    let env = std::env::var("TYPEDIALOG_ENV").unwrap_or_else(|_| "default".to_string());

    // NCL env-specific → TOML env-specific → NCL generic → TOML generic
    let candidates = [
        config_dir.join(format!("{env}.ncl")),
        config_dir.join(format!("{env}.toml")),
        config_dir.join("config.ncl"),
        config_dir.join("config.toml"),
    ];

    for path in &candidates {
        if path.exists() {
            if let Ok(config) = load_from_file::<T>(path) {
                return Ok(config);
            }
        }
    }

    Ok(default)
}

/// Dispatch to NCL or TOML loader by extension.
fn load_from_file<T>(path: &Path) -> Result<T>
where
    T: serde::de::DeserializeOwned,
{
    match path.extension().and_then(|e| e.to_str()) {
        Some("ncl") => load_from_ncl(path),
        _ => load_from_toml(path),
    }
}

/// Export NCL via `nickel export --format json` and deserialise.
fn load_from_ncl<T>(path: &Path) -> Result<T>
where
    T: serde::de::DeserializeOwned,
{
    let output = std::process::Command::new("nickel")
        .args(["export", "--format", "json", &path.to_string_lossy()])
        .output()
        .map_err(|e| {
            Error::validation_failed(format!(
                "Failed to run `nickel export` for '{}': {}",
                path.display(),
                e
            ))
        })?;

    if !output.status.success() {
        return Err(Error::validation_failed(format!(
            "nickel export failed for '{}': {}",
            path.display(),
            String::from_utf8_lossy(&output.stderr)
        )));
    }

    serde_json::from_slice(&output.stdout).map_err(|e| {
        Error::validation_failed(format!(
            "Failed to deserialize NCL config '{}': {}",
            path.display(),
            e
        ))
    })
}

/// Parse TOML file and deserialise.
fn load_from_toml<T>(path: &Path) -> Result<T>
where
    T: serde::de::DeserializeOwned,
{
    let content = std::fs::read_to_string(path).map_err(|e| {
        Error::validation_failed(format!(
            "Failed to read config file '{}': {}",
            path.display(),
            e
        ))
    })?;

    toml::from_str(&content).map_err(|e| {
        Error::validation_failed(format!(
            "Failed to parse config file '{}': {}",
            path.display(),
            e
        ))
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Clone)]
    struct TestConfig {
        name: String,
        value: i32,
    }

    #[test]
    fn test_load_with_default() {
        let default = TestConfig {
            name: "default".to_string(),
            value: 42,
        };
        let config = load_backend_config::<TestConfig>("test", None, default.clone()).unwrap();
        assert_eq!(config, default);
    }

    #[test]
    fn test_load_with_explicit_toml_path() {
        let temp_dir = std::env::temp_dir();
        let test_config_path = temp_dir.join("test-backend-config.toml");
        std::fs::write(&test_config_path, "name = \"test\"\nvalue = 100\n").ok();

        let config = load_backend_config::<TestConfig>(
            "test",
            Some(test_config_path.as_path()),
            TestConfig::default(),
        )
        .unwrap();

        assert_eq!(config.name, "test");
        assert_eq!(config.value, 100);
        std::fs::remove_file(test_config_path).ok();
    }
}
