//! # Admin keys controller
//!
//! @author Pierre Hubert


use bcrypt::verify;

use crate::api_data::admin::admin_auth_success::AdminAuthSuccess;
use crate::api_data::admin::admin_keys_api::AdminKeyAPI;
use crate::constants::admin::AdminRole;
use crate::data::admin::AdminKey;
use crate::data::admin_action_log::AdminAction;
use crate::data::base_request_handler::BaseRequestHandler;
use crate::data::error::Res;
use crate::data::http_request_handler::HttpRequestHandler;
use crate::data::webauthn_config::get_wan;
use crate::helpers::{admin_access_token_helper, admin_account_helper, admin_account_key_helper, admin_key_authentication_challenges_helper, admin_key_registration_challenges_helper};
use crate::helpers::admin_log_helper::log_admin_action;
use crate::routes::RequestResult;

impl HttpRequestHandler {
    pub fn post_admin_auth_key(&mut self, name_mail: &str, name_key_id: &str) -> Res<AdminKey> {
        let mail = self.post_string(name_mail)?;
        let key_id = self.post_u64(name_key_id)?;

        let admin = admin_account_helper::find_admin_by_email(&mail)?;
        let keys = admin_account_key_helper::get_admin_keys(admin.id)?;

        let key = keys.into_iter()
            .filter(|k| k.id == key_id)
            .next();

        match key {
            Some(key) => Ok(key),
            None => Err(self.bad_request("The key is not associated with this account!".to_string()).unwrap_err())
        }
    }
}

/// Get an admin keys
pub fn get_keys_list(r: &mut HttpRequestHandler) -> RequestResult {
    let admin_id = r.post_admin_id("id")?;

    if admin_id != r.admin_id()? {
        r.check_admin_has_role(AdminRole::MANAGE_ADMINS)?;
    }

    let keys = admin_account_key_helper::get_admin_keys(admin_id)?;

    r.set_response(keys.iter().map(AdminKeyAPI::new).collect::<Vec<AdminKeyAPI>>())
}


/// Generate a challenge to register a new key
pub fn challenge_register_key(r: &mut HttpRequestHandler) -> RequestResult {
    let wan = get_wan();

    let (res, state) = wan.generate_challenge_register(&r.admin_id()?.id_str(), false)?;

    admin_key_registration_challenges_helper::set(r.admin_id()?, state)?;

    r.set_response(res)
}

/// Register key
pub fn register_key(r: &mut HttpRequestHandler) -> RequestResult {
    let key_name = r.post_string("name")?;
    let key_password = r.post_string("password")?;

    let creds = r.post_register_public_key_credential("key")?;
    let state = r.some_or_internal_error(
        admin_key_registration_challenges_helper::get(r.admin_id()?)?,
        "No challenge found!",
    )?;

    let wan = get_wan();
    let key = wan.register_credential(&creds, &state, |_| Ok(false))?;

    let key_id = admin_account_key_helper::add_key(r.admin_id()?, &key_name, key.0, key_password)?;

    log_admin_action(r.admin_id()?, &r.remote_ip(),
                     AdminAction::RegisteredAdminKey {
                         key_id,
                         key_name,
                         target: r.admin_id()?,
                     })?;

    r.ok()
}

/// Delete an admin auth key
pub fn delete_auth_key(r: &mut HttpRequestHandler) -> RequestResult {
    let admin_id = r.post_admin_id("adminID")?;
    let key_id = r.post_u64("keyID")?;

    if admin_id != r.admin_id()? {
        unimplemented!(); // TODO
    }

    for key in admin_account_key_helper::get_admin_keys(admin_id)? {
        if key.id == key_id {
            log_admin_action(r.admin_id()?, &r.remote_ip(),
                             AdminAction::DeletedAdminKey {
                                 key_id,
                                 key_name: key.name.to_string(),
                                 target: admin_id,
                             })?;

            admin_account_key_helper::delete_key(key)?;

            return r.ok();
        }
    }

    r.not_found("Requested key was not found!".to_string())
}

/// Generate a challenge to authenticate with a security key
pub fn challenge_auth_with_key(r: &mut HttpRequestHandler) -> RequestResult {
    let key = r.post_admin_auth_key("mail", "key_id")?;

    let (challenge_response, auth_state) =
        get_wan().generate_challenge_authenticate(vec![key.key])?;

    admin_key_authentication_challenges_helper::set(key.id, auth_state)?;

    r.set_response(challenge_response)
}

/// Authenticate a user with a security key
pub fn auth_with_key(r: &mut HttpRequestHandler) -> RequestResult {
    let key = r.post_admin_auth_key("mail", "key_id")?;
    let credentials = r.post_auth_public_key_credential("credential")?;

    let state = r.some_or_internal_error(
        admin_key_authentication_challenges_helper::get(key.id)?,
        "Associated authentication state not found!",
    )?;

    // Perform authentication
    let state = get_wan().authenticate_credential(&credentials, &state)?;
    if !state.1.user_present {
        r.forbidden("Invalid key!".to_string())?;
    }

    // Check key password (if any)
    if let Some(pass_hash) = key.password {
        let password = r.post_string("password")?;

        if !verify(password, &pass_hash)? {
            r.forbidden("Bad key password!".to_string())?;
        }
    }

    // Generate access token
    let token = admin_access_token_helper::create(key.admin_id)?;

    log_admin_action(key.admin_id, &r.remote_ip(),
                     AdminAction::AuthWithAccessKey { key: key.name, key_id: key.id })?;

    r.set_response(AdminAuthSuccess::new(token))
}