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, VarChar};

use crate::common::FromDB;

#[allow(dead_code)]
#[derive(Serialize)]
pub struct Message {
    pub id: UBigInt,
    pub time: BigInt,
    pub content: VarChar,
    pub author_id: UBigInt,
    pub channel_id: UBigInt
}

const MAX_MESSAGES: u64 = 1000;
#[async_trait]
impl FromDB<Message, (BigInt, UBigInt)> for Message {
    type Row = Option<(UBigInt, BigInt, VarChar, UBigInt, UBigInt)>;

    async fn get(p: &Pool, id: UBigInt) -> Response<Self> {
        //! Typically used as the backend to the .update(...) method to 
        //! pick out a message to later edit
        if let Ok(conn) = p.get_conn().await {
            let q = "SELECT id, time, content, author_id, channel_id 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(Self {
                         id,
                        time: row.1,
                        content: row.2,
                        author_id: row.3,
                        channel_id: row.4
                    }),
                    None => Response::Empty
                }
            }
            return Response::Other(sql_err!("Message::FromDB::get"));
        }
        return Response::Other(no_conn!("Message"));
    }

    async fn update(p: &Pool, row: Self) -> Response<Self> {
        //! Primarily used by users to edit previous comments
        // NOTE: we only allow the changing of content in this since
        // no other column has good reason to be modified
        if let Ok(conn) = p.get_conn().await {
            let q = "UPDATE messages
                SET content = :content
                WHERE id = :id";
            let result: Result<Conn, SqlError> =
                conn.drop_exec(q, params!{"id" => row.id, "content" => row.content}).await;
            return match result {
                Ok(_) => Response::Success,
                Err(_) => Response::Other(sql_err!("Message::FromDB::update"))
            }
        }
        return Response::Other(no_conn!("Message::FromDB::update"))
    }

    async fn delete(p: &Pool, id: UBigInt) -> Response<Self> {
        //! Deletes a single message
        //! Typically used by normal users/bots to remove unwanted comments
        if let Ok(conn) = p.get_conn().await {
            let q = "DELETE FROM messages WHERE id = :id";
            let result: Result<Conn, SqlError> =
                conn.drop_exec(q, params!{"id" => id}).await;
            return match result {
                Ok(_) => Response::Success,
                Err(_) => Response::Other(sql_err!("Message::FromDB::delete"))
            }
        }
        return Response::Other(no_conn!("Message::FromDB::update"))
    }

    async fn filter(p: &Pool, (time, channel_id): (BigInt, UBigInt)) -> Response<Self> {
        //! FIlter happens via unix_timestamp and channel_id respectively
        if let Ok(conn) = p.get_conn().await {
            let q = "SELECT id, time, content, author_id";
            if let Ok(query)= conn.prep_exec(q, params!{"time" => time, "cid" => channel_id}).await {
                let mapping_r = query.map_and_drop(|row| {
                    let (id, time, content, author_id): (UBigInt, BigInt, VarChar, UBigInt) =
                        mysql_async::from_row(row);

                    Message {
                         id,
                        time,
                        content,
                        author_id,
                        channel_id
                    }
                }).await;

                match mapping_r {
                    Ok((_, messages)) => Response::Set(messages),
                    Err(_) => Response::Other(sql_err!("db::Message::filter"))
                }
            }
            else {
                return Response::Empty;
            }

        }
        else {
            return Response::Other(no_conn!("db::Message::filter"));
        }
    }
}

impl Message {
    pub async fn send(p: &Pool, content: &str, cid: UBigInt, uid: UBigInt) -> Result<Response<Self>, SqlError> {
        //! @returns on_sucess -> empty
        //! @returns on_failure Err(SqlErr)

        use chrono::Utc;
        let conn = p.get_conn().await?;
        let q = "INSERT INTO messages 
            (time, content, author_id, channel_id)
            VALUES (:time, :content, :author, :channel)";
        let now = Utc::now().timestamp();
    
        let res = conn.prep_exec(q, params!{
            "time" => now,
            "content" => content,
            "author" => uid,
            "channel" => cid
        }).await;
        if let Err(e) = res {
            return match e {
                SqlError::Server(err) => {
                    if err.code == 1452 {
                        return Ok(Response::RestrictedInput("Channel not found".into()))
                    }
                    else {
                        Ok(Response::Other(sql_err!("db::messages::send")))
                    }
                },
                _ => Ok(Response::Other(sql_err!("db::messages::send")))
            }
            
        }
        // all good response
        else {
            return Ok(Response::Empty);
        }

    }
    
    pub async fn get_time_range(p: &Pool, channel_id: UBigInt, start: BigInt, end: BigInt, limit: Option<u64>) -> Result<Response<Self>, SqlError> {
        //! @returns on success : Set(Vec<Messages>)
        //! @returns on userfail: RestrictedInput(message)
        //! @returns on error   : Err(SqlError)

        if start >= end { 
            Ok(Response::RestrictedInput("Invalid start/end parameters".into()))
        }
        else {
            let conn = p.get_conn().await?;
            let limit = if let Some(limit) = limit {
                match limit {
                    1 ..= MAX_MESSAGES => limit,
                    _ => MAX_MESSAGES
                }
            } else {
                MAX_MESSAGES
            };

            let q = "SELECT id, time, content, author_id FROM messages 
                WHERE channel_id = :channel AND time >= :start AND time < :end
                LIMIT :limit";

            let select_result = conn.prep_exec(
                q, params!{
                    "start" => start,
                    "end" => end,
                    "channel" => channel_id,
                    "limit" => limit
                }).await?;

            let(_conn, messages) = select_result.map_and_drop(|row| {
                type Tuple = (UBigInt, BigInt, String, UBigInt);
                let (id, time, content, author_id): Tuple = mysql_async::from_row(row);
                Self {
                    id,
                    time,
                    content,
                    author_id,
                    channel_id
                }
            }).await?;

            Ok(Response::Set(messages))
        }
    }

    pub async fn get_from_id(p: &Pool, channel_id: UBigInt, start: UBigInt, limit: Option<UBigInt>) -> Result<Response<Self>, SqlError> {
        //! @returns on success : Set(Vec<Messages>)
        //! @returns on failure : Err(SqlError)
        let conn = p.get_conn().await?;
        let limit = if let Some(limit) = limit{
            match limit {
                1 ..= MAX_MESSAGES => limit,
                _ => MAX_MESSAGES
            }
        } else {
            MAX_MESSAGES // messages at a time
        };

        let q = "SELECT id, time, content, author_id FROM messages WHERE
            channel_id = :channel AND id >= :start LIMIT :limit";

        let params = params!{
            "channel" => channel_id,
            "start" => start,
            "limit" => limit
        };

        let select_result = conn.prep_exec(q, params).await?;

        let (_conn, messages) = select_result.map_and_drop(|row| {
            type Tuple = (UBigInt, BigInt, String, UBigInt);
            let (id, time, content, author_id): Tuple = mysql_async::from_row(row);
            Self {
                id,
                time,
                content,
                author_id,
                channel_id
            }
        }).await?;

        Ok(Response::Set(messages))
    }
}

