import UsersManager from "../Users/Users";
import CompaniesManager from "../Companies/Companies";
import {
    ConvertU2UMessageConversationParams,
    CountUnreadConversationParams,
    CreateMessageConversationParams,
    DeleteConversationsByCompanyParams,
    DeleteConversationsByUserParams,
    DeleteMessageConversationParams,
    FindConversationItem,
    FindConversationsResults,
    IMessageConversation,
    IMessageConversationsManager,
    MarkConversationAsReadParams,
    MESSAGE_CONVERSATION_TYPES,
    PaginateFindContactsParams,
    PaginateFindContactsResults,
    PaginateMessageConversationsParams,
    PaginateMessageConversationsResults,
} from "./MessagesConversations.Interface";
import { MessageConversationsAdapterMongoose } from "./MessagesConversations.Adapter.Mongoose";
import EmployeesList from "../Employees/Employee";
import ClientsList from "../Clients/Clients";
import getAvatar from "../../helpers/getAvatar";
import UserList from "../Users/Users";
import MessagesList from "../Messages/Messages";
import { isNull } from "../../helpers/IsNull";
import SystemNoficationsList from "../SystemNotifications/SystemNotification";
import mongoose from "mongoose";

type GetConversationParams = {
    userId: string;
};

class MessageConversationsManager implements IMessageConversationsManager {
    conversations: MessageConversationsAdapterMongoose;

    constructor() {
        this.conversations = new MessageConversationsAdapterMongoose();
    }

    public async deleteConversationsByUser(data: DeleteConversationsByUserParams): Promise<void> {
        const conversationsFrom = await this.conversations.find({ userIdFrom: data.userId });

        for (const conv of conversationsFrom) {
            await MessagesList.messages.messageList.deleteMany({ conversationId: conv.id });
        }

        const conversationsTo = await this.conversations.find({ userIdTo: data.userId });

        for (const conv of conversationsTo) {
            await MessagesList.messages.messageList.deleteMany({ conversationId: conv.id });
        }

        await this.conversations.conversationList.deleteMany({ userIdFrom: data.userId });
        await this.conversations.conversationList.deleteMany({ userIdTo: data.userId });
    }

    public async deleteConversationsByCompany(
        data: DeleteConversationsByCompanyParams
    ): Promise<void> {
        const conversations = await this.conversations.find({ companyId: data.companyId });

        for (const conv of conversations) {
            await MessagesList.messages.messageList.deleteMany({ conversationId: conv.id });
        }

        await this.conversations.conversationList.deleteMany({ companyId: data.companyId });
    }

    public async createConversation(
        data: CreateMessageConversationParams
    ): Promise<IMessageConversation> {
        if (data.userIdFrom) {
            const userFromCheck = await UsersManager.users.findOne({ _id: data.userIdFrom });
            if (!userFromCheck) {
                throw new Error("Usuario invalido");
            }
        }

        if (data.userIdTo) {
            const userToCheck = await UsersManager.users.findOne({ _id: data.userIdTo });
            if (!userToCheck) {
                throw new Error("Usuario invalido");
            }
        }

        if (data.companyId) {
            const companyCheck = await CompaniesManager.companies.findOne({ _id: data.companyId });

            if (!companyCheck) {
                throw new Error("La compañia no existe");
            }
        }

        const newConversation = await this.conversations.create(data);

        return newConversation;
    }

    public async deleteConversation(data: DeleteMessageConversationParams): Promise<void> {
        const checkUser = await UsersManager.users.findOne({ _id: data.sessionUser });
        if (!checkUser) {
            throw new Error("Usuario invalido");
        }

        const checkConversation = await this.conversations.conversationList.findOne({
            _id: data.id,
        });

        if (!checkConversation) {
            throw new Error("Conversacion invalida");
        }

        if (checkConversation.type == MESSAGE_CONVERSATION_TYPES.USER_TO_USER) {
            if (String(checkConversation.userIdFrom) !== data.sessionUser) {
                throw new Error("No tienes permiso para eliminar esta conversacion");
            }
        } else if (checkConversation.type == MESSAGE_CONVERSATION_TYPES.USER_TO_ORG) {
            if (String(checkConversation.userIdFrom) !== data.sessionUser) {
                throw new Error("No tienes permiso para eliminar esta conversacion");
            }
        }

        await this.conversations.delete(data.id);
    }

    public async paginateConversations(
        filters: PaginateMessageConversationsParams
    ): Promise<PaginateMessageConversationsResults> {
        return await this.conversations.paginate(filters);
    }

