use mysql_async::{params, Pool, Conn};
use mysql_async::prelude::Queryable;
use mysql_async::error::Error as SqlError;

use async_trait::async_trait;

use serde::Serialize;

use crate::{Response, no_conn, sql_err};
use crate::{UBigInt, BigInt, Integer, VarChar};

use crate::common::{FromDB};

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


pub const STATUS_ONLINE: Integer = 0;
pub const STATUS_OFFLINE: Integer = 1;
pub const STATUS_AWAY: Integer = 2;
pub const STATUS_DO_NOT_DISTURB: Integer = 3;
/*
 *
 * conn = getconn
 * result = conn.query
 * return response based on result
 *
 */
#[async_trait]
impl FromDB<Member, Integer> for Member {
    type Row = Option<(VarChar, VarChar, BigInt, Integer, UBigInt)>;

    async fn get(p: &Pool, id: UBigInt) -> Response<Self> {
        if let Ok(conn) = p.get_conn().await {
            let q = "SELECT secret, name, joindate, status, permissions FROM members WHERE id = :id";
            let db_res : Result<(Conn, Self::Row), SqlError> 
                        = conn.first_exec(q, params!{"id" => id}).await;
            if let Ok((_, row)) = db_res {
                return match row {
                    Some(row) => Response::Row(Self {
                        id: id,
                        secret: row.0,
                        name: row.1,
                        joindate: row.2,
                        status: row.3,
                        permissions: row.4
                    }),
                    None => Response::Empty
                }
            }
            return Response::Other(sql_err!("Fetch failed"));
        }
        return Response::Other(no_conn!("Member::FromDB::get"));
    }

    async fn update(p: &Pool, row: Member) -> Response<Self> {
        let q = r#"UPDATE members
                SET status = :status, permissions = :perms, name = :name
                WHERE id = :id"#;

        if let Ok(conn) = p.get_conn().await {
            let query = conn.drop_exec(q, params!{
                "id"        => row.id,
                "status"    =>  row.status,
                "name"      => row.name,
                "perms"     => row.permissions
            }).await;

            return match query {
                Ok(_)       => Response::Success,
                Err(err)    => Response::Other(sql_err!(err))
            }
        }

        return Response::Other(no_conn!("db::Member::update"));
    }

    async fn delete(p: &Pool, id: UBigInt) -> Response<Self> {
        if let Ok(conn) = p.get_conn().await {
            let q = "DELETE from members WHERE id = :id";
            let db_result: Result<Conn, SqlError>
                = conn.drop_exec(q, params!{"id" => id}).await;
            match Member::get(p, id).await {
                Response::Row(_) => {
                    if let Ok(conn) = db_result {
                        return match conn.prep_exec("", params!{"id" => id}).await {
                            Ok(_) => Response::Success,
                            Err(_) => Response::Other(sql_err!("Member::FromDB::delete"))
                        }
                    }
                    return Response::Success
                },
                Response::Empty => return Response::Empty,
                _ => return Response::Other(sql_err!("Member::FromDB::delete | another stupid get happened delid this"))
            }
        }

        return Response::Empty;
    }

    async fn filter(p: &Pool, status: Integer) -> Response<Self> {
        //! @params status
        return match (p.get_conn().await, status) {
            (Ok(conn), STATUS_ONLINE) | (Ok(conn), STATUS_OFFLINE) |
            (Ok(conn), STATUS_AWAY) | (Ok(conn), STATUS_DO_NOT_DISTURB) => {
                // TODO: Allow people to query somewhere in the middle of this set
                // i.e. we should be able to get user 100 -> 150 instead of just the 
                // first 1000 people
                let q = 
                    "SELECT id, name, status, permissions FROM memebrs 
                    WHERE status = :stat LIMIT 1000"; // high limit for now
                if let Ok(query) = conn.prep_exec(q, params!{"stat" => status}).await {
                    let mapping_r = query.map_and_drop(|row| {
                        let (id, name, status, permissions): (UBigInt, VarChar, Integer, UBigInt) =
                            mysql_async::from_row(row);

                        Member {
                            id: id,
                            secret: "".into(), // no show for obv reasons
                            name: name,
                            joindate: 0, // doesn't matter
                            status: status,
                            permissions: permissions
                        }
                    }).await;
                    match mapping_r {
                        Ok((_, members)) => Response::Set(members),
                        Err(_) => Response::Other(sql_err!("db::Members::filter"))
                    }
                }
                else {
                    Response::Other(sql_err!("db::Members::filter"))
                }
            }
            _ => Response::Other(sql_err!("err"))
        }
    }
}

impl Member {
    pub async fn add(p: &Pool, name: &str, secret: &str, perms: u64) -> Result<Response<Self>, SqlError> {
        //! @param {pool} p
        //! @param {&str} name of new user
        //! @param {&str} encrypted secret : userland auth module should have a function for this
        //! @param {u64} permissions mask

        //! @returns : on_succes => Ok(Response<Member>)
        //! @returns : on_partial_succes => Ok(Response<Member>)
        //! @returns : on_failure => Err(SomeBS)
        use chrono::Utc;

        let conn = p.get_conn().await?;

        //name
        //perms
        //secret
        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?;

        let (_, opt_id): (Conn, Option<UBigInt>) = conn.first_exec(
            "SELECT id FROM members WHERE secret = :secret", 
            params!{
                "secret" => secret.clone()
            }).await?;

        if let Some(id) = opt_id {
            return Ok(Response::Row(Self {
                id: id,
                secret: secret.into(),
                name: name.into(),
                joindate: now,
                status: STATUS_ONLINE,
                permissions: perms
            }))
        }

        return Ok(Response::Empty);
    }

    pub async fn update_perms(p: &Pool, uid: UBigInt, permissions: UBigInt) -> Result<UBigInt, SqlError> {
        //! @return on_sucess Ok(NewPermisionsMask)
        //!
        let conn = p.get_conn().await?;
        conn.drop_exec(
            "UPDATE members SET permissions = :perms WHERE id = :id",
            params!{
                "id" => uid,
                "perms" => permissions
            }).await?;

        Ok(permissions)
    }
}
