use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::path::{Path, PathBuf};

/// Environment variable name
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct EnvName(Cow<'static, str>);

impl EnvName {
    #[doc(hidden)]
    pub const fn from_static(value: &'static str) -> Self {
        Self(Cow::Borrowed(value))
    }

    #[doc(hidden)]
    pub fn from_string(value: String) -> Self {
        Self(Cow::Owned(value))
    }

    /// Environment variable text
    pub fn as_str(&self) -> &str {
        self.0.as_ref()
    }
}

impl Display for EnvName {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.write_str(self.as_str())
    }
}

/// Environment variable value
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EnvValue(Cow<'static, str>);

impl EnvValue {
    /// Static environment value
    pub const fn from_static(value: &'static str) -> Self {
        Self(Cow::Borrowed(value))
    }

    /// Owned environment value
    pub fn from_string(value: String) -> Self {
        Self(Cow::Owned(value))
    }

    /// Environment value text
    pub fn as_str(&self) -> &str {
        self.0.as_ref()
    }
}

/// Environment variable prefix
#[doc(hidden)]
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct EnvPrefix(Cow<'static, str>);

impl EnvPrefix {
    #[doc(hidden)]
    pub const fn from_static(value: &'static str) -> Self {
        Self(Cow::Borrowed(value))
    }

    #[doc(hidden)]
    pub fn from_string(value: String) -> Self {
        Self(Cow::Owned(value))
    }

    /// Prefix text
    pub fn as_str(&self) -> &str {
        self.0.as_ref()
    }
}

/// TOML document text
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TomlText(String);

impl TomlText {
    /// TOML document text
    pub fn new(value: impl Into<String>) -> Self {
        Self(value.into())
    }

    /// TOML text
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

/// Config file path
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConfigFilePath(PathBuf);

impl ConfigFilePath {
    /// Filesystem path
    pub fn new(value: impl Into<PathBuf>) -> Self {
        Self(value.into())
    }

    /// Filesystem path
    pub fn as_path(&self) -> &Path {
        &self.0
    }

    /// Displayable path text
    pub fn display(&self) -> std::path::Display<'_> {
        self.0.display()
    }
}

/// Config loading inputs
#[derive(Clone, Debug)]
pub struct Sources {
    env: BTreeMap<String, String>,
    file_path: Option<ConfigFilePath>,
    toml: Option<TomlText>,
    process_env: bool,
    file_system: bool,
}

impl Sources {
    /// Empty inputs for deterministic tests
    pub fn empty() -> Self {
        Self {
            env: BTreeMap::new(),
            file_path: None,
            toml: None,
            process_env: false,
            file_system: false,
        }
    }

    /// Add one environment variable
    pub fn with_env(mut self, name: EnvName, value: EnvValue) -> Self {
        self.env.insert(name.as_str().into(), value.as_str().into());
        self
    }

    /// Add an in-memory TOML document
    pub fn with_toml(mut self, text: TomlText) -> Self {
        self.toml = Some(text);
        self
    }

    /// Override the config file path
    pub fn with_file_path(mut self, path: ConfigFilePath) -> Self {
        self.file_path = Some(path);
        self
    }

    #[doc(hidden)]
    pub fn env_value(&self, name: &EnvName) -> Option<String> {
        self.env.get(name.as_str()).cloned()
    }

    #[doc(hidden)]
    pub fn file_path(&self) -> Option<&ConfigFilePath> {
        self.file_path.as_ref()
    }

    #[doc(hidden)]
    pub fn toml(&self) -> Option<&TomlText> {
        self.toml.as_ref()
    }

    #[doc(hidden)]
    pub fn reads_process_env(&self) -> bool {
        self.process_env
    }

    #[doc(hidden)]
    pub fn reads_file_system(&self) -> bool {
        self.file_system
    }
}

impl Default for Sources {
    fn default() -> Self {
        Self {
            env: BTreeMap::new(),
            file_path: None,
            toml: None,
            process_env: true,
            file_system: true,
        }
    }
}
