use std::collections::HashMap;

use saphyr::{LoadableYamlNode, Yaml};

pub(crate) mod adapters;
mod value;

use crate::prelude::*;
use value::Value;

pub struct Settings {
    global_config_dir: std::path::PathBuf,
    global_config_file: std::path::PathBuf,
    project_config_dir: std::path::PathBuf,
    project_config_file: std::path::PathBuf,
    settings: HashMap<String, Value>,
}

impl Settings {
    pub fn new(project_config_file: &std::path::Path) -> Result<Self> {
        let global_config_dir = directories::ProjectDirs::from("com", "cyloncore", "oo")
            .ok_or_else(|| anyhow!("Failed to retrieve project dir."))?
            .config_dir()
            .to_path_buf();
        let project_config_dir = project_config_file
            .parent()
            .unwrap_or(project_config_file)
            .to_path_buf();
        let mut settings = Self {
            global_config_file: global_config_dir.join("config.yaml"),
            global_config_dir,
            project_config_file: project_config_file.into(),
            project_config_dir,
            settings: Default::default(),
        };
        settings.load_settings()?;
        Ok(settings)
    }
    fn load_settings(&mut self) -> Result<()> {
        self.settings = Default::default();
        self.load_yaml(include_str!("../data/default_settings.yaml"))?;
        if self.global_config_file.exists() {
            let data = std::fs::read_to_string(&self.global_config_file)?;
            self.load_yaml(&data)?;
        } else {
            log::info!("No global config file {:?}", self.global_config_file);
        }
        if self.project_config_file.exists() {
            let data = std::fs::read_to_string(&self.project_config_file)?;
            self.load_yaml(&data)?;
        } else {
            log::info!("No project config file {:?}", self.project_config_file);
        }
        Ok(())
    }
    fn to_string<'a>(node: &'a Yaml) -> Result<&'a str> {
        node.as_str()
            .ok_or_else(|| anyhow!("Expected string, got {:?}", node))
    }
    fn load_node(&mut self, key: String, node: &saphyr::Yaml) -> Result<()> {
        use saphyr::{Scalar, Yaml};
        match node {
            Yaml::Alias(_) => Err(anyhow!("aliases are not supported")),
            Yaml::Mapping(mapping) => {
                for (k, v) in mapping.iter() {
                    let k = Self::to_string(k)?;
                    self.load_node(
                        if key.is_empty() {
                            k.to_string()
                        } else {
                            format!("{}.{}", key, k)
                        },
                        v,
                    )?;
                }
                Ok(())
            }
            Yaml::Representation(_, _, _) => Err(anyhow!("Unsupported representation")),
            Yaml::Tagged(_, _) => Err(anyhow!("Unsupported tagged")),
            Yaml::BadValue => Err(anyhow!("Bad value")),
            Yaml::Sequence(seq) => {
                self.settings.insert(
                    key,
                    Value::StringVec(
                        seq.iter()
                            .map(|x| Ok(Self::to_string(x)?.to_string()))
                            .collect::<Result<_>>()?,
                    ),
                );
                Ok(())
            }
            Yaml::Value(value) => {
                match value {
                    Scalar::Boolean(b) => {
                        self.settings.insert(key, Value::Boolean(*b));
                    }
                    Scalar::Null => {}
                    Scalar::Integer(i) => {
                        self.settings.insert(key, Value::Integer(*i));
                    }
                    Scalar::FloatingPoint(f) => {
                        self.settings.insert(key, Value::Float(f.into_inner()));
                    }
                    Scalar::String(string) => {
                        self.settings.insert(key, Value::String(string.to_string()));
                    }
                }
                Ok(())
            }
        }
    }
    fn load_yaml(&mut self, data: &str) -> Result<()> {
        let nodes = saphyr::Yaml::load_from_str(data)?;
        if let Some(node) = nodes.first() {
            self.load_node("".to_string(), node)?;
        }
        Ok(())
    }
    /// Returns the path to the global config directory (`~/.config/oo/`).
    pub fn global_config_dir(&self) -> &std::path::Path {
        &self.global_config_dir
    }

    /// Returns the path to the project-local config directory (`.oo/`).
    pub fn project_config_dir(&self) -> &std::path::Path {
        &self.project_config_dir
    }

    /// Like `get`, but returns `None` instead of logging an error when the key
    /// is missing.  Use this for truly optional settings (e.g. theme overrides).
    pub(crate) fn get_optional<'a, T>(&'a self, key: impl AsRef<str>) -> Option<&'a T>
    where
        &'a T: TryFrom<&'a Value> + std::fmt::Debug,
        T: value::ConstantDefault,
        <&'a T as TryFrom<&'a Value>>::Error: std::fmt::Debug,
    {
        let key = key.as_ref();
        let v = self.settings.get(key)?;
        match v.try_into() {
            Ok(v) => Some(v),
            Err(e) => {
                log::error!("Failed to cast '{v:?}' for key '{key}' with error: '{e:?}'");
                None
            }
        }
    }

    pub(crate) fn get<'a, T>(&'a self, key: impl AsRef<str>) -> &'a T
    where
        &'a T: TryFrom<&'a Value> + std::fmt::Debug,
        T: value::ConstantDefault,
        <&'a T as TryFrom<&'a Value>>::Error: std::fmt::Debug,
    {
        let key = key.as_ref();
        if let Some(v) = self.settings.get(key) {
            match v.try_into() {
                Ok(v) => v,
                Err(e) => {
                    log::error!("Failed to cast '{v:?}' for key '{key}' with error: '{e:?}'");
                    T::constant_default()
                }
            }
        } else {
            log::error!("Unknown '{key}' key in settings, make sure to define a default.");
            T::constant_default()
        }
    }

    pub(crate) fn merge_extension_defaults(&mut self, defaults: Vec<(String, String)>) {
        for (ext_name, yaml) in defaults {
            log::debug!("Loading defaults for extension '{}'", ext_name);
            if let Err(e) = self.load_yaml(&yaml) {
                log::warn!("Failed to load defaults for extension '{}': {}", ext_name, e);
            }
        }
    }
}

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

    fn create_test_settings() -> (Settings, tempfile::TempDir) {
        let dir = tempfile::tempdir().unwrap();
        let project_config = dir.path().join(".oo").join("config.yaml");
        std::fs::create_dir(dir.path().join(".oo")).unwrap();

        let settings = Settings::new(&project_config).expect("Failed to create test settings");
        (settings, dir)
    }

    #[test]
    fn test_load_yaml_boolean() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
