use anyhow::Result;
use axum::{
    extract::{FromRef, FromRequestParts},
    http::request::Parts,
};
use oauth2::basic::BasicClient;
use phono_models::client::AppDbClient;
use sqlx::postgres::PgPoolOptions;

use crate::{
    auth, errors::AppError, sessions::PgStore, settings::Settings,
    workspace_pooler::WorkspacePooler,
};

/// Global app configuration
#[derive(Clone, Debug)]
pub struct App {
    pub app_db: sqlx::PgPool,
    pub workspace_pooler: WorkspacePooler,
    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 app_db = PgPoolOptions::new()
            .max_connections(settings.app_db_max_connections)
            .connect(settings.database_url.as_str())
            .await?;

        let session_store = PgStore::new(app_db.clone());
        let reqwest_client = reqwest::ClientBuilder::new().https_only(true).build()?;
        let oauth_client = auth::new_oauth_client(&settings)?;
        let workspace_pooler = WorkspacePooler::builder()
            .app_db_pool(app_db.clone())
            .build()?;

        Ok(Self {
            app_db,
            workspace_pooler,
            oauth_client,
            reqwest_client,
            session_store,
            settings,
        })
    }
}

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

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

/// Extractor to automatically obtain a sqlx connection for the application
/// database.
pub struct AppDbConn(pub AppDbClient);

impl FromRequestParts<App> for AppDbConn {
    type Rejection = AppError;

    async fn from_request_parts(_: &mut Parts, state: &App) -> Result<Self, Self::Rejection> {
        let conn = state.app_db.acquire().await?;
        Ok(Self(AppDbClient::from_pool_conn(conn)))
    }
}
