import { EmployeesServicesAdapterMongoose } from "./EmployeesServices.Adapter.Mongoose";
import {
    CreateEmployeeServiceParams,
    UpdateEmployeeServiceParams,
    DeleteEmployeeServiceParams,
    FindEmployeesServicesParams,
    IEmployeeService,
    IEmployeesServicesManager,
    PaginateEmployeesServicesParams,
    PaginateEmployeesServicesResults,
    CollaboratorServiceView,
    FindCollaboratorServicesParams,
    CollaboratorServiceItems,
    UpdateCollaboratorServicesParams,
    FindEmployeesByServiceParams,
    DeleteEmployeeServiceByCompanyParams,
    DeleteEmployeeServiceByEmployeeParams,
} from "./EmployeesServices.Interface";
import { EmployeeRoles } from "../Employees/Employees.Interface";
import UsersManager from "../Users/Users";
import CompaniesManager from "../Companies/Companies";
import EmployeesList from "../Employees/Employee";
import ServicesManager from "../Services/Service";
import { NoPermissionMessage } from "../../helpers/NoPermissionMessage";
import { isNull } from "../../helpers/IsNull";
import { joinStrings } from "../../helpers/String";
import getAvatar from "../../helpers/getAvatar";
import { TextObjectFilterResult } from "../../Models/TextObjectFilter.model";
import { NotificationsManager } from "../Notifications/Notifications";
import { NotificationType } from "../../Models/SystemNotifications/SystemNotification.Interface";

class EmployeesServicesManager implements IEmployeesServicesManager {
    employeesServices: EmployeesServicesAdapterMongoose;

    constructor() {
        this.employeesServices = new EmployeesServicesAdapterMongoose();
    }

    public async deleteEmployeeServiceByEmployee(
        data: DeleteEmployeeServiceByEmployeeParams
    ): Promise<void> {
        await this.employeesServices.employeesServicesList.deleteMany({
            employeeId: data.employeeId,
        });
    }

    public async deleteEmployeeServiceByCompany(
        data: DeleteEmployeeServiceByCompanyParams
    ): Promise<void> {
        await this.employeesServices.employeesServicesList.deleteMany({
            companyId: data.companyId,
        });
    }

    public async createEmployeeService(
        data: CreateEmployeeServiceParams
    ): Promise<IEmployeeService> {
        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 serviceCheck = await ServicesManager.services.findOne({
            _id: data.serviceId,
            companyId: data.companyId,
        });

        if (!serviceCheck) {
            throw new Error("El servicio no existe o no pertenece a la compañia.");
        }

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

        const employeeCheck = await EmployeesList.employees.findOne({
            companyId: data.companyId,
            _id: data.employeeId,
        });

        if (!employeeCheck) {
            throw new Error("El empleado no existe o no pertenece a la compañia.");
        }

        const employeeServiceCheck = await this.employeesServices.findOne({
            employeeId: data.employeeId,
            serviceId: data.serviceId,
            companyId: data.companyId,
        });

        if (employeeServiceCheck) {
            throw new Error("El servicio ya fue asignado para este empleado");
        }

        const newEmplyeeService = await this.employeesServices.create(data);

        await NotificationsManager.sendSystemNotification({
                userId: String(employeeCheck.userId),
                subject: "Servicio asignado",
                message: `Su usuario ha sido asignado para brindar el servicio ${serviceCheck.name} para la organización ${companyCheck.name}.`,
                type: NotificationType.EMPLOYEE,
                code: String(employeeCheck._id)
            });

        return newEmplyeeService;
    }

