use mysql_async::Pool;
use hyper::{Response, Body, StatusCode};
use serde_json::Value;
use serde_json::json;

use crate::http::{self, set_json_body};
use db::messages::Message;

pub async fn get_by_time(pool: &Pool, response: &mut Response<Body>, params: Value) {
    /*
     * Has a ton of required parameters just be warned
     * @channel: channel id we're looking at
     * @start-time: how long ago do we start searching
     * @end-time: how long ago do we stop searching
     * { 
     *  "channel": 1,
     *  "start-time": unix_now - 24 hours
     *  "end-time": unix_now - 23 hours
     * }
     *
     */
    let channel = match params.get("channel") {
        Some(chan_v) => chan_v.as_u64(),
        None => None
    };
    let start_time = match params.get("start-time") {
        Some(val) => val.as_i64(),
        None => None
    };
    let end_time = match params.get("end-time") {
        Some(val) => val.as_i64(),
        None => None
    };
    let limit = match params.get("limit") {
        Some(val) => val.as_u64(),
        None => None
    };

    // TODO: flatten me mommy
    if let (Some(channel), Some(start), Some(end)) = (channel, start_time, end_time) {
        match Message::get_time_range(pool, channel, start, end, limit).await {
            Ok(db_response) => {
                match db_response {
                    // this absolute lack of data streaming is prolly gonna suck like
                    // a whore in hell week for performance but lets pretend servers don't get massive
                    db::Response::Set(messages) => {
                        set_json_body(response, json!({"messages": messages}));
                    },
                    db::Response::RestrictedInput(_/*error message to log*/) => {
                        *response.status_mut() = StatusCode::BAD_REQUEST;
                    }
                    _ => {
                        *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
                    }
                };
            },
            Err(e) => {
                eprintln!("{}", e);
                *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
            }
        };
    } else {
        *response.status_mut() = StatusCode::BAD_REQUEST;
    }
}

pub async fn send_message(pool: &Pool, response: &mut Response<Body>, params: Value) {
    /*
     * @content: expecting string type
     * @channel: channel id that we're going to send a message to
    */
    // NOTE: auth module guarantees this will be there in the correct form
    let author = http::extract_uid(&params);

    match (params.get("content") , params.get("channel")) {
        (Some(content_v), Some(channel_id_v)) => {
            let (content, channel) = (content_v.as_str(), channel_id_v.as_u64());

            if let (Some(message), Some(cid)) = (content, channel) {
                // call returns empty on sucess so we don't need to do anything
                // TODO: loggin
                let db_result = db::messages::Message::send(pool, message, cid, author).await;
                if let Err(issue) =  db_result {
                    *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
                    eprintln!("\t{}", issue);
                }
                else {
                    match db_result.unwrap() {
                        db::Response::RestrictedInput(msg) => {
                            // user issue
                            *response.status_mut() = StatusCode::BAD_REQUEST;
                            set_json_body(response, json!({"msg": msg}))
                        },
                        db::Response::Empty => {}, // nothing to do hyper defaults to 200
                        db::Response::Other(msg) => {
                            eprintln!("{}", msg);
                            *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
                        },
                        _ => *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR
                    };
                }
            }
            else {
                *response.status_mut() = StatusCode::BAD_REQUEST;
            }
        },
        _ => *response.status_mut() = StatusCode::BAD_REQUEST
    }
}

pub async fn from_id(pool: &Pool, response: &mut Response<Body>, params: Value) {
    /*
     * @start-id: u64
     * @limit: optional<u64>
     * @channel: u64
     * {
     *  "channel": 1,
     *  "start": 123,
     *  "limit": 100
     * }
     */
    let channel = match params.get("channel") {
        Some(chan_v) => chan_v.as_u64(),
        None => None
    };
    let start_id = match params.get("start") {
        Some(val) => val.as_u64(),
        None => None
    };
    let limit = match params.get("limit") {
        Some(val) => val.as_u64(),
        None => None
    };

    if let (Some(channel), Some(start_id)) = (channel, start_id) {
        match Message::get_from_id(pool, channel, start_id, limit).await {
            Ok(db_response) => {
                match db_response {
                    db::Response::Set(messages) => {
                        // NOTE this check is here because the db's check doesn't
                        // correctly with async and caching and magic idfk its here
                        // it works its correct and the cost is the same as putting
                        // it in the db layer so whatever
                        if messages.len() == 0 {
                            *response.status_mut() = StatusCode::NOT_FOUND;
                        }
                        else {
                            set_json_body(response, json!({"messages": messages}));
                        }
                    },

                    _ =>  *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR
                };
            },
            Err(err) => {
                *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
                eprintln!("{}", err);
            }
        };
    }
    else {
        *response.status_mut() = StatusCode::BAD_REQUEST;
    }
}

#[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());
    }

}