test_bool: true
"#;
        settings.load_yaml(yaml).unwrap();
        assert!(settings.settings.contains_key("test_bool"));
    }

    #[test]
    fn test_load_yaml_integer() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
test_int: 42
"#;
        settings.load_yaml(yaml).unwrap();
        assert!(settings.settings.contains_key("test_int"));
    }

    #[test]
    fn test_load_yaml_float() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
test_float: 3.14
"#;
        settings.load_yaml(yaml).unwrap();
        assert!(settings.settings.contains_key("test_float"));
    }

    #[test]
    fn test_load_yaml_string() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
test_string: "hello"
"#;
        settings.load_yaml(yaml).unwrap();
        assert!(settings.settings.contains_key("test_string"));
    }

    #[test]
    fn test_load_yaml_sequence() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
test_seq:
  - item1
  - item2
  - item3
"#;
        settings.load_yaml(yaml).unwrap();
        assert!(settings.settings.contains_key("test_seq"));
    }

    #[test]
    fn test_load_yaml_nested_mapping() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
parent:
  child1: value1
  child2: value2
"#;
        settings.load_yaml(yaml).unwrap();
        assert!(settings.settings.contains_key("parent.child1"));
        assert!(settings.settings.contains_key("parent.child2"));
    }

    #[test]
    fn test_load_yaml_deeply_nested() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
