use std::time::{SystemTime, UNIX_EPOCH};
use std::collections::HashMap;

use serde_json::json;
use mysql_async::{self, Pool};
use hyper::{Response, Body, StatusCode};
use db::{BigInt, Member, Invite};
use crate::{http, meta, qs_param};


/*
 * 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 = if let Ok(inv) = Invite::get(pool, id).await {
        match inv {
            db::Response::Row(data) => Some(data),
            _ => None
        }
    } else {
        None
    };

    if let Some(invite) = query {
        // if expires at all
        if invite.expires {
            let now: i64 = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .expect("Systemtime fetch failed")
                .as_millis() as i64;

            // 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();
        let secret = auth::encrypt_secret(&raw_secret).unwrap();
        return match 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(_) =>  None
        }
    }
    // The invite itself was not valid
    // Returning None because we couldn't actually create a proper secret to store
    return None;
}

pub async fn join(pool: &Pool, response: &mut Response<Body>, params: HashMap<String, String>) {
    /*
     * 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!({
                "user": new_account,
                "server": meta::get_config()
            }))
        },
        None => {
            *response.status_mut() = StatusCode::NOT_FOUND;
        }
    }
}



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

    return match 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<String, String>) {
    /*
     * Creates a new invite
     * Parameters required asked of the user to provide
     * uses : Option<i64>
     * expires: Option<bool>
     */

    let id = qs_param!(params, "id", u64).unwrap();

    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>
            http::set_json_body(response, serde_json::json!(invite))
        },
        Err(mysqle) => {
            println!("\tINVITES::CREATE::ERROR: {}", mysqle);
            *response.status_mut() = StatusCode::BAD_REQUEST;
        }
    }
}
