use bcrypt;
use mysql_async::{Conn, Pool};
use mysql_async::prelude::{params, Queryable};
use crate::db_types::{BigInt, Integer, UBigInt, VarChar};

use crate::routes;

// used when we create a new users for the first time
pub const BCRYPT_COST: u32  = 14;
pub enum AuthReason {
    Good,     //passed regular check
    OpenAuth, // route does not require auth
    NoKey,    // key missing 
    BadKey,   // key is bad
}


fn valid_user(secret: &str, row: &Option<(VarChar, VarChar, BigInt, Integer, UBigInt)>) -> bool {
    match row {
        Some(row) => {
            match bcrypt::verify(secret, &row.0) {
                Ok(result) => result, 
                Err(_) => return false
            }
        },
        _ => return false
    }
}

fn valid_perms(user_opt: &Option<(VarChar, VarChar, BigInt, Integer, UBigInt)>, path: &str) -> bool {
    use crate::perms;
    if let Some(user) = user_opt {
        if let Some(p) = perms::get_perm_mask(path) {
            return (p & user.4) == p;
        }
        return true; // no perms required
    }
    return false;
}

pub async fn wall_entry(path: &str, pool: &Pool, params: &serde_json::Value) -> Result<AuthReason, mysql_async::error::Error> {
    // Start by Checking if the api key is in our keystore
    if routes::is_open(path) {
        Ok(AuthReason::OpenAuth)
    }
    else {
        match (params.get("id"), params.get("secret")) {
            /* 
             * If we apparantly have user data then check for validity in credentials
             */

            (Some(id_v), Some(secret_v)) => {
                /* unwrapping because i couldn't care less about poorly formatted request data */
                let id = id_v.as_u64().unwrap();
                let secret = secret_v.as_str().unwrap();
                let conn = pool.get_conn().await?;
                let db_tup: (Conn, Option<(VarChar, VarChar, BigInt, Integer, UBigInt)>) = conn.first_exec(
                    "SELECT secret, name, joindate, status, permissions FROM members WHERE id = :id", 
                    mysql_async::params!{"id" => id}).await?;
                let user_data = &db_tup.1;
                if valid_user(secret, user_data) && valid_perms(user_data, path) {
                    Ok(AuthReason::Good)
                }
                else {
                    Ok(AuthReason::BadKey)
                }
            },
            _ => {
                Ok(AuthReason::NoKey)
            }
        }
    }
}

pub fn generate_secret() -> String {
    /*
     * Generates a url-safe-plaintext secret for our db
     * */
    use getrandom::getrandom;
    use base64::{encode_config, URL_SAFE};

    let mut buf: Vec<u8> = vec![0;64];
    getrandom(&mut buf).unwrap();
    encode_config(buf,URL_SAFE) 
}



#[cfg(test)]
mod auth_tests {
    use crate::testing::get_pool;
    use serde_json::Value;
    use mysql_async::prelude::Queryable;

    #[tokio::test]
    async fn missing_key() {
        let pool = get_pool();
        let conn = pool.get_conn().await.unwrap();
        let conn = conn.drop_exec(
            r#"INSERT INTO members (id, secret, name, joindate, status,permissions)
            VALUES(1, "abc", "bsname", 1,0,0)
            "#, 
            ()).await.unwrap();

        let params: Value = serde_json::from_str(r#"
        {
            "id": 1
        }
        "#).unwrap();
        
        let result  = super::wall_entry("/channels/list", &pool, &params).await;
        let _ = conn.drop_exec(r#"DELETE FROM members WHERE secret = "abc""#,()).await;
        assert_eq!(true, result.is_ok());
    }

}
