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

use crate::app_state::AppState;

#[derive(Clone, Debug, Deserialize)]
pub struct Settings {
    #[serde(default)]
    pub base_path: String,

    pub database_url: String,

    /// When set to 1, embedded Diesel migrations will be run on startup.
    pub run_database_migrations: Option<u8>,

    #[serde(default = "default_host")]
    pub host: String,

    #[serde(default = "default_port")]
    pub port: u16,

    pub auth: AuthSettings,

    pub email: EmailSettings,
}

fn default_port() -> u16 {
    3000
}

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

#[derive(Clone, Debug, Deserialize)]
pub struct AuthSettings {
    pub client_id: String,
    pub client_secret: String,
    pub redirect_url: String,
    pub auth_url: String,
    pub token_url: String,
    pub userinfo_url: String,
    pub logout_url: Option<String>,

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

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

#[derive(Clone, Debug, Deserialize)]
pub struct SmtpSettings {
    pub server: String,
    pub username: String,
    pub password: String,
}

#[derive(Clone, Debug, Deserialize)]
pub struct PostmarkSettings {
    pub server_token: String,
}

#[derive(Clone, Debug, Deserialize)]
pub struct EmailSettings {
    pub verification_from: lettre::message::Mailbox,
    pub message_from: lettre::message::Mailbox,
    pub smtp: Option<SmtpSettings>,
    pub postmark: Option<PostmarkSettings>,
}

pub struct SlackSettings {
    pub client_id: String,
    pub client_secret: String,
    pub redirect_url: String,
    pub auth_url: String,
    pub token_url: String,
}

impl Settings {
    pub fn load() -> Result<Self> {
        match dotenv() {
            Err(err) => {
                if err.not_found() {
                    tracing::info!("no .env file found");
                } else {
                    return Err(err).context("dotenvy error");
                }
            }
            Ok(pathbuf) => {
                tracing::info!(
                    "using env file {}",
                    pathbuf
                        .to_str()
                        .ok_or(anyhow::anyhow!("pathbuf is not valid unicode"))?
                );
            }
        }
        let s = Config::builder()
            .add_source(Environment::default().separator("__"))
            .build()
            .context("config error")?;
        s.try_deserialize().context("deserialize error")
    }
}

impl<S> FromRef<S> for Settings
where
    S: Into<AppState> + Clone,
{
    fn from_ref(state: &S) -> Self {
        Into::<AppState>::into(state.clone()).settings.clone()
    }
}
