use crate::pseudonym_service_pool::PseudonymServicePool;
use async_trait::async_trait;
use oauth_token_service::{TokenService, TokenServiceConfig, TokenServiceError};
use paas_client::auth::{Auth, SystemAuths};
use paas_client::prelude::PAASConfig;
use paas_client::pseudonym_service::PseudonymService;
use std::collections::HashMap;
use std::error::Error;

/// Create a pool of pseudonym services for concurrent operations
///
/// The pool size determines how many concurrent operations can proceed without blocking.
/// A larger pool allows more concurrency but uses more memory and creates more sessions.
pub async fn create_pseudonym_service_pool(
    oauth_auth: OauthAuth,
    paas_config: PAASConfig,
    pool_size: usize,
) -> PseudonymServicePool {
    // TODO: We now have a single login for both transcryptors. But we really should have separate logins for each.
    let mut services = Vec::with_capacity(pool_size);

    for _ in 0..pool_size {
        let mut auth_map = HashMap::new();
        for transcryptor in paas_config.transcryptors.iter() {
            auth_map.insert(transcryptor.system_id.parse().unwrap(), oauth_auth.clone());
        }

        let system_auths: SystemAuths = SystemAuths::from_auths(auth_map);

        let mut ps = PseudonymService::new(paas_config.clone(), system_auths)
            .await
            .expect("Failed to create pseudonym service");

        ps.init()
            .await
            .expect("Failed to initialize PEP client session");

        services.push(ps);
    }

    PseudonymServicePool::new(services)
}

/// OauthAuth is a wrapper around the OauthTokenConnector
#[derive(Debug, Clone)]
pub struct OauthAuth {
    connector: TokenService,
}

#[async_trait]
impl Auth for OauthAuth {
    fn token_type(&self) -> &str {
        "Bearer"
    }

    /// Get the pseudonym service, renewing the token if necessary
    async fn token(&self) -> Result<String, Box<dyn Error>> {
        let token = self.connector.get_token().await?;
        Ok(token.into_secret())
    }
}
fn get_oauth_config() -> TokenServiceConfig {
    TokenServiceConfig {
        identity_service_base_url: std::env::var("OAUTH_BASE_URL").expect("OAUTH_BASE_URL not set"),
        username: std::env::var("OAUTH_USERNAME").expect("OAUTH_USERNAME not set"),
        token: std::env::var("OAUTH_TOKEN").expect("OAUTH_TOKEN not set"),
        client_id: std::env::var("OAUTH_CLIENT_ID").expect("OAUTH_CLIENT_ID not set"),
    }
}

impl OauthAuth {
    /// Create a new OauthAuth
    pub async fn new(config: Option<TokenServiceConfig>) -> Result<Self, TokenServiceError> {
        let config = config.unwrap_or_else(get_oauth_config);

        Ok(Self {
            connector: TokenService::new(config),
        })
    }
}
