//! Runtime application configuration values.

use anyhow::{Context as _, Result};
use axum::extract::FromRef;
use config::{Config, Environment};
use dotenvy::dotenv;
use serde::Deserialize;
use tracing::info;
use url::Url;

use crate::app::App;

/// Runtime application configuration values, typically read from environment
/// variables.
///
/// This may be used with the [`axum::extract::State`] extractor.
#[derive(Clone, Debug, Deserialize)]
pub(crate) struct Settings {
    /// Prefix under which to nest all routes. If specified, include leading
    /// slash but no trailing slash, for example "/app". For default behavior,
    /// leave as empty string.
    #[serde(default)]
    pub(crate) root_path: String,

    /// postgresql:// URL for Phonograph's application database.
    pub(crate) database_url: Url,

    #[serde(default = "default_app_db_max_connections")]
    pub(crate) app_db_max_connections: u32,

    /// When set to 1, embedded SQLx migrations will be run on startup.
    #[serde(default)]
    pub(crate) run_database_migrations: u8,

    /// Address for server to bind to
    #[serde(default = "default_host")]
    pub(crate) host: String,

    /// Port for server to bind to
    #[serde(default = "default_port")]
    pub(crate) port: u16,

    /// Host visible to end users, for example "https://phono.dev"
    pub(crate) frontend_host: String,

    pub(crate) auth: AuthSettings,
}

fn default_app_db_max_connections() -> u32 {
    16
}

fn default_port() -> u16 {
    8080
}

fn default_host() -> String {
    "127.0.0.1".to_owned()
}

/// OAuth2 and session cookie settings.
#[derive(Clone, Debug, Deserialize)]
pub(crate) struct AuthSettings {
    pub(crate) client_id: String,

    pub(crate) client_secret: String,

    pub(crate) auth_url: String,

    pub(crate) token_url: String,

    pub(crate) userinfo_url: String,

    pub(crate) logout_url: Option<String>,

    #[serde(default = "default_cookie_name")]
    pub(crate) cookie_name: String,
}

fn default_cookie_name() -> String {
    "PHONO_SESSION".to_string()
}

impl Settings {
    /// Load environment variables into a [`Settings`] struct.
    pub(crate) fn load() -> Result<Self> {
        let s = Config::builder()
            .add_source(Environment::default().separator("__"))
            .build()
            .context("config error")?;
        s.try_deserialize().context("deserialize error")
    }
}

impl FromRef<App> for Settings {
    fn from_ref(state: &App) -> Self {
        state.settings.clone()
    }
}

/// Attempt to load environment variables from .env file.
pub(crate) fn load_dotenv() -> Result<()> {
    dotenv()
        .map(|pathbuf| {
            info!(
                "using env file {0}",
                pathbuf
                    .to_str()
                    .ok_or(anyhow::anyhow!("pathbuf is not valid unicode"))?
            );
            Ok(())
        })
        .or_else(|err| {
            if err.not_found() {
                info!("no env file loaded");
                Ok(Ok(()))
            } else {
                Err(err)
            }
        })?
}
