use anyhow::{anyhow, Context as ErrContext, Result};
use config::Config;
use serde::{Deserialize, Serialize};
use std::{env, path::PathBuf};

use super::config::GlobalConfig;

pub trait Context<'a, Cfg>: Send
where
    Cfg: Serialize + Deserialize<'a> + Default,
{
    fn config_file_name(&self) -> String {
        "Beaker.toml".to_string()
    }

    fn config_file_path(&self) -> Result<PathBuf> {
        let curr = env::current_dir()?;
        let it = curr.ancestors();

        for p in it {
            let p = p.join(self.config_file_name());
            if p.exists() {
                return Ok(p);
            }
        }

        Err(anyhow!(
            "Config file `{}` not found in all the ancestor paths",
            self.config_file_name()
        ))
    }

    fn root(&self) -> Result<PathBuf> {
        self.config_file_path()?
            .parent()
            .map(|p| p.to_path_buf())
            .ok_or_else(|| anyhow!("Already at root dir"))
    }

    fn config(&self) -> Result<Cfg> {
        let conf = Config::builder().add_source(Config::try_from(&Cfg::default())?);
        let conf = match self.config_file_path() {
            Ok(path) => conf.add_source(config::File::from(path)),
            _ => conf,
        };
        conf.build()?
            .try_deserialize::<Cfg>()
            .with_context(|| "Unable to deserialize configuration.")
    }

    fn global_config(&self) -> Result<GlobalConfig> {
        let conf = Config::builder().add_source(Config::try_from(&GlobalConfig::default())?);
        let conf = match self.config_file_path() {
            Ok(path) => conf.add_source(config::File::from(path)),
            _ => conf,
        };
        conf.build()?
            .try_deserialize::<GlobalConfig>()
            .with_context(|| "Unable to deserialize configuration.")
    }
}
