//! Configuration loader with multi-format support
//!
//! Loads configuration from:
//! 1. Nickel (.ncl) - preferred, type-safe
//! 2. TOML (.toml) - fallback
//! 3. JSON (.json) - fallback
//! 4. Defaults - if no config file found

use std::fs;
use std::path::{Path, PathBuf};

use tracing::{debug, info, warn};

use crate::config::schema::KbConfig;
use crate::error::{KbError, Result};

/// Configuration file search paths (in order of preference)
const CONFIG_FILE_NAMES: &[&str] = &[
    ".kogral/config.ncl",
    ".kogral/config.toml",
    ".kogral/config.json",
    "config/kb.ncl",
    "config/kb.toml",
    "config/kb.json",
];

/// Load configuration from the current directory or specified path
///
/// Search order:
/// 1. Specified path (if provided)
/// 2. `.kogral/config.ncl` (Nickel, preferred)
/// 3. `.kogral/config.toml` (TOML fallback)
/// 4. `.kogral/config.json` (JSON fallback)
/// 5. `config/kb.{ncl,toml,json}` (alternate location)
/// 6. Default configuration (if no file found)
///
/// # Arguments
///
/// * `base_dir` - Base directory to search from (defaults to current directory)
/// * `config_path` - Optional explicit config file path
///
/// # Returns
///
/// Loaded `KbConfig` or default if no config found
///
/// # Errors
///
/// Returns an error if config file exists but cannot be read or parsed
pub fn load_config(base_dir: Option<&Path>, config_path: Option<&Path>) -> Result<KbConfig> {
    // If explicit path provided, load directly
    if let Some(path) = config_path {
        info!("Loading config from explicit path: {}", path.display());
        return load_config_from_file(path);
    }

    let search_base = base_dir.unwrap_or_else(|| Path::new("."));

    // Search for config files in order of preference
    for file_name in CONFIG_FILE_NAMES {
        let path = search_base.join(file_name);
        if path.exists() {
            info!("Found config file: {}", path.display());
            return load_config_from_file(&path);
        }
    }

    // No config file found, use defaults
    warn!(
        "No config file found in {}. Using defaults.",
        search_base.display()
    );
    warn!("Searched paths: {:?}", CONFIG_FILE_NAMES);

    // Infer graph name from directory
    let graph_name = search_base
        .file_name()
        .and_then(|n| n.to_str())
        .unwrap_or("default")
        .to_string();

    Ok(KbConfig::default_for_graph(graph_name))
}

/// Load configuration from a specific file
///
/// Detects format from extension: .ncl, .toml, .json
///
/// # Errors
///
/// Returns an error if file cannot be read, parsed, or has unsupported
/// extension
pub fn load_config_from_file<P: AsRef<Path>>(path: P) -> Result<KbConfig> {
    let path = path.as_ref();

    if !path.exists() {
        return Err(KbError::Config(format!(
            "Config file not found: {}",
            path.display()
        )));
    }

    let extension = path.extension().and_then(|e| e.to_str()).ok_or_else(|| {
        KbError::Config(format!(
            "Cannot determine config format: {}",
            path.display()
        ))
    })?;

    match extension {
        "ncl" => load_from_nickel(path),
        "toml" => load_from_toml(path),
        "json" => load_from_json(path),
        other => Err(KbError::Config(format!(
            "Unsupported config format: .{other}"
        ))),
    }
}

/// Load config from Nickel file
#[cfg(not(test))]
fn load_from_nickel<P: AsRef<Path>>(path: P) -> Result<KbConfig> {
    use super::nickel::load_nickel_config;
    info!("Loading config from Nickel: {}", path.as_ref().display());
    load_nickel_config(path)
}

/// Test stub for Nickel loading
#[cfg(test)]
fn load_from_nickel<P: AsRef<Path>>(path: P) -> Result<KbConfig> {
    // In tests, fall back to TOML/JSON or return error
    Err(KbError::Config(format!(
        "Nickel loading not available in tests: {}",
        path.as_ref().display()
    )))
}

/// Load config from TOML file
fn load_from_toml<P: AsRef<Path>>(path: P) -> Result<KbConfig> {
    let path = path.as_ref();
    info!("Loading config from TOML: {}", path.display());

    let content = fs::read_to_string(path)?;
    let config: KbConfig = toml::from_str(&content)?;

    debug!("Config loaded from TOML: {}", path.display());
    Ok(config)
}

