Skip to content
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct Config {
    #[serde(default)]
    pub default_org: Option<String>,
    #[serde(default)]
    pub default_project: Option<String>,
    #[serde(default)]
    pub favorite_repos: Vec<String>,
}

impl Config {
    pub fn dir() -> Result<PathBuf> {
        dirs::config_dir()
            .map(|d| d.join("pike"))
            .context("Could not determine config directory")
    }

    pub fn load() -> Result<Self> {
        let path = Self::dir()?.join("config.json");
        if !path.exists() {
            return Ok(Self::default());
        }
        let content = std::fs::read_to_string(&path).context("Failed to read config file")?;
        serde_json::from_str(&content).context("Failed to parse config file")
    }

    pub fn save(&self) -> Result<()> {
        let dir = Self::dir()?;
        std::fs::create_dir_all(&dir).context("Failed to create config directory")?;
        let path = dir.join("config.json");
        let content = serde_json::to_string_pretty(self).context("Failed to serialize config")?;
        std::fs::write(&path, content).context("Failed to write config file")
    }
}

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

    fn with_temp_config_dir<F>(f: F)
    where
        F: FnOnce(PathBuf),
    {
        let tmp = TempDir::new().unwrap();
        let dir = tmp.path().join("pike");
        f(dir);
    }

    #[test]
    fn config_default_values() {
        let config = Config::default();
        assert!(config.default_org.is_none());
        assert!(config.default_project.is_none());
        assert!(config.favorite_repos.is_empty());
    }

    #[test]
    fn config_roundtrip() {
        let config = Config {
            default_org: Some("my-org".to_string()),
            default_project: Some("Roadmap".to_string()),
            favorite_repos: vec!["my-org/repo1".to_string(), "my-org/repo2".to_string()],
        };

        let json = serde_json::to_string_pretty(&config).unwrap();
        let deserialized: Config = serde_json::from_str(&json).unwrap();

        assert_eq!(deserialized.default_org, Some("my-org".to_string()));
        assert_eq!(deserialized.default_project, Some("Roadmap".to_string()));
        assert_eq!(
            deserialized.favorite_repos,
            vec!["my-org/repo1", "my-org/repo2"]
        );
    }

    #[test]
    fn config_save_and_load() {
        with_temp_config_dir(|dir| {
            let config = Config {
                default_org: Some("test-org".to_string()),
                default_project: None,
                favorite_repos: vec![],
            };

            std::fs::create_dir_all(&dir).unwrap();
            let path = dir.join("config.json");
            let content = serde_json::to_string_pretty(&config).unwrap();
            std::fs::write(&path, &content).unwrap();

            let loaded: Config =
                serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
            assert_eq!(loaded.default_org, Some("test-org".to_string()));
            assert!(loaded.default_project.is_none());
        });
    }

    #[test]
    fn config_deserialize_missing_fields() {
        let json = r#"{}"#;
        let config: Config = serde_json::from_str(json).unwrap();
        assert!(config.default_org.is_none());
        assert!(config.default_project.is_none());
        assert!(config.favorite_repos.is_empty());
    }
}