use super::Response;
use crate::database::{
    self, get_relationship_internal, message::Message, Permission, PermissionCalculator,
    Relationship,
};
use crate::guards::auth::UserRef;
use crate::guards::channel::ChannelRef;

use bson::{doc, from_bson, Bson, Bson::UtcDatetime};
use chrono::prelude::*;
use hashbrown::HashSet;
use num_enum::TryFromPrimitive;
use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize};
use ulid::Ulid;

const MAXGROUPSIZE: usize = 50;

#[derive(Debug, TryFromPrimitive)]
#[repr(usize)]
pub enum ChannelType {
    DM = 0,
    GROUPDM = 1,
    GUILDCHANNEL = 2,
}

macro_rules! with_permissions {
    ($user: expr, $target: expr) => {{
        let permissions = PermissionCalculator::new($user.clone())
            .channel($target.clone())
            .as_permission();

        if !permissions.get_access() {
            return None;
        }

        permissions
    }};
}

#[derive(Serialize, Deserialize)]
pub struct CreateGroup {
    name: String,
    nonce: String,
    users: Vec<String>,
}

/// create a new group
#[post("/create", data = "<info>")]
pub fn create_group(user: UserRef, info: Json<CreateGroup>) -> Response {
    let name: String = info.name.chars().take(32).collect();
    let nonce: String = info.nonce.chars().take(32).collect();

    let mut set = HashSet::new();
    set.insert(user.id.clone());
    for item in &info.users {
        set.insert(item.clone());
    }

    if set.len() > MAXGROUPSIZE {
        return Response::BadRequest(json!({ "error": "Maximum group size is 50." }));
    }

    let col = database::get_collection("channels");
    if let Some(_) = col.find_one(doc! { "nonce": nonce.clone() }, None).unwrap() {
        return Response::BadRequest(json!({ "error": "Group already created!" }));
    }

    let relationships = user.fetch_relationships();

    let mut users = vec![];
    for item in set {
        if let Some(target) = UserRef::from(item) {
            if get_relationship_internal(&user.id, &target.id, &relationships)
                != Relationship::Friend
            {
                return Response::BadRequest(json!({ "error": "Not friends with user(s)." }));
            }

            users.push(Bson::String(target.id));
        } else {
            return Response::BadRequest(json!({ "error": "Specified non-existant user(s)." }));
        }
    }

    let id = Ulid::new().to_string();
    if col
        .insert_one(
            doc! {
                "_id": id.clone(),
                "nonce": nonce,
                "type": ChannelType::GROUPDM as u32,
                "recipients": users,
                "name": name,
            },
            None,
        )
        .is_ok()
    {
        Response::Success(json!({ "id": id }))
    } else {
        Response::InternalServerError(json!({ "error": "Failed to create guild channel." }))
    }
}

/// fetch channel information
#[get("/<target>")]
pub fn channel(user: UserRef, target: ChannelRef) -> Option<Response> {
    with_permissions!(user, target);

    match target.channel_type {
        0..=1 => Some(Response::Success(json!({
            "id": target.id,
            "type": target.channel_type,
            "recipients": target.recipients,
        }))),
        2 => {
            if let Some(info) = target.fetch_data(doc! {
                "name": 1,
                "description": 1,
            }) {
                Some(Response::Success(json!({
                    "id": target.id,
                    "type": target.channel_type,
                    "guild": target.guild,
                    "name": info.get_str("name").unwrap(),
                    "description": info.get_str("description").unwrap_or(""),
                })))
            } else {
                None
            }
        }
        _ => unreachable!(),
    }
}

/// delete channel
/// or leave group DM
/// or close DM conversation
#[delete("/<target>")]
pub fn delete(user: UserRef, target: ChannelRef) -> Option<Response> {
    let permissions = with_permissions!(user, target);

    if !permissions.get_manage_channels() {
        return Some(Response::LackingPermission(Permission::ManageChannels));
    }

    let col = database::get_collection("channels");
    match target.channel_type {
        0 => {
            if col
                .update_one(
                    doc! { "_id": target.id },
                    doc! { "$set": { "active": false } },
                    None,
                )
                .is_ok()
            {
                Some(Response::Result(super::Status::Ok))
            } else {
                Some(Response::InternalServerError(
                    json!({ "error": "Failed to close channel." }),
                ))
            }
        }
        1 => {
            // ? TODO: group dm

            Some(Response::Result(super::Status::Ok))
        }
        2 => {
            // ? TODO: guild

            Some(Response::Result(super::Status::Ok))
        }
        _ => Some(Response::InternalServerError(
            json!({ "error": "Unknown error has occurred." }),
        )),
    }
}

/// fetch channel messages
#[get("/<target>/messages")]
pub fn messages(user: UserRef, target: ChannelRef) -> Option<Response> {
    let permissions = with_permissions!(user, target);

    if !permissions.get_read_messages() {
        return Some(Response::LackingPermission(Permission::ReadMessages));
    }

    let col = database::get_collection("messages");
    let result = col.find(doc! { "channel": target.id }, None).unwrap();

    let mut messages = Vec::new();
    for item in result {
        let message: Message =
            from_bson(bson::Bson::Document(item.unwrap())).expect("Failed to unwrap message.");
        messages.push(json!({
            "id": message.id,
            "author": message.author,
            "content": message.content,
            "edited": if let Some(t) = message.edited { Some(t.timestamp()) } else { None }
        }));
    }

    Some(Response::Success(json!(messages)))
}

