use hyper::{StatusCode, Response, Body};
use hyper::header::HeaderValue;

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

use serde_json::Value;

use crate::db_types::{UBigInt, VarChar, Integer};
use crate::common;

#[derive(Debug)]
pub enum ChannelType {
    Voice,
    Text,
    Undefined
}

impl ChannelType {
    // These funcs are mainly here to help translation from mysql
    pub fn from_i32(x: i32) -> ChannelType {
        match x {
            1 => ChannelType::Voice,
            2 => ChannelType::Text,
            _ => ChannelType::Undefined
        }
    }
    pub fn as_i32(&self) -> i32 {
        match self {
            ChannelType::Voice => 1,
            ChannelType::Text => 2,
            ChannelType::Undefined => 3,

        }
    }
}

// Primary way of interpretting sql data on our channels table
pub struct Channel {
    pub id: u64,
    pub name: String,
    pub description: String,
    pub kind: ChannelType
}

#[derive(Debug)]
struct InsertableChannel {
    name: String,
    kind: ChannelType
}

impl Channel {
    /* 
     * When our sql library queries things we generally get back tuples rather reasily 
     * we can use this method to get something that makes more sense
    */
    fn from_tup(tup: (UBigInt, VarChar, Option<VarChar>, Integer))  -> Channel {
        let desc = match tup.2 {
            Some(val) => val,
            None => "None".into()
        };

        Channel {
            id: tup.0,
            name: tup.1,
            description: desc,
            kind: ChannelType::from_i32(tup.3)
        }
    }
    /*
     * When responding with some channel data to the client we use json
     * this itemizes a single struct as the following(without the pretty output)
     * {
     *  "id": id<i32>,
     *  "name": "<some name here>",
     *  "description": Option<"<description here>">,
     *  "kind": kind<i32>
     * }
    */
    fn as_json_str(&self) -> String {
        let mut base = String::from("{");
        base.push_str(&format!("\"id\":{},", self.id));
        base.push_str(&format!("\"name\":\"{}\",", self.name));
        base.push_str(&format!("\"description\":\"{}\",", self.description));
        base.push_str(&format!("\"kind\":{}}}", self.kind.as_i32()));
        return base;
    }
}


/*
 * Forwarding SQL errors as we can handle those error in caller site for this leaf function
 * On success we back a Vec<Channel> which we can JSON'ify later and use as a response
*/
async fn get_channels_vec(conn: Conn) -> Result<Vec<Channel>, Error> {
    let rows_db = conn.prep_exec(r"SELECT * FROM channels", ()).await?;
    let (_, rows) = rows_db.map_and_drop(|row| {
        let (id, name, desc, kind): (UBigInt, VarChar, Option<VarChar>, Integer) = mysql_async::from_row(row);
        Channel::from_tup((id, name, desc, kind))
    }).await?;

    Ok(rows)
}

pub async fn list_channels(pool: &Pool, response: &mut Response<Body>) {
    /*
     * Primary dispatcher for dealing with the CHANNELS_LIST route
     * For the most part this function will have a lot more error handling as it 
     * should know what kind of issues its child functions will have
    */
    if let Ok(conn) = pool.get_conn().await {
        match get_channels_vec(conn).await {
            Ok(chans) => {
                *response.status_mut() = StatusCode::OK;
                response.headers_mut().insert("Content-Type", 
                    HeaderValue::from_static("application/json"));
                
                // At this point we build the content of our response body
                // which is a json payload hence why there is weird string manipulation
                // because we're trying to avoid dependancy issues and serializing things ourselves
                let mut new_body = String::from("{\"channels\":[");
                for chan in chans.iter() {
                    let s = format!("{},", chan.as_json_str());
                    new_body.push_str(&s);
                }
                if new_body.ends_with(',') {new_body.pop();}
                new_body.push_str("]}");

                *response.body_mut() = Body::from(new_body);
            },
            Err(_) => {
                *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
            }
        }
    }
    else {
        *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
    }
}


async fn insert_channel(pool: &Pool, name: &str, desc: &str, kind: i64) -> Result<(), Error>{
    let conn = pool.get_conn().await?;
    conn.prep_exec(
        "INSERT INTO channels (name, description, kind) VALUES (:name, :description, :kind)", 
        params!{"name" => name, "kind" => kind, "description" => desc}).await?;
    Ok(())
}

