import { ClientsAdapterMongoose, IClientDocument } from "./Clients.Adapter.Mongoose";
import {
    ClientMergeParams,
    CreateClientByUserParams,
    CreateCustomClientParams,
    DeleteClientParams,
    DelteClientsByCompanyParams,
    FindClientByIdParams,
    IClient,
    IClientsManager,
    OrganizationClientsResults,
    OrganizationClientView,
    PaginateOrganizationClientsParams,
    UpdateClientParams,
} from "./Clients.Interface";
import UsersManager from "../Users/Users";
import CompaniesManager from "../Companies/Companies";
import { EmployeeRoles } from "../Employees/Employees.Interface";
import EmployeesList from "../Employees/Employee";
import { NoPermissionMessage } from "../../helpers/NoPermissionMessage";
import { isNull } from "../../helpers/IsNull";
import { joinStrings } from "../../helpers/String";
import { IUser } from "../Users/Users.Interface";
import getAvatar from "../../helpers/getAvatar";
import {
    TextObjectFilterParams,
    TextObjectFilterResult,
} from "../../Models/TextObjectFilter.model";
import MetricsList from "../../Models/Metrics/Metrics";
import { NotificationsManager } from "../Notifications/Notifications";
import AppointmentList from "../Appointments/Appointments";
import RepeatsList from "../Repeats/Repeats";
import ClientAccount from "../ClientAccounts/ClientAccount";
import Payments from "../Payments/Payments";
import { NotificationType } from "../../Models/SystemNotifications/SystemNotification.Interface";

class ClientManager implements IClientsManager {
    clients: ClientsAdapterMongoose;

    constructor() {
        this.clients = new ClientsAdapterMongoose();
    }

    public async deleteClient(data: DeleteClientParams): Promise<void> {
        const { checkCashOnAccount = true } = data;

        const userChek = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

        if (!userChek) {
            throw new Error(
                "No se ha encontrado el usuario o no tiene permisos para realizar esta acción"
            );
        }

        const clientChek = await this.clients.findOne({ _id: data.clientId });

        if (!clientChek) {
            throw new Error("El cliente no existe");
        }

        //Obtener el saldo de la cuenta del cliente.
        if (checkCashOnAccount) {
            const clientAccountAmount = await ClientAccount.getAvailableAmount({
                clientId: data.clientId,
                sessionUser: data.sessionUser,
            });

            if (clientAccountAmount > 0) {
                throw new Error(
                    "No se puede eliminar el cliente ya que tiene saldo positivo en la cuenta."
                );
            }
        }

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

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

        if (
            !(await EmployeesList.checkPermission(
                companyCheck,
                data.sessionUser,
                EmployeeRoles.ADMIN
            ))
        ) {
            throw new Error(NoPermissionMessage());
        }

        //obtener los turnos del cliente
        const appointments = await AppointmentList.Appointments.find({
            clientId: data.clientId,
        });

        //eliminar los turnos
        for (const item of appointments) {
            if (!item.id) {
                continue;
            }
            await AppointmentList.Appointments.delete(item.id);
        }

        //Eliminar las repeticiones.
        await RepeatsList.deleteRepeatsByClient({ clientId: data.clientId });

        //Eliminar la cuenta del cliente.
        await ClientAccount.deleteClientAccount({
            clientId: data.clientId,
            checkCashOnAccount: checkCashOnAccount,
            sessionUser: data.sessionUser,
        });

        //Eliminar los pagos del cliente.
        await Payments.deletePaymentsByClient({
            clientId: data.clientId,
        });

        await this.clients.clientList.deleteOne({ _id: data.clientId });
    }

    public async deleteClientsByCompany(data: DelteClientsByCompanyParams): Promise<void> {
        await this.clients.clientList.deleteMany({ companyId: data.companyId });
    }

