use std::fs;
use std::path::PathBuf;

use anyhow::{Context, Result};

use super::ToolDef;
use crate::config;

/// Directory holding per-user custom tool TOMLs.
/// `~/.config/aimgr/custom/tools/` (or under `$AIMGR_CONFIG_HOME` in tests).
pub fn dir() -> Result<PathBuf> {
    Ok(config::config_home()?.join("custom").join("tools"))
}

pub fn path_for(name: &str) -> Result<PathBuf> {
    Ok(dir()?.join(format!("{name}.toml")))
}

pub fn load_all() -> Result<Vec<ToolDef>> {
    let d = dir()?;
    if !d.exists() {
        return Ok(Vec::new());
    }
    let mut out = Vec::new();
    for entry in fs::read_dir(&d).with_context(|| format!("scanning {}", d.display()))? {
        let entry = entry?;
        let path = entry.path();
        if path.extension().and_then(|s| s.to_str()) != Some("toml") {
            continue;
        }
        let stem = path
            .file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or("")
            .to_string();
        let text =
            fs::read_to_string(&path).with_context(|| format!("reading {}", path.display()))?;
        let parsed: ToolDef =
            toml::from_str(&text).with_context(|| format!("parsing {}", path.display()))?;
        if parsed.name != stem {
            anyhow::bail!(
                "{}: `name = \"{}\"` does not match filename `{stem}.toml`",
                path.display(),
                parsed.name
            );
        }
        out.push(parsed);
    }
    out.sort_by(|a, b| a.name.cmp(&b.name));
    Ok(out)
}

pub fn load(name: &str) -> Result<Option<ToolDef>> {
    let path = path_for(name)?;
    if !path.exists() {
        return Ok(None);
    }
    let text = fs::read_to_string(&path).with_context(|| format!("reading {}", path.display()))?;
    let parsed: ToolDef =
        toml::from_str(&text).with_context(|| format!("parsing {}", path.display()))?;
    if parsed.name != name {
        anyhow::bail!(
            "{}: `name = \"{}\"` does not match filename `{name}.toml`",
            path.display(),
            parsed.name
        );
    }
    Ok(Some(parsed))
}

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

    fn with_config_home<F: FnOnce()>(home: &std::path::Path, f: F) {
        let _guard = crate::test_util::env_lock();
        let key = config::CONFIG_HOME_ENV;
        let prev = std::env::var_os(key);
        std::env::set_var(key, home);
        f();
        match prev {
            Some(v) => std::env::set_var(key, v),
            None => std::env::remove_var(key),
        }
    }

    #[test]
    fn load_all_empty_when_dir_missing() {
        let tmp = tempdir().unwrap();
        with_config_home(tmp.path(), || {
            assert!(load_all().unwrap().is_empty());
        });
    }

    #[test]
    fn load_reads_one_tool() {
        let tmp = tempdir().unwrap();
        with_config_home(tmp.path(), || {
            let d = dir().unwrap();
            fs::create_dir_all(&d).unwrap();
            fs::write(
                d.join("my-tool.toml"),
                r#"
name = "my-tool"
description = "Demo"
[[install]]
id = "manual"
command = "echo hi"
"#,
            )
            .unwrap();
            let tool = load("my-tool").unwrap().unwrap();
            assert_eq!(tool.name, "my-tool");
            assert_eq!(tool.install.len(), 1);
        });
    }
}
