use mysql_async::Pool;
use hyper::{Response, Body, HeaderMap, StatusCode};
use hyper::body::to_bytes;
use serde_json::json;

use std::collections::HashMap;

use crate::http::set_json_body;
use crate::perms;
use crate::qs_param;
use db::Message;

pub async fn get_by_time(pool: &Pool, response: &mut Response<Body>, params: HashMap<String, String>) {
    /*
     * 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_id": 1,
     *  "start_time": unix_now - 24 hours
     *  "end_time": unix_now - 23 hours
     * }
     *
     */
    let channel_id = qs_param!(params, "channel_id", u64);
    let start_time = qs_param!(params, "start_time", i64);
    let end_time   = qs_param!(params, "end_time", i64);
    let limit      = qs_param!(params, "limit",  u64);

    // TODO: flatten me mommy
    if let (Some(channel), Some(start), Some(end)) = (channel_id, 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>, body: Body, headers: HeaderMap, params: HashMap<String, String>) {
    /*
     * Message content is sent in the message body
     * @channel_id: channel id that we're going to send a message to
     * @content-type in the headers
     * TODO: more features here because send_message is a large handler
    */
    use db::Response::*;
    use db::Member;

    // NOTE: auth module guarantees that id will be present so the unwrap is safe
    let uid = qs_param!(params, "id", u64).unwrap();
    let ctype: Option<&str> = match headers.get("Content-Type") {
        Some(hval) => {
            match hval.to_str() {
                Ok(s) => Some(s),
                _ => None
            }
        },
        None => None
    };

    let permissions = match Member::get(pool, uid).await {
        Row(user) => user.permissions,
        _ => 0
    };
    if perms::has_perm(permissions, perms::SEND_MESSAGES) == false {
        *response.status_mut() = StatusCode::BAD_REQUEST;
        return;
    }

    let channel_id = qs_param!(params, "channel_id", u64);

    // Black magic
    let body_bytes: &[u8] = &to_bytes(body).await.unwrap(); // yolo
    let content = String::from_utf8_lossy(body_bytes);

    // 400 on empty bodies or missing channel id's
    if content.len() == 0 || channel_id.is_none() || ctype.is_none() {
        *response.status_mut() = StatusCode::BAD_REQUEST;
    } else {
        // block away wrong content types
        const CONTENT_TYPES: [&'static str;7] = ["text", "png", "jpeg", "jpg", "webm", "mp3", "mp4"];
        if CONTENT_TYPES.contains(&ctype.unwrap()) == false {
            *response.status_mut() = StatusCode::BAD_REQUEST;
        } 
        else {
            // ctype safe unwrap
            match db::Message::send(pool, &content, ctype.unwrap(), channel_id.unwrap(), uid).await {
                Ok(Empty) => {/* TODO: put something here to notify the rtc server if its there*/},
                Ok(RestrictedInput(_msg)) => *response.status_mut() = StatusCode::BAD_REQUEST,
                Ok(Other(msg)) => {
                    eprintln!("{}", msg);
                    *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
                },
                _ => *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR
            }
        }
    }
}

pub async fn from_id(pool: &Pool, response: &mut Response<Body>, params: HashMap<String, String>) {
    /*
     * @start-id: u64
     * @limit: optional<u64>
     * @channel: u64
     * {
     *  "channel_id": 1,
     *  "start": 123,
     *  "limit": 100
     * }
     */
    let channel = qs_param!(params, "channel_id", u64);
    let start_id = qs_param!(params, "start", u64);    
    let limit = qs_param!(params, "limit", u64);

    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) => {
                        // *any* kind of empty response, even those from weird
                        // parameters get 404's
                        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;
    }
}