    public async clientMerge(data: ClientMergeParams): Promise<void> {
        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

        if (!sessionUser) {
            throw new Error(
                "No se ha encontrado el usuario o no tiene permisos para realizar esta acción"
            );
        }

        const clientFrom = await this.clients.findOne({
            _id: data.fromClientId,
        });

        if (!clientFrom) {
            throw new Error("El cliente origen no existe");
        }

        const clientTo = await this.clients.findOne({ _id: data.toClientId });

        if (!clientTo) {
            throw new Error("El cliente destino no existe");
        }

        if (String(clientFrom.companyId) != String(clientTo.companyId)) {
            throw new Error("Los clientes deben pertenecer a la misma organización");
        }

        const companyId = clientFrom.companyId;

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

        if (!companyCheck) {
            throw new Error("Ha ocurrido un error al determinar la organización");
        }

        if (
            !(await EmployeesList.checkPermission(
                companyCheck,
                data.sessionUser,
                EmployeeRoles.ADMIN
            ))
        ) {
            throw new Error(NoPermissionMessage());
        }

        if (clientFrom.userId) {
            throw new Error(
                "No se puede realizar la fusión con un cliente que posea un usuario asociado"
            );
        }

        //Vamos a actualizar todas las colecciones que apuntes al cliente from y asignamos el cliente to.
        //Appointments
        const clientFromAppointments = await AppointmentList.Appointments.find({
            clientId: data.fromClientId,
        });

        for (const item of clientFromAppointments) {
            const appointment = await AppointmentList.Appointments.findOne({
                _id: item.id,
            });

            if (appointment) {
                appointment.clientId = data.toClientId;
                appointment.userId = clientTo.userId;
                await appointment.save();
            }
        }

        //Copiar datos del cliente que esten vacios en el destino y tengan informacion en el origen.

        if (!clientTo.firstName && clientFrom.firstName) {
            clientTo.firstName = clientFrom.firstName;
        }

        if (!clientTo.lastName && clientFrom.lastName) {
            clientTo.lastName = clientFrom.lastName;
        }

        if (!clientTo.phoneCountryCode && clientFrom.phoneCountryCode) {
            clientTo.phoneCountryCode = clientFrom.phoneCountryCode;
        }

        if (!clientTo.phoneAreaCode && clientFrom.phoneAreaCode) {
            clientTo.phoneAreaCode = clientFrom.phoneAreaCode;
        }

        if (!clientTo.phoneNumber && clientFrom.phoneNumber) {
            clientTo.phoneNumber = clientFrom.phoneNumber;
        }

        await clientTo.save();

        //Eliminar el clientFrom
        await this.clients.delete(data.fromClientId);
    }

    public async createClientByUser(data: CreateClientByUserParams): Promise<IClientDocument> {
        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

        if (!sessionUser) {
            throw new Error(
                "No se ha encontrado el usuario o no tiene permisos para realizar esta acción"
            );
        }

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

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

        const userCheck = await UsersManager.users.findOne({
            _id: data.userId,
        });

        if (!userCheck) {
            throw new Error("El usuario no existe");
        }

        const clientCheck = await this.clients.findOne({
            companyId: data.companyId,
            userId: data.userId,
        });

        if (clientCheck) {
            throw new Error("El cliente ya pertenece a la compañia");
        }

        const canAdd = await MetricsList.canAddClient(companyCheck.ownerId);

        if (!canAdd) {
            throw new Error(
                "La organización ha alcanzado el limite de clientes permitidos de acuerdo a su plan."
            );
        }

        const newClient = await this.clients.create({
            ...data,
            ...{
                email: userCheck.email,
                firstName: userCheck.firstName,
                lastName: userCheck.lastName,
                phoneAreaCode: userCheck.phoneAreaCode,
                phoneCountryCode: userCheck.phoneCountryCode,
                phoneNumber: userCheck.phoneNumber,
            },
        });

        await MetricsList.addClient({
            userId: String(companyCheck.ownerId),
            quantity: 1,
        });

        await NotificationsManager.sendSystemNotification({
                userId: String(userCheck._id),
                subject: "Ha sido dado de alta como cliente",
                message: `Su usuario ha sido dado de alta como cliente en la organización ${companyCheck.name}`,
                type: NotificationType.BILLING
            });

        return newClient;
    }

    public async createCustomClient(data: CreateCustomClientParams): Promise<IClientDocument> {
        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

        if (!sessionUser) {
            throw new Error(
                "No se ha encontrado el usuario o no tiene permisos para realizar esta acción"
            );
        }

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

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

        if (
            !(await EmployeesList.checkPermission(
                companyCheck,
                data.sessionUser,
                EmployeeRoles.ADMIN
            ))
        ) {
            throw new Error(NoPermissionMessage());
        }

        const checkClient = await this.clients.findOne({
            email: data.email,
            companyId: data.companyId,
        });

        if (checkClient) {
            throw new Error("El cliente ya pertenece a la organización");
        }

        const canAdd = await MetricsList.canAddClient(companyCheck.ownerId);

        if (!canAdd) {
            throw new Error(
                "La organización ha alcanzado el limite de clientes permitidos de acuerdo a su plan."
            );
        }

        const newClient = await this.clients.create(data);

        await MetricsList.addClient({
            userId: companyCheck.ownerId,
            quantity: 1,
        });

        return newClient;
    }

