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

use bson::{doc, from_bson, Bson, Bson::UtcDatetime};
use chrono::prelude::*;
use mongodb::options::FindOptions;
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 = vec_to_set(&info.users);
    set.insert(user.id.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 mut query = vec![];
    for item in &set {
        if item == &user.id {
            continue;
        }

        query.push(Bson::String(item.clone()));
    }

    if let Ok(result) = database::get_collection("users").find(
        doc! {
            "_id": {
                "$in": &query
            }
        },
        FindOptions::builder().limit(query.len() as i64).build(),
    ) {
        if result.count() != query.len() {
            return Response::BadRequest(json!({ "error": "Specified non-existant user(s)." }));
        }

        let relationships = user.fetch_relationships();
        for item in set {
            if item == user.id {
                continue;
            }

            if get_relationship_internal(&user.id, &item, &relationships) != Relationship::Friend {
                return Response::BadRequest(json!({ "error": "Not friends with user(s)." }));
            }
        }

        query.push(Bson::String(user.id.clone()));

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

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

    match target.channel_type {
        0 => Some(Response::Success(json!({
            "id": target.id,
            "type": target.channel_type,
            "recipients": target.recipients,
        }))),
        1 => {
            if let Some(info) = target.fetch_data(doc! {
                "name": 1,
                "description": 1,
                "owner": 1,
            }) {
                Some(Response::Success(json!({
                    "id": target.id,
                    "type": target.channel_type,
                    "recipients": target.recipients,
                    "name": info.get_str("name").unwrap(),
                    "owner": info.get_str("owner").unwrap(),
                    "description": info.get_str("description").unwrap_or(""),
                })))
            } else {
                None
            }
        }
        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!(),
    }
}

/// [groups] add user to channel
#[put("/<target>/recipients/<member>")]
pub fn add_member(user: UserRef, target: ChannelRef, member: UserRef) -> Option<Response> {
    if target.channel_type != 1 {
        return Some(Response::BadRequest(json!({ "error": "Not a group DM." })));
    }

    with_permissions!(user, target);

    let recp = target.recipients.unwrap();
    if recp.len() == 50 {
        return Some(Response::BadRequest(
            json!({ "error": "Maximum group size is 50." }),
        ));
    }

    let set = vec_to_set(&recp);
    if set.get(&member.id).is_some() {
        return Some(Response::BadRequest(
            json!({ "error": "User already in group!" }),
        ));
    }

    match get_relationship(&user, &member) {
        Relationship::Friend => {
            if database::get_collection("channels")
                .update_one(
                    doc! { "_id": &target.id },
                    doc! {
                        "$push": {
                            "recipients": &member.id
                        }
                    },
                    None,
                )
                .is_ok()
            {
                Some(Response::Result(super::Status::Ok))
            } else {
                Some(Response::InternalServerError(
                    json!({ "error": "Failed to add user to group." }),
                ))
            }
        }
        _ => Some(Response::BadRequest(
            json!({ "error": "Not friends with user." }),
        )),
    }
}

/// [groups] remove user from channel
#[delete("/<target>/recipients/<member>")]
pub fn remove_member(user: UserRef, target: ChannelRef, member: UserRef) -> Option<Response> {
    if target.channel_type != 1 {
        return Some(Response::BadRequest(json!({ "error": "Not a group DM." })));
    }

    if &user.id == &member.id {
        return Some(Response::BadRequest(
            json!({ "error": "Cannot kick yourself, leave the channel instead." }),
        ));
    }

    let permissions = with_permissions!(user, target);

    if !permissions.get_kick_members() {
        return Some(Response::LackingPermission(Permission::KickMembers));
    }

    let set = vec_to_set(&target.recipients.unwrap());
    if set.get(&member.id).is_none() {
        return Some(Response::BadRequest(
            json!({ "error": "User not in group!" }),
        ));
    }

    if database::get_collection("channels")
        .update_one(
            doc! { "_id": &target.id },
            doc! {
                "$pull": {
                    "recipients": &member.id
                }
            },
            None,
        )
        .is_ok()
    {
        Some(Response::Result(super::Status::Ok))
    } else {
        Some(Response::InternalServerError(
            json!({ "error": "Failed to add user to group." }),
        ))
    }
}

/// 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");
    let target_id = target.id.clone();

    let try_delete = || {
        let messages = database::get_collection("messages");

        if messages
            .delete_many(doc! { "channel": &target_id }, None)
            .is_ok()
        {
            if col.delete_one(doc! { "_id": &target_id }, None).is_ok() {
                Some(Response::Result(super::Status::Ok))
            } else {
                Some(Response::InternalServerError(
                    json!({ "error": "Failed to delete group." }),
                ))
            }
        } else {
            Some(Response::InternalServerError(
                json!({ "error": "Failed to delete messages." }),
            ))
        }
    };

    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 => {
            let mut recipients =
                vec_to_set(&target.recipients.expect("Missing recipients on Group DM."));
            let owner = target.owner.expect("Missing owner on Group DM.");

            if recipients.len() == 1 {
                try_delete()
            } else {
                recipients.remove(&user.id);
                let new_owner = if owner == user.id {
                    recipients.iter().next().unwrap()
                } else {
                    &owner
                };

                if col
                    .update_one(
                        doc! { "_id": target_id },
                        doc! {
                            "$set": {
                                "owner": new_owner,
                            },
                            "$pull": {
                                "recipients": &user.id,
                            }
                        },
                        None,
                    )
                    .is_ok()
                {
                    Some(Response::Result(super::Status::Ok))
                } else {
                    Some(Response::InternalServerError(
                        json!({ "error": "Failed to remove you from the group." }),
                    ))
                }
            }
        }
        2 => try_delete(),
        _ => 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 col
        .find_one(doc! { "nonce": nonce.clone() }, None)
        .unwrap()
        .is_some()
    {
        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,
                    "channel": target.id.clone(),
                    "author": user.id,
                    "content": content,
                },
                None,
            )
            .is_ok()
        {
            if target.channel_type == ChannelType::DM as u8 {
                let col = database::get_collection("channels");
                col.update_one(
                    doc! { "_id": &target.id },
                    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)
            },
            "$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 }, 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." }),
        )),
    }
}