use chrono::Utc;
use hyper::{Response, Body};
use mysql_async::{Conn, Pool, error::Error as SqlError};
use mysql_async::prelude::{params, Queryable};
use serde::Serialize;

use crate::db_types::{UBigInt, BigInt, Integer, VarChar};
use crate::auth;

#[derive(Serialize)]
pub struct Member {
    pub id: UBigInt,
    pub secret: VarChar,
    pub name: VarChar,
    pub joindate: BigInt,
    pub status: Integer,
    pub permissions: UBigInt,
}

const STATUS_ONLINE: Integer = 0;
const _STATUS_OFFLINE: Integer = 1;
const _STATUS_AWAY: Integer = 2;
const _STATUS_DO_NOT_DISTURB: Integer = 3;

pub async fn insert_new_member(p: &Pool, name: VarChar, perms: u64) -> Result<Member, SqlError> {
    use crate::auth::generate_secret;

    let conn:   Conn   = p.get_conn().await?;
    let secret_raw: String = generate_secret();
    let secret = match bcrypt::hash(&secret_raw, auth::BCRYPT_COST) {
        Ok(value) => value,
        Err(e) => panic!("\tCould not insert member due to bcrypt failure:\n\t\t{}",e)
    };
    let now:    BigInt = Utc::now().timestamp();
    
    let conn = conn.drop_exec(
        "INSERT INTO members(secret, name, joindate, status, permissions)
        VALUES(:secret, :name, :joindate, :status, :permissions)", 
        mysql_async::params!{
            "secret" => secret.clone(),
            "name" => name.clone(),
            "joindate" => now,
            "status" => STATUS_ONLINE,
            "permissions" => perms
        }).await?;

    // now pull back the user from our db and return that row
    let db_row_result: (Conn, Option<UBigInt>) = conn.first_exec(
        "SELECT id FROM members WHERE secret = :secret", 
        params!{
            "secret" => secret.clone()
        }).await?;

    Ok(Member {
        id: db_row_result.1.unwrap(), // if we made it this far this shouldn't fail (i hope)
        secret: secret_raw,
        name: name,
        joindate: now,
        status: 0,
        permissions: perms
    })
}

async fn select_online_default(p: &Pool) -> Result<Vec<(UBigInt, VarChar)>, SqlError> {
    let conn = p.get_conn().await?;
    let q = format!("SELECT id, name FROM members WHERE status = {} LIMIT 100", STATUS_ONLINE);
    let data = conn.prep_exec(&q, ()).await?;
    let (_, users) = data.map_and_drop(|row| {
        let (id, nickname): (UBigInt, VarChar)  = mysql_async::from_row(row);
        (id, nickname)
    }).await?;

    return Ok(users);
}

pub async fn get_online_members(p: &Pool, response: &mut Response<Body>) {
    /*
     * Json {
     *  "members": [...],
     *  "start": 0,
     *  "count": 100
     * }
     */
    if let Ok(list) = select_online_default(p).await {
        // unwrap_or`ing for serde_json since it shouldn't fail in theory(on paper)
        *response.body_mut() = Body::from(serde_json::to_string(&list).unwrap_or("[]".into()));
    }
    println!("fuck bro");
}
