use std::time::{SystemTime, UNIX_EPOCH};

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


use crate::{Response, sql_err};
use crate::{UBigInt, BigInt, VarChar};
use crate::{Message, UserMessage};

const MAX_MESSAGES: u64 = 1000;


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

        let conn = p.get_conn().await?;
        let q = "INSERT INTO messages 
            (time, content, content_type, author_id, channel_id)
            VALUES (:time, :content, :ctype, :author, :channel)";

        let now: i64 = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("System time `NOW` failed")
            .as_millis() as i64;
    
        let res = conn.prep_exec(q, params!{
            "time" => now,
            "content" => content,
            "ctype" => content_type,
            "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<UserMessage>, 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 mem.name, msg.id, msg.time, msg.content, msg.content_type, msg.author_id 
                FROM messages as msg
                JOIN members as mem ON mem.id = msg.author_id
                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 = (VarChar, UBigInt, BigInt, String, String, UBigInt);
                let (name, id, time, content, content_type, author_id): Tuple = mysql_async::from_row(row);
                UserMessage {
                    id,
                    time,
                    content,
                    content_type,
                    author_id,
                    name,
                    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 user failure : RestrictedInput(String)
        //! @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, content_type, 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, VarChar, VarChar,  UBigInt);
            let (id, time, content, content_type, author_id): Tuple = mysql_async::from_row(row);
            Self {
                id,
                time,
                content,
                content_type,
                author_id,
                channel_id
            }
        }).await?;

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