#[derive(Serialize, Deserialize)]
pub struct SendMessage {
    content: String,
    nonce: String,
}

/// send a message to a channel
#[post("/<target>/messages", data = "<message>")]
pub fn send_message(
    user: UserRef,
    target: ChannelRef,
    message: Json<SendMessage>,
) -> Option<Response> {
    let permissions = with_permissions!(user, target);

    if !permissions.get_send_messages() {
        return Some(Response::LackingPermission(Permission::SendMessages));
    }

    let content: String = message.content.chars().take(2000).collect();
    let nonce: String = message.nonce.chars().take(32).collect();

    let col = database::get_collection("messages");
    if let Some(_) = col.find_one(doc! { "nonce": nonce.clone() }, None).unwrap() {
        return Some(Response::BadRequest(
            json!({ "error": "Message already sent!" }),
        ));
    }

    let id = Ulid::new().to_string();
    Some(
        if col
            .insert_one(
                doc! {
                    "_id": id.clone(),
                    "nonce": nonce.clone(),
                    "channel": target.id.clone(),
                    "author": user.id.clone(),
                    "content": content.clone(),
                },
                None,
            )
            .is_ok()
        {
            if target.channel_type == ChannelType::DM as u8 {
                let col = database::get_collection("channels");
                col.update_one(
                    doc! { "_id": target.id.clone() },
                    doc! { "$set": { "active": true } },
                    None,
                )
                .unwrap();
            }

            /*websocket::queue_message(
                get_recipients(&target),
                json!({
                    "type": "message",
                    "data": {
                        "id": id.clone(),
                        "nonce": nonce,
                        "channel": target.id,
                        "author": user.id,
                        "content": content,
                    },
                })
                .to_string(),
            );*/

            Response::Success(json!({ "id": id }))
        } else {
            Response::InternalServerError(json!({
                "error": "Failed database query."
            }))
        },
    )
}

/// get a message
#[get("/<target>/messages/<message>")]
pub fn get_message(user: UserRef, target: ChannelRef, message: Message) -> Option<Response> {
    let permissions = with_permissions!(user, target);

    if !permissions.get_read_messages() {
        return Some(Response::LackingPermission(Permission::ReadMessages));
    }

    let prev =
        // ! CHECK IF USER HAS PERMISSION TO VIEW EDITS OF MESSAGES
        if let Some(previous) = message.previous_content {
            let mut entries = vec![];
            for entry in previous {
                entries.push(json!({
                    "content": entry.content,
                    "time": entry.time.timestamp(),
                }));
            }

            Some(entries)
        } else {
            None
        };

    Some(Response::Success(json!({
        "id": message.id,
        "author": message.author,
        "content": message.content,
        "edited": if let Some(t) = message.edited { Some(t.timestamp()) } else { None },
        "previous_content": prev,
    })))
}

#[derive(Serialize, Deserialize)]
pub struct EditMessage {
    content: String,
}

/// edit a message
#[patch("/<target>/messages/<message>", data = "<edit>")]
pub fn edit_message(
    user: UserRef,
    target: ChannelRef,
    message: Message,
    edit: Json<EditMessage>,
) -> Option<Response> {
    with_permissions!(user, target);

    if message.author != user.id {
        return Some(Response::Unauthorized(
            json!({ "error": "You did not send this message." }),
        ));
    }

    let col = database::get_collection("messages");
    let time = if let Some(edited) = message.edited {
        edited.0
    } else {
        Ulid::from_string(&message.id).unwrap().datetime()
    };

    let edited = Utc::now();
    match col.update_one(
        doc! { "_id": message.id.clone() },
        doc! {
            "$set": {
                "content": edit.content.clone(),
                "edited": UtcDatetime(edited.clone())
            },
            "$push": {
                "previous_content": {
                    "content": message.content,
                    "time": time,
                }
            },
        },
        None,
    ) {
        Ok(_) => {
            /*websocket::queue_message(
                get_recipients(&target),
                json!({
                    "type": "message_update",
                    "data": {
                        "id": message.id,
                        "channel": target.id,
                        "content": edit.content.clone(),
                        "edited": edited.timestamp()
                    },
                })
                .to_string(),
            );*/

            Some(Response::Result(super::Status::Ok))
        }
        Err(_) => Some(Response::InternalServerError(
            json!({ "error": "Failed to update message." }),
        )),
    }
}

/// delete a message
#[delete("/<target>/messages/<message>")]
pub fn delete_message(user: UserRef, target: ChannelRef, message: Message) -> Option<Response> {
    let permissions = with_permissions!(user, target);

    if !permissions.get_manage_messages() {
        if message.author != user.id {
            return Some(Response::LackingPermission(Permission::ManageMessages));
        }
    }

    let col = database::get_collection("messages");

    match col.delete_one(doc! { "_id": message.id.clone() }, None) {
        Ok(_) => {
            /*websocket::queue_message(
                get_recipients(&target),
                json!({
                    "type": "message_delete",
                    "data": {
                        "id": message.id,
                        "channel": target.id
                    },
                })
                .to_string(),
            );*/

            Some(Response::Result(super::Status::Ok))
        }
        Err(_) => Some(Response::InternalServerError(
            json!({ "error": "Failed to delete message." }),
        )),
    }
}