//! Config operations.

use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

use crate::config::types::{ConfigEntry, ConfigSnapshot};
use crate::{GitDomain, GitError, GitResult};

impl GitDomain {
    /// Open the repository-level config.
    pub fn config(&self) -> GitResult<GitConfig> {
        let cfg = self
            .repo()
            .config()
            .map_err(|e| GitError::ConfigError(e.to_string()))?;
        Ok(GitConfig {
            inner: Rc::new(RefCell::new(cfg)),
        })
    }

    pub fn config_get(&self, key: &str) -> GitResult<Option<String>> {
        let cfg = self.config()?;
        match cfg.get_str(key) {
            Ok(v) => Ok(Some(v)),
            Err(e) => {
                // git2 returns an error for not-found keys.
                if e.code() == git2::ErrorCode::NotFound {
                    Ok(None)
                } else {
                    Err(GitError::ConfigError(e.to_string()))
                }
            }
        }
    }

    pub fn config_set(&self, key: &str, value: &str) -> GitResult<()> {
        let cfg = self.config()?;
        cfg.set(key, value)
            .map_err(|e| GitError::ConfigError(e.to_string()))
    }

    pub fn config_delete(&self, key: &str) -> GitResult<()> {
        let cfg = self.config()?;
        cfg.delete(key)
            .map_err(|e| GitError::ConfigError(e.to_string()))
    }

    /// List all config entries. Optionally filter by key prefix (e.g. "user").
    pub fn config_entries(&self, prefix: Option<&str>) -> GitResult<ConfigSnapshot> {
        let cfg = self.config()?;
        let mut entries = Vec::new();

        let glob = prefix.filter(|p| !p.is_empty());

        let binding = cfg.inner.borrow();
        let _ = binding
            .entries(glob)
            .map_err(|e: git2::Error| GitError::ConfigError(e.to_string()))?
            .for_each(|entry| {
                let name = entry.name().unwrap_or("").to_string();
                let value = entry.value().unwrap_or("").to_string();
                entries.push(ConfigEntry { name, value });
            });

        Ok(ConfigSnapshot { entries })
    }

    pub fn config_has(&self, key: &str) -> GitResult<bool> {
        let cfg = self.config()?;
        Ok(cfg.get_str(key).is_ok())
    }

    pub fn config_get_family(&self, prefix: &str) -> GitResult<HashMap<String, String>> {
        let snapshot = self.config_entries(Some(prefix))?;
        Ok(snapshot
            .entries
            .into_iter()
            .map(|e| (e.name, e.value))
            .collect())
    }
}

/// A wrapper around git2::Config providing a cleaner API.
pub struct GitConfig {
    inner: Rc<RefCell<git2::Config>>,
}

impl GitConfig {
    fn get_str(&self, key: &str) -> Result<String, git2::Error> {
        self.inner.borrow().get_str(key).map(String::from)
    }

    fn set(&self, key: &str, value: &str) -> Result<(), git2::Error> {
        self.inner.borrow_mut().set_str(key, value)
    }

    fn delete(&self, key: &str) -> Result<(), git2::Error> {
        self.inner.borrow_mut().remove(key)
    }
}