/// Load config from JSON file
fn load_from_json<P: AsRef<Path>>(path: P) -> Result<KbConfig> {
    let path = path.as_ref();
    info!("Loading config from JSON: {}", path.display());

    let content = fs::read_to_string(path)?;
    let config: KbConfig = serde_json::from_str(&content)?;

    // Resolve environment variables in paths
    let config = config.resolve_paths();

    debug!("Config loaded from JSON: {}", path.display());
    Ok(config)
}

/// Find .kb directory by walking up from current directory
///
/// # Returns
///
/// Path to .kb directory if found, None otherwise
#[must_use]
pub fn find_kogral_directory() -> Option<PathBuf> {
    let mut current = std::env::current_dir().ok()?;

    loop {
        let kogral_dir = current.join(".kogral");
        if kogral_dir.is_dir() {
            return Some(kogral_dir);
        }

        // Try parent directory
        if !current.pop() {
            break;
        }
    }

    None
}

/// Initialize a new .kb directory with default config
///
/// # Arguments
///
/// * `path` - Base directory to create .kb in
/// * `format` - Config file format ("ncl", "toml", or "json")
///
/// # Returns
///
/// Path to created config file
///
/// # Errors
///
/// Returns an error if directories cannot be created or config file cannot be
/// written
pub fn init_kogral_directory<P: AsRef<Path>>(path: P, format: &str) -> Result<PathBuf> {
    let base = path.as_ref();
    let kogral_dir = base.join(".kogral");

    // Create directory structure
    fs::create_dir_all(&kogral_dir)?;
    fs::create_dir_all(kogral_dir.join("notes"))?;
    fs::create_dir_all(kogral_dir.join("decisions"))?;
    fs::create_dir_all(kogral_dir.join("guidelines"))?;
    fs::create_dir_all(kogral_dir.join("patterns"))?;
    fs::create_dir_all(kogral_dir.join("journal"))?;

    // Infer graph name from directory
    let graph_name = base
        .file_name()
        .and_then(|n| n.to_str())
        .unwrap_or("default")
        .to_string();

    let config = KbConfig::default_for_graph(graph_name);

    // Write config file in requested format
    let config_file = match format {
        "toml" => {
            let path = kogral_dir.join("config.toml");
            let content = toml::to_string_pretty(&config)?;
            fs::write(&path, content)?;
            path
        }
        "json" => {
            let path = kogral_dir.join("config.json");
            let content = serde_json::to_string_pretty(&config)?;
            fs::write(&path, content)?;
            path
        }
        "ncl" => {
            // For Nickel, we'd generate a .ncl file, but for now just write TOML
            // and instruct user to convert
            let path = kogral_dir.join("config.toml");
            let content = toml::to_string_pretty(&config)?;
            fs::write(&path, content)?;
            warn!("Generated TOML config. Convert to Nickel manually for type safety.");
            path
        }
        other => return Err(KbError::Config(format!("Unsupported init format: {other}"))),
    };

    info!("Initialized .kb directory at: {}", kogral_dir.display());
    info!("Config file: {}", config_file.display());

    Ok(config_file)
}

#[cfg(test)]
mod tests {
    use tempfile::tempdir;

    use super::*;

    #[test]
    fn test_load_config_no_file_uses_defaults() {
        let dir = tempdir().unwrap();
        let config = load_config(Some(dir.path()), None).unwrap();
        assert_eq!(config.graph.version, "1.0.0");
    }

    #[test]
    fn test_load_config_from_toml() {
        let dir = tempdir().unwrap();
        let kogral_dir = dir.path().join(".kogral");
        fs::create_dir(&kogral_dir).unwrap();
        let config_path = kogral_dir.join("config.toml");

        let toml_content = r#"
[graph]
name = "test-graph"
version = "2.0.0"
description = "Test description"

[inheritance]
base = "/tmp/shared"
priority = 200

[storage]
primary = "filesystem"

[embeddings]
enabled = true
provider = "fastembed"
model = "test-model"
dimensions = 512

[query]
similarity_threshold = 0.5
max_results = 20
recency_weight = 3.0
cross_graph = true

[templates]
templates_dir = "templates"

[mcp.server]
name = "kogral-mcp"
version = "1.0.0"
transport = "stdio"

[sync]
auto_index = true
watch_paths = ["notes", "decisions"]
debounce_ms = 500
"#;

        fs::write(&config_path, toml_content).unwrap();

        let config = load_config_from_file(&config_path).unwrap();
        assert_eq!(config.graph.name, "test-graph");
        assert_eq!(config.graph.version, "2.0.0");
        assert_eq!(config.inheritance.priority, 200);
        assert_eq!(config.embeddings.dimensions, 512);
        assert_eq!(config.query.max_results, 20);
    }