    public async updateClient(data: UpdateClientParams): Promise<void> {
        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

        if (!sessionUser) {
            throw new Error(
                "No se ha encontrado el usuario o no tiene permisos para realizar esta acción"
            );
        }

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

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

        if (
            !(await EmployeesList.checkPermission(
                companyCheck,
                data.sessionUser,
                EmployeeRoles.ADMIN
            ))
        ) {
            throw new Error(NoPermissionMessage());
        }

        const clientToUpdate = await this.clients.findOne({ _id: data.id });

        if (!clientToUpdate) {
            throw new Error("El cliente no existe");
        }

        if (data.email) {
            clientToUpdate.email = data.email;
        }

        if (data.banned !== undefined) {
            clientToUpdate.banned = data.banned;
        }

        if (data.firstName) {
            clientToUpdate.firstName = data.firstName;
        }

        if (data.lastName) {
            clientToUpdate.lastName = data.lastName;
        }

        if (data.phoneAreaCode) {
            clientToUpdate.phoneAreaCode = data.phoneAreaCode;
        }

        if (data.phoneNumber) {
            clientToUpdate.phoneNumber = data.phoneNumber;
        }

        if (data.phoneCountryCode) {
            clientToUpdate.phoneCountryCode = data.phoneCountryCode;
        }

        await clientToUpdate.save();
    }

    public async findOrganizationClients(
        filters: PaginateOrganizationClientsParams
    ): Promise<OrganizationClientsResults> {
        const clients = await this.clients.paginate(filters);
        const formattedClients: OrganizationClientView[] = [];

        for (const client of clients.data) {
            let userInfo: IUser | null = null;

            if (client.userId) {
                userInfo = await UsersManager.users.findOne({
                    _id: client.userId,
                });
            }

            const userFirstName = userInfo ? userInfo.firstName : client.firstName;
            const userLastName = userInfo ? userInfo.lastName : client.lastName;
            const userFullName = userInfo
                ? joinStrings([userInfo.firstName, userInfo.lastName], " ")
                : joinStrings([client.firstName, client.lastName], " ");
            const userPhoneNumber = userInfo ? userInfo.phoneNumber : client.phoneNumber;
            const userPhoneCountryCode = userInfo
                ? userInfo.phoneCountryCode
                : client.phoneCountryCode;
            const userPhoneAreaCode = userInfo ? userInfo.phoneAreaCode : client.phoneAreaCode;
            const userEmail = userInfo ? userInfo.email : client.email;
            const userAvatar = userInfo
                ? getAvatar(client.userId, userInfo.avatar, userFullName)
                : userFullName;

            const clientFirstName = client.firstName;
            const clientLastName = client.lastName;
            const clientFullName = joinStrings([client.firstName, client.lastName], " ");
            const clientPhoneNumber = client.phoneNumber;
            const clientPhoneCountryCode = client.phoneCountryCode;
            const clientPhoneAreaCode = client.phoneAreaCode;

            formattedClients.push({
                id: isNull<string>(client.id, ""),
                firstName: isNull<string>(clientFirstName, isNull<string>(userFirstName, "")),
                lastName: isNull<string>(clientLastName, isNull<string>(userLastName, "")),
                fullName: isNull<string>(clientFullName, isNull<string>(userFullName, "")),
                phoneNumber: isNull<string>(clientPhoneNumber, isNull<string>(userPhoneNumber, "")),
                phoneCountryCode: isNull<string>(
                    clientPhoneCountryCode,
                    isNull<string>(userPhoneCountryCode, "")
                ),
                phoneAreaCode: isNull<string>(
                    clientPhoneAreaCode,
                    isNull<string>(userPhoneAreaCode, "")
                ),
                email: userEmail,
                avatar: userAvatar,
                banned: client.banned || false,
            });
        }

        return {
            data: formattedClients,
            page: clients.page,
            pages: clients.pages,
        };
    }

    public async textObjectFilter(data: TextObjectFilterParams): Promise<TextObjectFilterResult[]> {
        const clients = await this.clients.find(data);
        const formattedResult: TextObjectFilterResult[] = [];

        for (const client of clients) {
            let userInfo: IUser | null = null;

            if (client.userId) {
                userInfo = await UsersManager.users.findOne({
                    _id: client.userId,
                });
            }

            const userFullName = userInfo
                ? joinStrings([userInfo.firstName, userInfo.lastName], " ")
                : joinStrings([client.firstName, client.lastName], " ");

            const userAvatar = userInfo
                ? getAvatar(client.userId, userInfo.avatar, userFullName)
                : userFullName;

            formattedResult.push({
                id: isNull<string>(client.id, ""),
                name: joinStrings([client.firstName, client.lastName], " "),
                description: client.email,
                image: userAvatar,
            });
        }

        return formattedResult;
    }