    private async getUserRelationWithMe(userId: string, sessionUser: string): Promise<string> {
        const myOrgs = await EmployeesList.employees.find({ userId: sessionUser });

        for (const org of myOrgs) {
            const organization = await CompaniesManager.companies.findOne({ _id: org.companyId });

            if (!organization) {
                continue;
            }

            //Buscamos si es un colaborador en una de mis organizaciones.

            const employee = await EmployeesList.employees.findOne({
                userId,
                companyId: org.companyId,
            });

            if (employee) {
                return "Colaborador en " + organization.name;
            }

            //Buscamos si es un cliente en una de mis organizaciones.

            const client = await ClientsList.clients.findOne({
                userId: userId,
                companyId: org.companyId,
            });

            if (client) {
                return "Cliente de " + organization.name;
            }
        }

        const orgsAsClient = await ClientsList.clients.find({
            userId: sessionUser,
        });

        for (const organization of orgsAsClient) {
            const checkColaIsCollaborator = await EmployeesList.employees.findOne({
                companyId: organization.companyId,
                userId: userId,
            });

            if (checkColaIsCollaborator) {
                const org = await CompaniesManager.companies.findOne({
                    _id: organization.companyId,
                });
                if (!org) {
                    continue;
                }
                return "Miembro de " + org.name;
            }
        }

        return "Otros contactos";
    }

    public async findConversations(
        data: PaginateFindContactsParams
    ): Promise<FindConversationsResults> {
        const items: FindConversationItem[] = [];
        const conversations = await this.conversations.paginate(data);

        for (const conversation of conversations.data) {
            let contactName = "";
            let contactUserId: string | undefined = "";
            let contactAvatar: string = "";
            let conversationName = "";
            let conversationDescription = "";
            let conversationImage = "";

            if (conversation.type == MESSAGE_CONVERSATION_TYPES.USER_TO_USER) {
                if (conversation.userIdFrom == data.sessionUser) {
                    const user = await UserList.users.findOne({ _id: conversation.userIdTo });
                    if (!user) {
                        continue;
                    }
                    contactName = UserList.getUserFullName(user);
                    contactUserId = String(conversation.userIdTo);
                    contactAvatar = user.avatar ? user.avatar : "";
                } else if (conversation.userIdTo == data.sessionUser) {
                    const user = await UserList.users.findOne({ _id: conversation.userIdFrom });
                    if (!user) {
                        continue;
                    }
                    contactName = UserList.getUserFullName(user);
                    contactUserId = String(conversation.userIdFrom);
                    contactAvatar = user.avatar ? user.avatar : "";
                }

                if (contactUserId) {
                    conversationName = contactName;
                    conversationDescription = await this.getUserRelationWithMe(
                        contactUserId,
                        data.sessionUser
                    );
                    conversationImage = getAvatar(contactUserId, contactAvatar, contactName);
                }
            } else if (conversation.type == MESSAGE_CONVERSATION_TYPES.USER_TO_ORG) {
                if (conversation.userIdFrom == data.sessionUser) {
                    const org = await CompaniesManager.companies.findOne({
                        _id: conversation.companyId,
                    });
                    if (!org) {
                        continue;
                    }
                    conversationName = org.name;
                    conversationDescription = org.description;
                    conversationImage = isNull<string>(org.iconFile, org.name);
                } else if (conversation.companyId) {
                    const user = await UserList.users.findOne({ _id: conversation.userIdFrom });
                    if (!user) {
                        continue;
                    }
                    conversationName = UserList.getUserFullName(user);
                    conversationDescription = "Consulta a la organización";
                    conversationImage = getAvatar(user.id, user.avatar, conversationName);
                }
            }

            let unreadCount = 0;

            if (conversation && conversation.id) {
                unreadCount = await this.countUnreadMessages({
                    conversationId: conversation.id,
                    sessionUser: data.sessionUser,
                });
            }

            items.push({
                conversation: {
                    id: conversation.id,
                    userIdFrom: conversation.userIdFrom,
                    userIdTo: conversation.userIdTo,
                    companyId: conversation.companyId,
                    type: conversation.type,
                    newMessages: unreadCount,
                },
                name: conversationName,
                description: conversationDescription,
                image: conversationImage,
                orderWeight: 1,
            });
        }

        return {
            data: items,
            page: conversations.page,
            pages: conversations.pages,
        } as FindConversationsResults;
    }

    private async getConversationIfExists(
        data: GetConversationParams
    ): Promise<IMessageConversation | null> {
        let conversation = await this.conversations.conversationList.findOne({
            userIdFrom: data.userId,
        });

        if (!conversation) {
            conversation = await this.conversations.conversationList.findOne({
                userIdTo: data.userId,
            });
        }

        return conversation;
    }

