use crate::config::{
    argv_to_config,
    get_config
};
use config::Config;

use oauth2::{
    AuthUrl,
    ClientId,
    ClientSecret,
    ResourceOwnerPassword,
    ResourceOwnerUsername,
    Scope,
    TokenUrl
};
use oauth2::basic::{
    BasicClient,
    BasicTokenResponse
};
use oauth2::reqwest::http_client;

extern crate pamsm;
use pamsm::{PamServiceModule, Pam, PamFlag, PamError, PamLibExt};

fn get_or_pam_error(config: &Config, key: &str) -> Result<String, PamError> {
    match config.get_str(key) {
        Ok(v) => Ok(v),
        _ => Err(PamError::SERVICE_ERR),
    }
}

fn do_legacy_auth(username: String, password: String, config: Config) -> Result<BasicTokenResponse, PamError> {
    let client_id = ClientId::new(get_or_pam_error(&config, "pam.client_id")?);
    let client_secret = ClientSecret::new(get_or_pam_error(&config, "pam.client_secret")?);
    let auth_url = match AuthUrl::new(get_or_pam_error(&config, "pam.auth_url")?) {
        Ok(u) => u,
        _ => return Err(PamError::SERVICE_ERR),
    };
    let token_url = match TokenUrl::new(get_or_pam_error(&config, "pam.token_url")?){
        Ok(u) => u,
        _ => return Err(PamError::SERVICE_ERR),
    };
    let scope = get_or_pam_error(&config, "pam.scope")?;

    let client = BasicClient::new(client_id, Some(client_secret), auth_url, Some(token_url));
    match client
        .exchange_password(
            &ResourceOwnerUsername::new(username),
            &ResourceOwnerPassword::new(password)
        )
        .add_scope(Scope::new(scope.to_string()))
        .request(http_client) {
            Ok(t) => Ok(t),
            _ => Err(PamError::AUTHINFO_UNAVAIL),
        }
}

struct PamOidc;

impl PamServiceModule for PamOidc {
    fn authenticate(pamh: Pam, _: PamFlag, argv: Vec<String>) -> PamError {
        let conf_args = argv_to_config(argv);
        let conf = get_config(conf_args);

        if conf.get_str("pam.flow").unwrap() == "password" {
            let username = match pamh.get_user(None) {
                Ok(Some(u)) => match u.to_str() {
                    Ok(u) => u,
                    _ => return PamError::CRED_INSUFFICIENT,
                },
                Ok(None) => return PamError::CRED_INSUFFICIENT,
                Err(e) => return e,
            };
            let password = match pamh.get_authtok(None) {
                Ok(Some(p)) => match p.to_str() {
                    Ok(p) => p,
                    _ => return PamError::CRED_INSUFFICIENT,
                },
                Ok(None) => return PamError::CRED_INSUFFICIENT,
                Err(e) => return e,
            };

            match do_legacy_auth(username.to_string(), password.to_string(), conf) {
                Ok(_) => return PamError::SUCCESS,
                Err(e) => return e,
            };
        }

        return PamError::SERVICE_ERR;
    }
}