    public async updateEmployeeService(data: UpdateEmployeeServiceParams): 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");
        }

        const serviceCheck = await ServicesManager.services.findOne({
            _id: data.serviceId,
            companyId: data.companyId,
        });

        if (!serviceCheck) {
            throw new Error("El servicio no existe o no pertenece a la compañia.");
        }

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

        const employeeCheck = await EmployeesList.employees.findOne({
            companyId: data.companyId,
            _id: data.employeeId,
        });

        if (!employeeCheck) {
            throw new Error("El empleado no existe o no pertenece a la compañia.");
        }

        await this.employeesServices.update(data);
    }

    public async updateCollaboratorServices(data: UpdateCollaboratorServicesParams): 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 employeeCheck = await EmployeesList.employees.findOne({
            companyId: data.companyId,
            _id: data.employeeId,
        });

        if (!employeeCheck) {
            throw new Error("El empleado no existe o no pertenece a la compañia.");
        }

        for (const service of data.services) {
            const serviceCheck = await ServicesManager.services.findOne({
                _id: service.serviceId,
                companyId: data.companyId,
            });

            if (!serviceCheck) {
                continue;
            }

            try {
                if (!service.active) {
                    //Aca no envio notificacion porque ya lo hace internamente createEmployeeService
                    await this.delete({
                        employeeId: data.employeeId,
                        serviceId: service.serviceId,
                        companyId: data.companyId,
                        sessionUser: data.sessionUser,
                    });
                }
            } catch (error) {
                continue;
            }
            try {
                if (service.active) {
                    //Aca no envio notificacion porque ya lo hace internamente createEmployeeService
                    await this.createEmployeeService({
                        employeeId: data.employeeId,
                        serviceId: service.serviceId,
                        companyId: data.companyId,
                        sessionUser: data.sessionUser,
                    });
                }
            } catch (error) {
                continue;
            }
        }
    }

    public async paginate(
        filters: PaginateEmployeesServicesParams
    ): Promise<PaginateEmployeesServicesResults> {
        return await this.employeesServices.paginate(filters);
    }

    public async findAllServicesByCollaborator(
        filters: FindCollaboratorServicesParams
    ): Promise<CollaboratorServiceView> {
        const employeeCheck = await EmployeesList.employees.findOne({
            _id: filters.employeeId,
        });

        if (!employeeCheck) {
            throw new Error("El empleado no existe o no pertenece a la compañia.");
        }

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

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

        const services = await ServicesManager.services.find({ companyId: filters.companyId });
        const returnServices: CollaboratorServiceItems[] = [];

        for (const service of services) {
            const hasService = await this.employeesServices.findOne({
                employeeId: filters.employeeId,
                serviceId: String(service.id),
                companyId: filters.companyId,
            });

            returnServices.push({
                id: isNull<string>(service.id, ""),
                name: isNull<string>(service.name, ""),
                description: isNull<string>(service.description, ""),
                length: isNull<number>(service.length, 0),
                price: isNull<number>(service.price, 0),
                limit: isNull<number>(service.limit, 1),
                active: hasService ? true : false,
            });
        }

        const fullName = joinStrings([userCheck.firstName, userCheck.lastName], " ");

        return {
            companyId: filters.companyId,
            employeeId: filters.employeeId,
            services: returnServices,
            fullName: fullName,
            avatar: getAvatar(userCheck.id, userCheck.avatar, fullName),
        };
    }

    public async find(filters: FindEmployeesServicesParams): Promise<IEmployeeService[]> {
        return await this.employeesServices.find(filters);
    }

    public async delete(data: DeleteEmployeeServiceParams): 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 employeeCheck = await EmployeesList.employees.findOne({
            companyId: data.companyId,
            _id: data.employeeId,
        });
        if (!employeeCheck) {
            throw new Error("El empleado no existe o no pertenece a la compañia.");
        }

        const serviceCheck = await ServicesManager.services.findOne({
            _id: data.serviceId,
            companyId: data.companyId,
        });

        if (!serviceCheck) {
            throw new Error("El servicio no existe o no pertenece a la compañia.");
        }

        const deleteItem = await this.employeesServices.findOne({
            companyId: data.companyId,
            employeeId: data.employeeId,
            serviceId: data.serviceId,
        });

        if (!deleteItem) {
            throw new Error("Ha ocurrido un error al quitar el servicio");
        }

        await this.employeesServices.delete(String(deleteItem._id));

        await NotificationsManager.sendSystemNotification({
                userId: String(employeeCheck.userId),
                subject: "Servicio quitado",
                message: `E servicio ${serviceCheck.name} de la organización ${companyCheck.name} ya no se encuentra asignado a su usuario.`,
                type: NotificationType.EMPLOYEE,
                code: String(employeeCheck._id)
            });
    }

    public async textObjectFilter(
        data: FindEmployeesServicesParams
    ): Promise<TextObjectFilterResult[]> {
        const employeeCheck = await EmployeesList.employees.findOne({
            _id: data.employeeId,
        });

        if (!employeeCheck) {
            throw new Error("El empleado no existe o no pertenece a la compañia.");
        }

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

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

        const services = await this.employeesServices.find({
            employeeId: data.employeeId,
            companyId: data.companyId,
        });
        const returnServices: TextObjectFilterResult[] = [];

        for (const service of services) {
            const serviceData = await ServicesManager.services.findOne({
                _id: String(service.serviceId),
            });

            if (!serviceData) {
                continue;
            }

            returnServices.push({
                id: isNull<string>(service.serviceId, ""),
                name: isNull<string>(serviceData.name, ""),
                description: isNull<string>(serviceData.description, ""),
            });
        }

        return returnServices;
    }

    public async findByService(
        data: FindEmployeesByServiceParams
    ): Promise<TextObjectFilterResult[]> {
        const returnData: TextObjectFilterResult[] = [];
        const servicesByEmployee = await this.employeesServices.find({
            serviceId: data.serviceId,
        });

        for (const service of servicesByEmployee) {
            const employeeData = await EmployeesList.findById({ id: service.employeeId });

            returnData.push({
                id: isNull<string>(service.employeeId, ""),
                name: isNull<string>(employeeData.fullName, ""),
                image: isNull<string>(employeeData.avatar, ""),
            });
        }

        return returnData;
    }
}

const EmployeesServicesList = new EmployeesServicesManager();

export default EmployeesServicesList;
