use serde_json::{Value, json};

use mysql_async;
use mysql_async::{Conn, Pool};
use mysql_async::prelude::{params, Queryable};

use hyper::{Response, Body, StatusCode};

use chrono::Utc;

use std::collections::HashMap;

use db::{UBigInt, BigInt};
use db::common::FromDB;
use db::member::Member;
use db::invites::Invite;

use crate::http;


/*
 * Error handling: 
 * All errors raisable from this module come from mysql_async and thus
 * are of the enum mysql_async::error::Error
*/

async fn valid_invite(pool: &Pool, id: BigInt) -> bool {
    /*
     * Fetches an invite from the database to check for validity
     */
    let query: Option<db::invites::Invite> = match db::invites::Invite::get(pool, id as u64).await {
        db::Response::Row(invite) => { Some(invite) },
        _ => { None }
    };

    if let Some(invite) = query {
        // if expires at all
        if invite.expires {
            let now = Utc::now().timestamp();
            // old?
            let mut valid_status = now > invite.id;
            // used?
            if invite.uses.is_some() && valid_status == false {
                valid_status = invite.uses.unwrap() <= 0; // safe unwrap since we know its Some(_)
            }
            return valid_status
        }
        // no expiry date? no problem
        return true
    }

    // prolly not a real id
    return false

}

async fn use_invite(pool: &Pool, code: Option<BigInt>) -> Option<Member>{
    /*
     * Attempts to change the state of the current invite being provided
     */
    use crate::auth;
    use crate::perms::GENERAL_NEW;
    let id = match code {
        Some(id) => id,
        None => 0
    };

    // some random comment
    if valid_invite(pool, id).await {
        let raw_secret = auth::generate_secret();
        if let Ok(secret) = auth::encrypt_secret(&raw_secret) {
            return match db::member::Member::add(pool, "Anonymous".into(), &secret, GENERAL_NEW).await {
                Ok(response) => {
                    match response {
                        db::Response::Row(member) => Some(member),
                        _ => None,
                    }
                },
                // TODO: logggin or something idk
                Err(_) =>  return None
            }
        }
        // Returning None because we couldn't actually create a proper secret to store
        else {
            return None;
        }
    }
    // The invite itself was not valid
    else {
        return None;
    }
}

pub async fn join(pool: &Pool, response: &mut Response<Body>, params: HashMap<&str, &str>) {
    /*
     * Main dispatcher for dealing with an attempted join via a given invide code
     * 
     */
    let code = match params.get("code") {
        Some(val) => {
            // i64 because we're using unix timestamps and basically the database is dumb
            if let Ok(num) = (*val).to_string().parse::<i64>() {
                Some(num)
            } else {
                None
            }
            
        },
        None => None
    };

    // Returns a new member if the code is used successfully
    match use_invite(&pool, code).await {
        Some(new_account) => {
            http::set_json_body(response, json!(new_account))
        },
        None => {
            *response.status_mut() = StatusCode::NOT_FOUND;
        }
    }
}


async fn process_expires_parameter(p: &Pool, exp: &Value, id: UBigInt) -> bool {
    // TODO: fix this somewhat unsafe code
    // TODO: BRO THIS SHIT
    // NOTE: its unsafe because of these lazy as heck unwraps everywhere
    use crate::perms::{CREATE_PERM_INVITES, CREATE_TMP_INVITES};
    let conn = p.get_conn().await.unwrap();
    let db_tup: (Conn, Option<UBigInt>) = conn.first_exec(
            "SELECT permissions FROM members WHERE id = :id", 
            params!{"id" => id})
            .await.unwrap();
    // depending on what type of invite we requested we should make sure we have the 
    // right permissions to do so
    let real_perms = db_tup.1.unwrap(); // safe via auth module
    if let Some(exp) = exp.as_bool() {
        // perma?
        if exp {
            return (real_perms & CREATE_PERM_INVITES) == CREATE_PERM_INVITES;
        }
        else {
            return (real_perms & CREATE_TMP_INVITES) == CREATE_TMP_INVITES;
        }
    }
    else {
        return false;
    }
}

async fn allowed_perm_invite(pool: &Pool, uid: u64) -> bool {
    use crate::perms;

    return match db::member::Member::get(pool, uid).await {
        db::Response::Row(user) => perms::has_perm(user.permissions, perms::CREATE_PERM_INVITES),
        _ => false
    };
}
pub async fn create(pool: &Pool, response: &mut Response<Body>, params: HashMap<&str, &str>) {
    /*
     * Creates a new invite
     * Parameters required asked of the user to provide
     * uses : Option<i64>
     * expires: Option<bool>
     */

    let id = http::extract_uid(&params);

    let use_count = match params.get("uses")  {
        Some(val) => {
            match (*val).to_string().parse::<i64>() {
                Ok(count) => Some(count),
                Err(_) => None
            }
        },
        None => None
    };

    let expirey_request = match params.get("expires") {
        Some(exp_val) => {
            match exp_val.to_string().parse::<bool>() {
                Ok(exp) => {
                    match exp {
                        true => allowed_perm_invite(pool, id).await,
                        false => false
                    }
                },
                _ => false
            }
        }
        None => true
    };

    // TODO: prolly add some kind option to set an expire time
    let invite = Invite::new(use_count, expirey_request);

    match invite.add(pool).await {
        Ok(_) => {
            // return the id of the invite
            // Link format from here is basically hostname.io:4536/join?code=<some-code>
            crate::http::set_json_body(response, serde_json::json!(invite))
        },
        Err(mysqle) => {
            println!("\tINVITES::CREATE::ERROR: {}", mysqle);
            *response.status_mut() = StatusCode::BAD_REQUEST;
        }
    }
}
