use crate::error::EnvError;
use crate::parser::Number;

pub mod error;
pub mod parser;

pub trait FromEnv: Sized {
    fn from_env(value: &str) -> Result<Self, EnvError>;
}

/// Adds support for loading booleans from environment variables.
impl FromEnv for bool {
    fn from_env(value: &str) -> Result<bool, EnvError> {
        match parser::parse_bool(value) {
            Ok(value) => Ok(value),
            Err(e) => Err(e),
        }
    }
}

/// Adds support for loading numbers from environment variables.
impl<T: Number> FromEnv for T {
    fn from_env(value: &str) -> Result<Self, EnvError> {
        match parser::parse_number(value) {
            Ok(value) => Ok(value),
            Err(e) => Err(e),
        }
    }
}

/// Adds support for loading strings from environment variables.
impl FromEnv for String {
    fn from_env(value: &str) -> Result<String, EnvError> {
        Ok(value.to_string())
    }
}

/// A loader for environment variables.
pub struct EnvLoader;

impl EnvLoader {
    /// Creates a new `EnvLoader` with the given prefix.
    pub fn with_prefix<P: Into<String>>(&self, prefix: P) -> PrefixedEnvLoader {
        PrefixedEnvLoader {
            prefix: prefix.into(),
        }
    }

    /// Gets the value of the environment variable with the given key.
    pub fn get<T: FromEnv>(&self, key: &str) -> Result<T, EnvError> {
        let raw = std::env::var(key).map_err(|_| EnvError::missing(key))?;
        FromEnv::from_env(&raw)
    }

    /// Gets the value of the environment variable with the given key, or returns the default value if the variable is not set.
    pub fn get_or<T: FromEnv>(&self, key: &str, default: T) -> T {
        match self.get(key) {
            Ok(value) => value,
            Err(_) => default,
        }
    }

    /// Gets the value of the environment variable with the given key, or returns `None` if the variable is not set.
    pub fn get_opt<T: FromEnv>(&self, key: &str) -> Option<T> {
        match self.get(key) {
            Ok(value) => Some(value),
            Err(_) => None,
        }
    }
}

/// A loader for environment variables with a prefix.
pub struct PrefixedEnvLoader {
    prefix: String,
}

impl PrefixedEnvLoader {
    /// Returns the key with the prefix prepended.
    fn key(&self, key: &str) -> String {
        format!("{}{}", self.prefix, key)
    }
 
    pub fn get<T: FromEnv>(&self, key: &str) -> Result<T, EnvError> {
        EnvLoader.get(&self.key(key))
    }

    pub fn get_or<T: FromEnv>(&self, key: &str, default: T) -> T {
        match self.get(key) {
            Ok(value) => value,
            Err(_) => default,
        }
    }

    pub fn get_opt<T: FromEnv>(&self, key: &str) -> Option<T> {
        match self.get(key) {
            Ok(value) => Some(value),
            Err(_) => None,
        }
    }
}
