use std::borrow::Cow;

use mysql_async::{Pool, params};
use mysql_async::prelude::{Queryable};
use mysql_async::error::Error;
use hyper::{Response, Body, StatusCode};
use serde_json::Value;
use chrono::Utc;

use crate::db_types::{UBigInt};


pub async fn insert_message(pool: &Pool, content: &Value, channel_name: &Value, author_id: UBigInt) 
    -> Result<(), Error>{
        match (content.as_str(), channel_name.as_str()) {
            (Some(content), Some(channel)) => {
                let conn = pool.get_conn().await?;
                let time = Utc::now().timestamp();
                conn.prep_exec(
                    r"INSERT INTO messages
                       (time, content, author_id, channel_name)
                       VALUES(:time, :content, :author, :channel)", 
                    params!{
                        "time"    => time,
                        "content" => content,
                        "author"  => author_id,
                        "channel" => channel
                }).await?;
                Ok(())
            }
            _ => {
                let e = Cow::from("Required parameter missing");
                Err(Error::Other(e))
            }
        }
}

pub async fn send_message(pool: &Pool, response: &mut Response<Body>, params: Value) {
    /*
     * @content: expecting string type
     * @id: expecting the channel id that we're posting data to
    */
    let content_r = params.get("content");
    let channel_name_r = params.get("channel");
    // auth module guarantees this will be there in the correct form
    let author = params.get("id")
        .unwrap().as_u64().unwrap();

    match (content_r, channel_name_r) {
        (Some(content), Some(channel_name)) => {
            match insert_message(pool, content, channel_name, author).await {
                Ok(_) => *response.status_mut() = StatusCode::OK,
                Err(err) =>  {
                    use mysql_async::error::Error::{Server};
                    println!("\tDB Error::send_message: {:?}", err);
                    // doing this to avoid client confusion as some input does cause sql errors
                    if let Server(se) = err {
                        if se.code == 1452 {
                            *response.status_mut() = StatusCode::BAD_REQUEST;
                            *response.body_mut() = Body::from(format!("{} does not exist", channel_name));
                        }
                        else {
                            *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
                        }
                    }
                    else {
                        *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
                    }
                }
            }
        },
        _ => {
            *response.status_mut() = StatusCode::BAD_REQUEST;
            *response.body_mut() = Body::from("content/channel missing from json parameters");
        }
    }
}

#[cfg(test)]
mod messaging_tests {
    use crate::testing::{get_pool, hyper_resp};
    use serde_json::Value;
    use hyper::StatusCode;

    #[tokio::test]
    async fn send_message_test_missing_channel() {
        /*
         * Attempt to send a message i na channel that does not exist
         */
        let p = get_pool();
        let mut resp = hyper_resp();

        let params: Value = serde_json::from_str(r#"
        {
            "channel": "this does not exist",
            "content": "bs message",
            "id": 420
        }
        "#).unwrap();

        super::send_message(&p, &mut resp, params).await;

        assert_ne!(StatusCode::OK, resp.status());
    }

    #[tokio::test]#[ignore]
    async fn send_message_good() {
        use crate::members::insert_new_member;
        use crate::perms::GENERAL_NEW;
        use mysql_async::params;
        use mysql_async::prelude::Queryable;
        use crate::testing::tmp_channel_params;

        let p = get_pool();
        let mut resp = hyper_resp();

        let tmp_chan = tmp_channel_params(&p, "sample").await;

        const TMP_NAME: &'static str = "bs user";
        let temp_member = insert_new_member(&p, TMP_NAME.into(), GENERAL_NEW).await.unwrap();


        let params: Value = serde_json::from_str(&format!(r#"
        {{
            "id": {},
            "channel": "{}",
            "content": "bs message"
        }}
        "#, temp_member.id, tmp_chan.name)).unwrap();

        super::send_message(&p, &mut resp, params).await;

        if resp.status() == StatusCode::BAD_REQUEST {
            panic!("{:?}", resp.body());
        }

        // Destroy the the message and the user that we created
        let conn = match p.get_conn().await {
            Ok(c) => c,
            Err(e) => panic!("Could not get connection to db during send_message_good:\nIssue:\t{}", e)
        };

        let conn = conn.drop_exec("DELETE FROM messages WHERE author_id = :id", params!{"id" => temp_member.id}).await.unwrap();
        let conn = conn.drop_exec("DELETE FROM members WHERE id = :id", params!{"id" => temp_member.id}).await.unwrap();
        let _ = conn.drop_exec("DELETE FROM channels WHERE name = :name", params!{"name" => tmp_chan.name}).await;
    }
}

