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

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



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

pub const MAX_NAME_LEN: usize = 256;
pub const MAX_DESCRIPTION_LEN: usize = 2048;

impl crate::Channel {
    pub async fn filter(p: &Pool, kind: Integer) -> Result<Response<Self>, SqlError> {
        //! @returns -> on success   : Ok(Response::Set(Vec<Channel>))
        //! @throw   -> on sql fail  : Err(SqlError)

        if kind != VOICE_CHANNEL || kind !=TEXT_CHANNEL {
            let conn = p.get_conn().await?;
            let result = match kind {
                0 => conn.prep_exec("SELECT id, name, description, kind FROM channels", ()).await?,
                _ => {
                    conn.prep_exec(
                        "SELECT id, name, description, kind FROM channels WHERE kind = :kind", 
                        params!{"kind" => kind}).await?
                }
            };

            let (_conn, channels) = result.map_and_drop(|row| {
                let (id, name, description, kind): (UBigInt, VarChar, Option<VarChar>, Integer) =
                    mysql_async::from_row(row);
                Self {
                    id,
                    name,
                    description,
                    kind
                }
            }).await?;

            Ok(Response::Set(channels))
        }
        else {
            return Ok(Response::RestrictedInput("Channel kind is invalid".into()));
        }

    }

    pub async fn add(p: &Pool, name: &str, description: &str, kind: Integer) 
    -> Result<Response<Self>, SqlError> {
        //! @returns on success -> Response::Row<Channel>
        //! @returns on partial success -> Response::Empty
        //! @returns on failure -> Response::Other
        //! @returns on user failure -> Response::RestrictedInput(msg)

        // bounds are literally [1, 2]
        if kind == TEXT_CHANNEL || kind == VOICE_CHANNEL {
            let conn = p.get_conn().await?;
            let q = "INSERT INTO channels (name, description, kind) VALUES (:n, :d, :k)";
            let conn = conn.drop_exec(q, params!{
                "n" => name,
                "d" => description,
                "k" => kind
            }).await?;

            let q = "SELECT id FROM channels WHERE name = :name";
            let (_, id): (_, Option<UBigInt>) = 
                conn.first_exec(q, params!{"name" => name}).await?;

            // probably safe but honestly could fail if the net goes down right here
            let id = id.unwrap();
            Ok(Response::Row(Self {
                id,
                name: name.to_string(),
                description: Some(description.to_string()),
                kind
            }))
        }
        else {
            return Ok(Response::RestrictedInput(String::from("Invalid channel type")));
        }
    }    

    pub async fn delete(p: &Pool, id: UBigInt) -> Response<Self> {
        //! Deletes channel given UBigInt as the row key
        //! @param p -> SqlPool
        //! @param id -> UBigInt
        //! @return on success -> Response::Success
        //! @return on server 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"))
        }
    }


}