    #[test]
    fn test_load_config_from_json() {
        let dir = tempdir().unwrap();
        let config_path = dir.path().join("config.json");

        let json_content = r#"{
  "graph": {
    "name": "json-graph",
    "version": "1.5.0",
    "description": ""
  },
  "inheritance": {
    "base": "/tmp/tools/.kogral-shared",
    "guidelines": [],
    "priority": 100
  },
  "storage": {
    "primary": "filesystem",
    "secondary": {
      "enabled": false,
      "type": "surrealdb",
      "url": "ws://localhost:8000",
      "namespace": "kogral",
      "database": "default"
    }
  },
  "embeddings": {
    "enabled": true,
    "provider": "fastembed",
    "model": "BAAI/bge-small-en-v1.5",
    "dimensions": 384,
    "api_key_env": "OPENAI_API_KEY"
  },
  "templates": {
    "templates_dir": "templates",
    "templates": {
      "note": "note.md.tera",
      "decision": "decision.md.tera",
      "guideline": "guideline.md.tera",
      "pattern": "pattern.md.tera",
      "journal": "journal.md.tera",
      "execution": "execution.md.tera"
    },
    "export": {
      "logseq_page": "export/logseq-page.md.tera",
      "logseq_journal": "export/logseq-journal.md.tera",
      "summary": "export/summary.md.tera",
      "json": "export/graph.json.tera"
    },
    "custom": {}
  },
  "query": {
    "similarity_threshold": 0.4,
    "max_results": 10,
    "recency_weight": 3.0,
    "cross_graph": true
  },
  "mcp": {
    "server": {
      "name": "kogral-mcp",
      "version": "1.0.0",
      "transport": "stdio"
    },
    "tools": {
      "search": true,
      "add_note": true,
      "add_decision": true,
      "link": true,
      "get_guidelines": true,
      "export": true
    },
    "resources": {
      "expose_project": true,
      "expose_shared": true
    }
  },
  "sync": {
    "auto_index": true,
    "watch_paths": ["notes", "decisions", "guidelines", "patterns", "journal"],
    "debounce_ms": 500
  }
}"#;

        fs::write(&config_path, json_content).unwrap();

        let config = load_config_from_file(&config_path).unwrap();
        assert_eq!(config.graph.name, "json-graph");
        assert_eq!(config.graph.version, "1.5.0");
    }

    #[test]
    fn test_load_config_nonexistent_file() {
        let result = load_config_from_file("/nonexistent/config.toml");
        assert!(result.is_err());
    }

    #[test]
    fn test_load_config_unsupported_format() {
        let dir = tempdir().unwrap();
        let config_path = dir.path().join("config.yaml");
        fs::write(&config_path, "test: value").unwrap();

        let result = load_config_from_file(&config_path);
        assert!(result.is_err());
    }

    #[test]
    fn test_init_kogral_directory_toml() {
        let dir = tempdir().unwrap();
        let config_file = init_kogral_directory(dir.path(), "toml").unwrap();

        assert!(config_file.exists());
        assert!(dir.path().join(".kogral").is_dir());
        assert!(dir.path().join(".kogral/notes").is_dir());
        assert!(dir.path().join(".kogral/decisions").is_dir());
        assert!(dir.path().join(".kogral/guidelines").is_dir());
        assert!(dir.path().join(".kogral/patterns").is_dir());
        assert!(dir.path().join(".kogral/journal").is_dir());

        // Verify config can be loaded
        let config = load_config_from_file(&config_file).unwrap();
        assert_eq!(config.graph.version, "1.0.0");
    }

    #[test]
    fn test_init_kogral_directory_json() {
        let dir = tempdir().unwrap();
        let config_file = init_kogral_directory(dir.path(), "json").unwrap();

        assert!(config_file.exists());
        assert!(config_file.extension().unwrap() == "json");
    }

    #[test]
    fn test_init_kogral_directory_unsupported_format() {
        let dir = tempdir().unwrap();
        let result = init_kogral_directory(dir.path(), "yaml");
        assert!(result.is_err());
    }

    #[test]
    fn test_find_kogral_directory_not_found() {
        // This will likely return None unless running in a KB-enabled directory
        let result = find_kogral_directory();
        // Can't assert much here without knowing test environment
        println!("KOGRAL directory search result: {:?}", result);
    }
}