    public async findClientById(data: FindClientByIdParams): Promise<OrganizationClientView> {
        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

        if (!sessionUser) {
            throw new Error(
                "No se ha encontrado el usuario o no tiene permisos para realizar esta acción"
            );
        }

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

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

        if (
            !(await EmployeesList.checkPermission(
                companyCheck,
                data.sessionUser,
                EmployeeRoles.EMPLOYEE
            ))
        ) {
            throw new Error(NoPermissionMessage());
        }

        const client = await this.clients.findOne({ _id: data.id });

        if (!client) {
            throw new Error("El cliente no existe");
        }

        let userInfo: IUser | null = null;

        if (client.userId) {
            userInfo = await UsersManager.users.findOne({
                _id: String(client.userId),
            });
        }

        const userFirstName = userInfo ? userInfo.firstName : client.firstName;
        const userLastName = userInfo ? userInfo.lastName : client.lastName;
        const userFullName = userInfo
            ? joinStrings([userInfo.firstName, userInfo.lastName], " ")
            : joinStrings([client.firstName, client.lastName], " ");
        const userPhoneNumber = userInfo ? userInfo.phoneNumber : client.phoneNumber;
        const userPhoneCountryCode = userInfo ? userInfo.phoneCountryCode : client.phoneCountryCode;
        const userPhoneAreaCode = userInfo ? userInfo.phoneAreaCode : client.phoneAreaCode;
        const userEmail = userInfo ? userInfo.email : client.email;
        const userAvatar = userInfo
            ? getAvatar(client.userId, userInfo.avatar, userFullName)
            : userFullName;

        const clientFirstName = client.firstName;
        const clientLastName = client.lastName;
        const clientFullName = joinStrings([client.firstName, client.lastName], " ");
        const clientPhoneNumber = client.phoneNumber;
        const clientPhoneCountryCode = client.phoneCountryCode;
        const clientPhoneAreaCode = client.phoneAreaCode;

        return {
            id: isNull<string>(client.id, ""),
            firstName: isNull<string>(clientFirstName, isNull<string>(userFirstName, "")),
            lastName: isNull<string>(clientLastName, isNull<string>(userLastName, "")),
            fullName: isNull<string>(clientFullName, isNull<string>(userFullName, "")),
            phoneNumber: isNull<string>(clientPhoneNumber, isNull<string>(userPhoneNumber, "")),
            phoneCountryCode: isNull<string>(
                clientPhoneCountryCode,
                isNull<string>(userPhoneCountryCode, "")
            ),
            phoneAreaCode: isNull<string>(
                clientPhoneAreaCode,
                isNull<string>(userPhoneAreaCode, "")
            ),
            email: userEmail,
            avatar: userAvatar,
            banned: client.banned || false,
        };
    }

    public async getClientWapNumber(data: IClient): Promise<string> {
        const userClient = await UsersManager.users.findOne({
            _id: data.userId,
        });

        const countryCode = data.phoneCountryCode ? data.phoneCountryCode.trim() : "549";
        const areaCode = data.phoneAreaCode
            ? data.phoneAreaCode.trim()
            : isNull<string>(userClient?.phoneAreaCode, "");
        const phoneNumber = data.phoneNumber
            ? data.phoneNumber.trim()
            : isNull<string>(userClient?.phoneNumber, "");

        if (!countryCode) {
            throw new Error("No se ha especificado el código de país.");
        }

        if (!areaCode) {
            throw new Error("No se ha especificado el código de área.");
        }

        if (!phoneNumber) {
            throw new Error("No se ha especificado el número de teléfono.");
        }

        //El 9 especifica que se trata de un numero de celular.
        //return `${countryCode}9${areaCode}${phoneNumber}@c.us`;
        //Quite el "9" porque ya viene con el codigo de pais.
        //Tambien quite el @c.us ya que daba error.
        return `${countryCode}${areaCode}${phoneNumber}`;
    }

    public getClientFullName(data: IClient): string {
        if (!data.firstName && !data.lastName) {
            throw new Error("No se ha especificado el nombre del cliente.");
        }

        const firstName = data.firstName ? data.firstName.trim() : "";
        const lastName = data.lastName ? data.lastName.trim() : "";

        return joinStrings([firstName, lastName], " ");
    }
}

const ClientsList = new ClientManager();

export default ClientsList;
