use std::sync::Arc;

use anyhow::Result;
use axum::{
    extract::{FromRef, FromRequestParts},
    http::request::Parts,
};
use deadpool_diesel::postgres::{Connection, Pool};
use oauth2::basic::BasicClient;

use crate::{
    app_error::AppError,
    email::{Mailer, SmtpOptions},
    nav::NavbarBuilder,
    sessions::PgStore,
    settings::Settings,
};

/// Global app configuration
pub struct App {
    pub db_pool: Pool,
    pub mailer: Mailer,
    pub navbar_template: NavbarBuilder,
    pub oauth_client: BasicClient,
    pub reqwest_client: reqwest::Client,
    pub session_store: PgStore,
    pub settings: Settings,
}

impl App {
    /// Initialize global application functions based on config values
    pub async fn from_settings(settings: Settings) -> Result<Self> {
        let database_url = settings.database_url.clone();
        let manager =
            deadpool_diesel::postgres::Manager::new(database_url, deadpool_diesel::Runtime::Tokio1);
        let db_pool = deadpool_diesel::postgres::Pool::builder(manager).build()?;

        let session_store = PgStore::new(db_pool.clone());
        let reqwest_client = reqwest::ClientBuilder::new().https_only(true).build()?;
        let oauth_client = crate::auth::new_oauth_client(&settings)?;

        let mailer = if let Some(smtp_settings) = settings.email.smtp.clone() {
            Mailer::new_smtp(SmtpOptions {
                server: smtp_settings.server,
                username: smtp_settings.username,
                password: smtp_settings.password,
            })?
        } else if let Some(postmark_settings) = settings.email.postmark.clone() {
            Mailer::new_postmark(postmark_settings.server_token)?
                .with_reqwest_client(reqwest_client.clone())
        } else {
            return Err(anyhow::anyhow!("no email backend settings configured"));
        };

        Ok(Self {
            db_pool,
            mailer,
            navbar_template: NavbarBuilder::default().with_base_path(&settings.base_path),
            oauth_client,
            reqwest_client,
            session_store,
            settings,
        })
    }
}

/// Global app configuration, arced for relatively inexpensive clones
pub type AppState = Arc<App>;

/// State extractor for shared reqwest client
#[derive(Clone)]
pub struct ReqwestClient(pub reqwest::Client);

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

/// Extractor to automatically obtain a Deadpool database connection
pub struct DbConn(pub Connection);

impl<S> FromRequestParts<S> for DbConn
where
    S: Into<AppState> + Clone + Sync,
{
    type Rejection = AppError;

    async fn from_request_parts(_: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        let conn = Into::<AppState>::into(state.clone()).db_pool.get().await?;
        Ok(Self(conn))
    }
}
