use serde::Deserialize;
use std::sync::Arc;

#[derive(Deserialize)]
pub struct Config {
    host: Option<Arc<str>>,
    port: Option<u16>,
    max_connections: Option<u32>,
    min_connections: Option<u32>,
    acquire_timeout: Option<u64>,
    idle_timeout: Option<u64>,
    max_lifetime: Option<u64>,
    username: Option<Arc<str>>,
    password: Option<Arc<str>>,
    database: Option<Arc<str>>,
    root_username: Option<Arc<str>>,
    root_password: Option<Arc<str>>,
    root_database: Option<Arc<str>>,
    #[serde(skip)]
    address: Option<Arc<str>>,
    #[serde(skip)]
    root_address: Option<Arc<str>>,
}

impl Config {
    pub fn new() -> envy::Result<Self> {
        ConfigBuilder::default().build()
    }

    pub fn builder<'a>() -> ConfigBuilder<'a> {
        ConfigBuilder::default()
    }

    pub fn max_connections(&self) -> u32 {
        self.max_connections.unwrap_or(32)
    }

    pub fn min_connections(&self) -> u32 {
        self.min_connections.unwrap_or(0)
    }

    pub fn acquire_timeout(&self) -> u64 {
        self.acquire_timeout.unwrap_or(30)
    }

    pub fn idle_timeout(&self) -> u64 {
        self.idle_timeout.unwrap_or(10 * 60)
    }

    pub fn max_lifetime(&self) -> u64 {
        self.max_lifetime.unwrap_or(30 * 60)
    }

    pub fn database(&self) -> Option<&str> {
        self.database.as_deref()
    }

    pub fn username(&self) -> Option<&str> {
        self.username.as_deref()
    }

    pub fn password(&self) -> Option<&str> {
        self.password.as_deref()
    }

    pub fn root_database(&self) -> Option<&str> {
        self.root_database.as_deref()
    }

    pub fn address(&self) -> &str {
        self.address.as_deref().unwrap()
    }

    pub fn root_address(&self) -> &str {
        self.root_address.as_deref().unwrap()
    }
}

#[derive(Default)]
pub struct ConfigBuilder<'a> {
    prefix: Option<&'a str>,
}

impl<'a> ConfigBuilder<'a> {
    pub fn with_prefix(mut self, prefix: &'a str) -> Self {
        self.prefix = Some(prefix);
        self
    }

    pub fn build(self) -> envy::Result<Config> {
        let mut cfg: Config = if let Some(prefix) = self.prefix {
            envy::prefixed(prefix)
        } else {
            envy::prefixed("PG_")
        }
        .from_env()?;
        let host = cfg.host.as_deref().unwrap_or("127.0.0.1");
        let port = cfg.port.unwrap_or(27017);
        let mut address = match (cfg.username.as_deref(), cfg.password.as_deref()) {
            (Some(username), Some(password)) => {
                format!("postgresql://{}:{}@{}:{}/", username, password, host, port)
            }
            (Some(username), None) => format!("postgresql://{}@{}:{}/", username, host, port),
            _ => format!("postgresql://{}:{}/", host, port),
        };
        if let Some(database) = cfg.database.as_deref() {
            address.push_str(database);
        }
        cfg.address = Some(Arc::from(address));
        let mut root_address = match (cfg.root_username.as_deref(), cfg.root_password.as_deref()) {
            (Some(root_username), Some(root_password)) => format!(
                "postgresql://{}:{}@{}:{}/",
                root_username, root_password, host, port
            ),
            (Some(root_username), None) => {
                format!("postgresql://{}@{}:{}/", root_username, host, port)
            }
            _ => format!("postgresql://{}:{}/", host, port),
        };
        if let Some(root_database) = cfg.root_database.as_deref() {
            root_address.push_str(root_database);
        }
        cfg.root_address = Some(Arc::from(root_address));
        Ok(cfg)
    }
}