    public async paginateFindContacts(
        data: PaginateFindContactsParams
    ): Promise<PaginateFindContactsResults> {
        const items: FindConversationItem[] = [];

        //Mostrar las organizaciones en las que el usuario es cliente.
        const companiesAsClient = await CompaniesManager.getByClientId({
            clientId: data.sessionUser,
            sessionUser: data.sessionUser,
        });

        for (const org of companiesAsClient) {
            items.push({
                conversation: {
                    companyId: org.id,
                    type: MESSAGE_CONVERSATION_TYPES.USER_TO_ORG,
                    newMessages: 0,
                },
                name: org.name,
                description: org.description,
                image: org.logoFile,
                orderWeight: 1,
            });

            //Mostrar los colaboradores de esa organizacion.
            const employeesByOrganization = await EmployeesList.findByCompanyId({
                companyId: org.id,
            });

            for (const employee of employeesByOrganization) {
                const conversation = await this.getConversationIfExists({
                    userId: employee.userId,
                });

                let unreadCount = 0;

                if (conversation && conversation.id) {
                    unreadCount = await this.countUnreadMessages({
                        conversationId: conversation.id,
                        sessionUser: data.sessionUser,
                    });
                }

                items.push({
                    conversation: conversation
                        ? ({
                              id: conversation.id,
                              userIdFrom: conversation.userIdFrom,
                              userIdTo: conversation.userIdTo,
                              companyId: conversation.companyId,
                              type: conversation.type,
                              newMessages: unreadCount,
                          } as IMessageConversation)
                        : {
                              userIdTo: employee.userId,
                              type: MESSAGE_CONVERSATION_TYPES.USER_TO_USER,
                              newMessages: 0,
                          },
                    name: employee.fullName,
                    description: org.name,
                    image: employee.avatar,
                    orderWeight: 2,
                });
            }
        }

        //Mostras los clientes y colaboradores de las organizaciones donde el usuario es colaborador.
        const companiesAsCollaborator = await EmployeesList.employees.find({
            userId: data.sessionUser,
        });

        for (const org of companiesAsCollaborator) {
            const organization = await CompaniesManager.companies.findOne({ _id: org.companyId });

            if (!organization) {
                continue;
            }

            //Mostras los clientes de las organizaciones donde el usuario es colaborador.
            const organizationClients = await ClientsList.clients.find({
                companyId: org.companyId,
            });

            for (const client of organizationClients) {
                if (!client.userId) {
                    continue;
                }
                const clientName = ClientsList.getClientFullName(client);
                items.push({
                    conversation: {
                        userIdFrom: data.sessionUser,
                        userIdTo: client.userId,
                        type: MESSAGE_CONVERSATION_TYPES.USER_TO_USER,
                        newMessages: 0,
                    },
                    name: clientName,
                    description: "Cliente de " + organization.name,
                    image: getAvatar(client.userId, "", clientName),
                    orderWeight: 2,
                });
            }

            //Mostrar los colaboradores de las organizaciones donde el usuario es colaborador.
            const organizationEmployees = await EmployeesList.employees.find({
                companyId: org.companyId,
            });

            for (const employee of organizationEmployees) {
                const employeeUser = await UserList.users.findOne({ _id: employee.userId });
                if (!employeeUser) {
                    continue;
                }
                if (employee.userId == data.sessionUser) {
                    continue;
                }
                const employeeName = UserList.getUserFullName(employeeUser);
                items.push({
                    conversation: {
                        userIdFrom: data.sessionUser,
                        userIdTo: employee.userId,
                        type: MESSAGE_CONVERSATION_TYPES.USER_TO_USER,
                        newMessages: 0,
                    },
                    name: employeeName,
                    description: "Colaborador en " + organization.name,
                    image: getAvatar(employee.userId, "", employeeName),
                    orderWeight: 2,
                });
            }
        }

        const query = data.query ? data.query.toUpperCase() : "";

        return {
            data: items.filter(
                (i) =>
                    String(i.name + i.description)
                        .toUpperCase()
                        .indexOf(query) >= 0
            ),
            page: 1,
            pages: 1,
        } as PaginateFindContactsResults;
    }

    public async markConversationAsRead(data: MarkConversationAsReadParams): Promise<void> {
        const checkUser = await UsersManager.users.findOne({ _id: data.sessionUser });
        if (!checkUser) {
            throw new Error("Usuario invalido");
        }

        const checkConversation = await this.conversations.conversationList.findOne({
            _id: data.conversationId,
        });

        if (!checkConversation) {
            throw new Error("Conversacion invalida");
        }

        await MessagesList.markAsRead({
            conversationId: data.conversationId,
            sessionUser: data.sessionUser,
        });

        await SystemNoficationsList.deleteConversationNotifications({
            conversationId: data.conversationId,
            userId: data.sessionUser,
            sessionUser: data.sessionUser,
        });
    }

    public async convertUserToUser(data: ConvertU2UMessageConversationParams): Promise<void> {
        const checkUser = await UsersManager.users.findOne({ _id: data.sessionUser });
        if (!checkUser) {
            throw new Error("Usuario invalido");
        }

        const checkConversation = await this.conversations.conversationList.findOne({
            _id: data.conversationId,
        });

        if (!checkConversation) {
            throw new Error("Conversacion invalida");
        }

        checkConversation.type = MESSAGE_CONVERSATION_TYPES.USER_TO_USER;
        checkConversation.userIdTo = data.sessionUser;
        await checkConversation.save();
    }

    public async countUnreadMessages(data: CountUnreadConversationParams): Promise<number> {
        const searchCriteria: mongoose.FilterQuery<IMessageConversation> = {};
        searchCriteria.conversationId = data.conversationId;
        searchCriteria.readed = false;
        searchCriteria.userId = { $ne: data.sessionUser };
        const unreadMessages = await MessagesList.messages.find(searchCriteria);
        return unreadMessages.length;
    }
}

const MessageConversationList = new MessageConversationsManager();

export default MessageConversationList;
