use std::collections::{BTreeMap, HashMap, HashSet};

use anyhow::Result;
use camino::Utf8PathBuf;
use serde::Deserialize;

use crate::dirs::Utf8ProjectDirs;

#[derive(Deserialize)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct Settings {
    #[serde(default)]
    pub git: Git,
    #[serde(default)]
    pub bookmarks: BTreeMap<String, Bookmark>,
    #[serde(default)]
    pub update_deps: bool,
}

#[derive(Default, Deserialize)]
#[cfg_attr(test, derive(Debug, Eq, PartialEq))]
pub struct Git {
    pub ssh_key: Option<Utf8PathBuf>,
}

#[derive(Deserialize)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct Bookmark {
    pub repository: String,
    pub description: Option<String>,
    pub folder: Option<Utf8PathBuf>,
    #[serde(default)]
    pub defaults: HashMap<String, DefaultSetting>,
}

#[derive(Deserialize)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct DefaultSetting {
    pub value: DefaultValue,
    pub skip_prompt: bool,
}

#[derive(Debug, Deserialize)]
#[cfg_attr(test, derive(PartialEq))]
#[serde(rename_all = "snake_case")]
pub enum DefaultValue {
    Bool(bool),
    String(String),
    Number(i64),
    Float(f64),
    List(String),
    MultiList(HashSet<String>),
}

pub fn load(dirs: &Utf8ProjectDirs) -> Result<Settings> {
    let buf = std::fs::read(dirs.config_dir().join("settings.toml"))?;
    basic_toml::from_slice(&buf).map_err(Into::into)
}

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

    #[test]
    fn defaults() {
        let raw = r#"
        update_deps = true

        [git]
        ssh_key = ".ssh/id_ed25519"

        [bookmarks.server]
        repository = "test"
        description = "sample"
        folder = "a/b/c"

        [bookmarks.server.defaults]
        test_bool = { value = { bool = true }, skip_prompt = false }
        test_string = { value = { string = "value" }, skip_prompt = false }
        test_number = { value = { number = 10 }, skip_prompt = false }
        test_float = { value = { float = 2.5 }, skip_prompt = false }
        test_list = { value = { list = "one" }, skip_prompt = true }
        test_multi_list = { value = { multi_list = ["one", "two"] }, skip_prompt = true }
    "#;
        let expect = Settings {
            git: Git {
                ssh_key: Some(Utf8PathBuf::from(".ssh/id_ed25519")),
            },
            bookmarks: [(
                "server".to_owned(),
                Bookmark {
                    repository: "test".to_owned(),
                    description: Some("sample".to_owned()),
                    folder: Some(Utf8PathBuf::from("a/b/c")),
                    defaults: [
                        (
                            "test_bool".to_owned(),
                            DefaultSetting {
                                value: DefaultValue::Bool(true),
                                skip_prompt: false,
                            },
                        ),
                        (
                            "test_string".to_owned(),
                            DefaultSetting {
                                value: DefaultValue::String("value".to_owned()),
                                skip_prompt: false,
                            },
                        ),
                        (
                            "test_number".to_owned(),
                            DefaultSetting {
                                value: DefaultValue::Number(10),
                                skip_prompt: false,
                            },
                        ),
                        (
                            "test_float".to_owned(),
                            DefaultSetting {
                                value: DefaultValue::Float(2.5),
                                skip_prompt: false,
                            },
                        ),
                        (
                            "test_list".to_owned(),
                            DefaultSetting {
                                value: DefaultValue::List("one".to_owned()),
                                skip_prompt: true,
                            },
                        ),
                        (
                            "test_multi_list".to_owned(),
                            DefaultSetting {
                                value: DefaultValue::MultiList(
                                    ["one".to_owned(), "two".to_owned()].into_iter().collect(),
                                ),
                                skip_prompt: true,
                            },
                        ),
                    ]
                    .into_iter()
                    .collect(),
                },
            )]
            .into_iter()
            .collect(),
            update_deps: true,
        };

        let result = basic_toml::from_str::<Settings>(raw);
        assert_eq!(expect, result.unwrap());
    }
}