level1:
  level2:
    level3:
      value: deep
"#;
        settings.load_yaml(yaml).unwrap();
        assert!(settings.settings.contains_key("level1.level2.level3.value"));
    }

    #[test]
    fn test_load_yaml_null_value() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
test_null:
"#;
        settings.load_yaml(yaml).unwrap();
        // Null values should not be inserted
        assert!(!settings.settings.contains_key("test_null"));
    }

    #[test]
    fn test_load_yaml_invalid_yaml() {
        let (mut settings, _dir) = create_test_settings();
        let invalid_yaml = "invalid: [unclosed";
        let result = settings.load_yaml(invalid_yaml);
        assert!(result.is_err());
    }

    #[test]
    fn test_load_yaml_bad_value() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = "badval: !!binary\n  sdlkfjlskdjf";
        let result = settings.load_yaml(yaml);
        assert!(result.is_err());
    }

    #[test]
    fn test_get_existing_bool() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = "test_bool: true";
        settings.load_yaml(yaml).unwrap();
        let val: &bool = settings.get("test_bool");
        assert!(*val);
    }

    #[test]
    fn test_get_existing_integer() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = "test_int: 100";
        settings.load_yaml(yaml).unwrap();
        let val: &i64 = settings.get("test_int");
        assert_eq!(*val, 100);
    }

    #[test]
    fn test_get_existing_float() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = "test_float: 2.71";
        settings.load_yaml(yaml).unwrap();
        let val: &f64 = settings.get("test_float");
        assert_eq!(*val, 2.71);
    }

    #[test]
    fn test_get_existing_string() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = "test_string: \"world\"";
        settings.load_yaml(yaml).unwrap();
        let val: &String = settings.get("test_string");
        assert_eq!(*val, "world");
    }

    #[test]
    fn test_get_existing_vec_string() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
test_vec:
  - a
  - b
  - c
"#;
        settings.load_yaml(yaml).unwrap();
        let val: &Vec<String> = settings.get("test_vec");
        assert_eq!(val.len(), 3);
        assert_eq!(val[0], "a");
    }

    #[test]
    fn test_get_missing_key_returns_default() {
        let (settings, _dir) = create_test_settings();
        let val: &bool = settings.get("nonexistent_bool");
        assert!(!*val);
    }

    #[test]
    fn test_get_optional_existing_key() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = "test_bool: true";
        settings.load_yaml(yaml).unwrap();
        let val: Option<&bool> = settings.get_optional("test_bool");
        assert_eq!(val, Some(&true));
    }

    #[test]
    fn test_get_optional_missing_key() {
        let (settings, _dir) = create_test_settings();
        let val: Option<&bool> = settings.get_optional("nonexistent");
        assert_eq!(val, None);
    }

    #[test]
    fn test_get_type_mismatch_returns_default() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = "test_int: 42";
        settings.load_yaml(yaml).unwrap();
        // Try to get as bool (wrong type)
        let val: &bool = settings.get("test_int");
        assert!(!*val);
    }

    #[test]
    fn test_get_optional_type_mismatch_returns_none() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = "test_int: 42";
        settings.load_yaml(yaml).unwrap();
        // Try to get as bool (wrong type)
        let val: Option<&bool> = settings.get_optional("test_int");
        assert_eq!(val, None);
    }

    #[test]
    fn test_global_config_dir() {
        let (settings, _dir) = create_test_settings();
        let dir = settings.global_config_dir();
        assert!(dir.is_absolute() || !dir.as_os_str().is_empty());
    }

    #[test]
    fn test_project_config_dir() {
        let (settings, _dir) = create_test_settings();
        let config_dir = settings.project_config_dir();
        // Should point to the .oo directory created in the test
        let is_oo_dir = config_dir
            .file_name()
            .and_then(|s| s.to_str())
            .map(|name| name == ".oo" || name == "oo")
            .unwrap_or(false);
        assert!(is_oo_dir);
    }

    #[test]
    fn test_settings_merge_multiple_loads() {
        let (mut settings, _dir) = create_test_settings();
        let yaml1 = "key1: value1";
        let yaml2 = "key2: value2";

        settings.load_yaml(yaml1).unwrap();
        settings.load_yaml(yaml2).unwrap();

        assert!(settings.settings.contains_key("key1"));
        assert!(settings.settings.contains_key("key2"));
    }

    #[test]
    fn test_load_yaml_overwrites_previous() {
        let (mut settings, _dir) = create_test_settings();
        let yaml1 = "same_key: 1";
        let yaml2 = "same_key: 2";

        settings.load_yaml(yaml1).unwrap();
        let val1: &i64 = settings.get("same_key");
        assert_eq!(*val1, 1);

        settings.load_yaml(yaml2).unwrap();
        let val2: &i64 = settings.get("same_key");
        assert_eq!(*val2, 2);
    }

    #[test]
    fn test_to_string_helper_valid() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = "test: hello";
        settings.load_yaml(yaml).unwrap();
        assert!(settings.settings.contains_key("test"));
    }

    #[test]
    fn test_load_yaml_empty_document() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = "";
        let result = settings.load_yaml(yaml);
        assert!(result.is_ok());
    }

    // --- Language-scoped settings tests ---

    #[test]
    fn test_language_lsp_falls_back_to_global() {
        let (settings, _dir) = create_test_settings();
        // No language-specific config; should return global defaults.
        // Global default has lsp.enable: false
        assert!(!*adapters::language::lsp::enable(&settings, "rust"));
    }

    #[test]
    fn test_language_lsp_enable_parses() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