pub async fn create_channel(pool: &Pool, response: &mut Response<Body>, params: Value) {
    /*
     * Create a channel base on a few parameters that may or may not be there
    */
    // Theres an extra un-needed unwrap to be cut out from this proc
    // specifically with the desc parameter
    let req_params: (Option<&str>, Option<&str>, Option<i64>) = 
        match (params.get("name"), params.get("description"), params.get("kind")) {
            (Some(name), Some(desc), Some(kind)) => (name.as_str(), desc.as_str(), kind.as_i64()),
            (Some(name), None, Some(kind))       => (name.as_str(), Some("No Description"), kind.as_i64()),
            _ => (None, None, None)
    };
    match req_params {
        (Some(name), Some(desc), Some(kind)) => {
            match insert_channel(pool, name, desc, kind).await {
                // Server Errors are generally _ok_ to reveal in body I suppose
                Err(Error::Server(se)) => {
                    common::db_err_response_body(response, se);
                    //*response.status_mut() = StatusCode::BAD_REQUEST;
                    //let b = format!("Server code: {}\nServer Message:{}", se.code, se.message);
                    //*response.body_mut() = Body::from(b);
                },
                // generic errors get a 500
                Err(_) => {
                    *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
                }
                // Nothing to do when things go right
                _ => {}
            }
        },
        // basically one of the parameter gets failed so we bail on all of this
        _ => *response.status_mut() = StatusCode::BAD_REQUEST
    }
}

async fn db_delete_channel(pool: &Pool, name: &Value) -> Result<(), Error> {
    let conn = pool.get_conn().await?;
    conn.prep_exec(r"DELETE FROM channels WHERE name = :name", params!{"name" => name.as_str().unwrap()}).await?;
    Ok(())
}

pub async fn delete_channel(pool: &Pool, response: &mut Response<Body>, params: Value) {
    // make sure we have the right parameters provided
    if let Some(name) = params.get("name") {
        match db_delete_channel(pool, name).await {
            Ok(_) => *response.status_mut() = StatusCode::OK,
            Err(e) => {
                *response.body_mut() = Body::from(format!("delete_chanel sql error :\n{}", e));
            }
        }
    }
    else {
        *response.status_mut() = StatusCode::BAD_REQUEST;
    }
}


#[cfg(test)]
mod channels_tests {
    use crate::testing::{get_pool, hyper_resp};
    use serde_json::Value;
    use hyper::StatusCode;
    const DUMMY_TRANSIENT_CHANNEL: &'static str = "sample channel";

    #[tokio::test]
    async fn list_all_channels_good() {
        // Generation of data
        let p = get_pool();
        let mut resp = hyper_resp();
        // @params: none
        // Collection of data
        super::list_channels(&p, &mut resp).await;

        // Analysis
        assert_eq!(StatusCode::OK, resp.status());
        println!("list_all_channels_good : \t{:?}", resp.body());
        let _ = p.disconnect().await;
    }

    #[tokio::test]
    async fn delete_and_create_channel_good() {
        use chrono::Utc;
        let p = get_pool();
        let mut resp = hyper_resp();
        // @params: name + kind + [description]
        let cname_id = Utc::now();
        let params: Value = serde_json::from_str(&format!(r#"
            {{
                "name": "{}-{}",
                "kind": 2,
                "description": "some random bs"
            }}
        "#, DUMMY_TRANSIENT_CHANNEL, cname_id)).unwrap();

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

        println!("CREATE CHANNEL: {:?}", resp.body());
        assert_eq!(StatusCode::OK, resp.status());


        // clean up and hopefully delete the channel properly
        let mut resp_delete = hyper_resp();
        let params_delete: Value = serde_json::from_str(&format!(r#"
            {{
                "name": "{}-{}"
            }}
        "#, DUMMY_TRANSIENT_CHANNEL, cname_id)).unwrap();
        println!("Parameters: {}", params_delete);
        super::delete_channel(&p, &mut resp_delete, params_delete).await;

        println!("Body: {:?}", resp.body());
        let _ = p.disconnect().await;
    }
    
    #[tokio::test]
    async fn delete_channel_missing_name() {
        let p = get_pool();
        let mut resp = hyper_resp();
        // this endpoint is super lenient for some reason btw
        let param: Value = serde_json::from_str("{}").expect("JSON is not written correctly");

        super::delete_channel(&p, &mut resp, param).await;

        assert_eq!(StatusCode::BAD_REQUEST, resp.status());
    }

    #[tokio::test]
    async fn delet_channel_non_real_channel() {
        let p = get_pool();
        let mut resp = hyper_resp();
        // this endpoint is super lenient for some reason btw
        let param: Value = serde_json::from_str(r#"{
            "name": "this channel doesn't exist"
        }"#).expect("JSON is not written correctly");

        super::delete_channel(&p, &mut resp, param).await;

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