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

use async_trait::async_trait;

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

use serde::Serialize;

#[derive(Serialize)]
pub struct Channel {
    pub id: UBigInt,
    pub name: VarChar,
    pub description: Option<VarChar>,
    pub kind: Integer
}

pub const VOICE_CHANNEL: Integer = 1;
pub const TEXT_CHANNEL: Integer = 2;

#[async_trait]
impl FromDB<Channel, Integer> for Channel {
    // id name desc kind
    type Row = Option<(UBigInt, VarChar, Option<VarChar>, Integer)>;

    async fn get(p: &Pool, id: UBigInt) -> Response<Channel> {
        //! Retrieves a Full single Channel row from the DB or fails in a 
        //! fashion described by crate::Response<Channel>
        //! @param p -> SqlPool
        //! @param id -> UBigInt
        //! @return on_success -> Response::Row(Channel)
        //! @return on_fail -> Response::{Empty, Other<String>}
        if let Ok(conn) = p.get_conn().await {
            let q = "SELECT id, name, description, kind FROM channels WHERE id = :id";
            let result: Result<(Conn, Self::Row), SqlError> = 
                conn.first_exec(q, params!{"id" => id}).await;
            if let Ok((_, row)) = result {
                return match row {
                    Some(row) => Response::Row(Channel {
                        id: id,
                        name: row.1,
                        description: row.2,
                        kind: row.3

                    }),
                    None => Response::Empty
                }
            }
            return Response::Other(no_conn!("Invite::FromDB::get fetch failed"));
        }
        return Response::Other(no_conn!("Invite::FromDB::get"));
    }

    async fn update(p: &Pool, row: Self) -> Response<Channel> {
        //! Updates a whole single based on a given Row Of Channel Type
        //! @param p -> SqlPool
        //! @param row -> Channel
        //! @return on_success -> Response::Success
        //! @return on_failure -> Response::Other
        if let Ok(conn) = p.get_conn().await {
            let q = "UPDATE channels
                SET name = :name, description = :desc, kind = :kind 
                WHERE id = :id";
            let result: Result<Conn, SqlError> =
                conn.drop_exec(q, params!{
                    "id" => row.id,
                    "name" => row.name,
                    "desc" => row.description,
                    "kind" => row.kind
                }).await;
            return match result {
                Ok(_) => Response::Success,
                Err(_) => Response::Other(sql_err!("Invite::FromDB::update Update failed"))
            }
        }
        return Response::Other(no_conn!("Invite::FromDB::get connection failed"));
    }

    async fn delete(p: &Pool, id: UBigInt) -> Response<Channel> {
        //! Deletes channel given UBigInt as the row key
        //! @param p -> SqlPool
        //! @param id -> UBigInt
        //! @return on_success -> Response::Success
        //! @return on_failure -> Response::Other
        if let Ok(conn) = p.get_conn().await {
            let q = "DELETE FROM channels WHERE id = :id";
            let result: Result<Conn, SqlError> = 
                conn.drop_exec(q, params!{"id" => id}).await;
            return match result {
                Ok(_) => Response::Success,
                Err(sql) => Response::Other(sql_err!(sql))
            }
        }
        else {
            return Response::Other(no_conn!("Member::FromDB::delete"))
        }
    }

    async fn filter(p: &Pool, kind: Integer) -> Response<Channel> {
        //! @returns -> on success   : Response::Set(Vec<Channel>)
        //! @returns -> on empty set : Response::Set(EmptyVector)
        //! @params  -> on fail      : Response::Other

        // NOTE: used for mapping datasets to vectors
        let map_rows = |row| {
            let (id, name, desc, k): (UBigInt, VarChar, Option<VarChar>, Integer) =
                mysql_async::from_row(row);
            Channel {
                id: id,
                name: name,
                description: desc,
                kind: k
            }
        };
        return match (p.get_conn().await, kind) {
            // Filter for text/voice channels specifically
            (Ok(conn), VOICE_CHANNEL..=TEXT_CHANNEL) =>  { // @NOTE: voice channel and text_channel are literally 1 & 2 respectively
                let q = "SELECT id, name, description, kind FROM channels WHERE kind = :kind";
                let q_result = conn.prep_exec(q, params!{"kind" => kind}).await;
                if let Ok(res) = q_result {
                    let mapping_result = res.map_and_drop(map_rows).await;
                    return match mapping_result {
                        Ok((_, channels)) => Response::Set(channels),
                        Err(_) => Response::Other(sql_err!("db::Channels::filter @with params"))
                    };
                }
                else {
                    Response::Other(sql_err!(""))
                }
            },
            /*
             * Here we are attempting to get all the channels with no filters applied
             * This should fetch everything basically in our channels registry
            */
            (Ok(conn), _) => {
                let q = "SELECT id, name, description, kind FROM channels";
                if let Ok(query) = conn.prep_exec(q, ()).await {
                    let mapping_r = query.map_and_drop(map_rows).await;

                    return match mapping_r {
                        Ok((_, channels)) => Response::Set(channels),
                        Err(_) => Response::Other(sql_err!("db::Channels::filter @no params"))
                    };
                }
                else {
                    Response::Other(sql_err!("db::Channels::filter @no params @no initial query"))
                }
            },
            (Err(_), _) =>  {Response::Other(no_conn!("Channel::FromDB::filter"))}
        }
    }
}

impl Channel {
    pub async fn add(p: &Pool, name: &str, desc: &str, kind: Integer) -> Response<Self> {
        //! @returns on success -> Response::Row<Channel>
        //! @returns on partial success -> Response::Empty
        //! @returns on failure -> Response::Other
        if let Ok(conn) = p.get_conn().await {
            let q = "INSERT INTO channels (name, description, kind) VALUES (:n, :d, :k)";
            let insert_result = conn.drop_exec(q, params!{
                "n" => name,
                "d" => desc,
                "k" => kind
            }).await;
            if let Ok(conn) = insert_result {
                // This is only kosher because names are enforced as unique by sql
                let q = "SELECT id FROM channels WHERE name = :name";
                let fetch_result : Result<(Conn, Option<UBigInt>), SqlError> = 
                    conn.first_exec(q, params!{"name" => name}).await;

                return match fetch_result {
                    Ok((_, id_opt)) => {
                        if let Some(id) = id_opt {
                            Response::Row(Channel {
                                id: id,
                                name: name.into(),
                                description: Some(desc.into()),
                                kind: kind
                            })
                        }
                        else { Response::Empty }
                    },
                    Err(_) => Response::Empty
                };
            }
            else {
                return Response::RestrictedInput(
                    "Could not add channel, make sure the name is unique and not longer than 256 bytes".into());
            }
        }
        return Response::Other(no_conn!("db::channels::add"))
    }
}