languages:
  cpp:
    lsp:
      enable: true
      server_command: "clangd"
"#;
        settings.load_yaml(yaml).unwrap();

        assert!(*adapters::language::lsp::enable(&settings, "cpp"));
        assert_eq!(adapters::language::lsp::server_command(&settings, "cpp"), "clangd");
    }

    #[test]
    fn test_language_lsp_quoted_true_parses() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
languages:
  rust:
    lsp:
      enable: "true"
      server_command: "rust-analyzer"
"#;
        settings.load_yaml(yaml).unwrap();

        // Quoted booleans are strings and should not be coerced to bools.
        assert!(!*adapters::language::lsp::enable(&settings, "rust"));
        assert_eq!(adapters::language::lsp::server_command(&settings, "rust"), "rust-analyzer");
    }

    #[test]
    fn test_language_lsp_uses_override() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
languages:
  rust:
    lsp:
      enable: true
      server_command: "rust-analyzer"
"#;
        settings.load_yaml(yaml).unwrap();

        assert!(*adapters::language::lsp::enable(&settings, "rust"));
        assert_eq!(adapters::language::lsp::server_command(&settings, "rust"), "rust-analyzer");
    }

    #[test]
    fn test_multiple_languages_independent() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
languages:
  cpp:
    lsp:
      enable: true
      server_command: "clangd"
  rust:
    lsp:
      enable: false
      server_command: "rust-analyzer"
"#;
        settings.load_yaml(yaml).unwrap();

        assert!(*adapters::language::lsp::enable(&settings, "cpp"));
        assert_eq!(adapters::language::lsp::server_command(&settings, "cpp"), "clangd");
        assert!(!*adapters::language::lsp::enable(&settings, "rust"));
        assert_eq!(adapters::language::lsp::server_command(&settings, "rust"), "rust-analyzer");
    }

    #[test]
    fn test_global_lsp_unaffected_by_language_override() {
        let (mut settings, _dir) = create_test_settings();
        let yaml = r#"
languages:
  cpp:
    lsp:
      enable: true
"#;
        settings.load_yaml(yaml).unwrap();

        // Global lsp.enable should still be false (from default_settings.yaml)
        let global_enable: &bool = settings.get("lsp.enable");
        assert!(!*global_enable);
    }
}



