import fs from "fs";
import path from "path";
import { rm } from "fs/promises";
import { CompaniesAdapterMongoose } from "./Companies.Adapter.Mongoose";
import {
    CreateCompanyParams,
    FindCompaniesParams,
    UpdateCompanyParams,
    ICompaniesAdapter,
    ICompaniesManager,
    ICompany,
    PaginateCompaniesParams,
    PaginateCompaniesResults,
    SetCompanyFileParams,
    DeleteCompanyFileParams,
    MyOranizationsView,
    MyOranizationsViewParams,
    OranizationsViewByIdParams,
    ClientOranizationsViewParams,
    ClientOrganizationView,
    DeleteCompanyParams,
    SetOrganizationPublishedStatusParams,
    COMPANY_PUBLISHED_STATUS,
    SetNotificationAutoParams,
    FixCompanyPostParams,
    MoveCompanyFixedPostParams,
    SysAdminPaginateCompaniesParams,
    SysAdminUpdateCompanyParams,
    SysAdminSetCompanyBannedParams,
} from "./Companies.Interface";
import UsersManager from "../Users/Users";
import EmployeesList from "../Employees/Employee";
import { EmployeeRoles } from "../Employees/Employees.Interface";
import { NoPermissionMessage } from "../../helpers/NoPermissionMessage";
import { isNull } from "../../helpers/IsNull";
import sharp from "sharp";
import getHeader from "../../helpers/getHeader";
import MetricsList from "../../Models/Metrics/Metrics";
import { TextObjectFilterResult } from "../TextObjectFilter.model";
import { NotificationsManager } from "../Notifications/Notifications";
import ClientsList from "../Clients/Clients";
import { validateSessionUser, validatePermissionsByCompany } from "../../helpers/check";
import { resizeAndCrop } from "../../helpers/resizeAndCrop";
import HeatMapList from "../HeatMap/HeatMap";
import AppointmentList from "../Appointments/Appointments";
import EmployeesServicesList from "../EmployeesServices/EmployeesServices";
import SchedulesEnabledList from "../SchedulesEnabled/SchedulesEnabled";
import SchedulesList from "../Schedules/Schedules";
import ServiceList from "../Services/Service";
import MessageConversationList from "../MessageConversations/MessagesConversations";
import WapServerList from "../WapServer/WapServer";
import RepeatsList from "../Repeats/Repeats";
import ClientAccount from "../ClientAccounts/ClientAccount";
import Payments from "../Payments/Payments";
import Discounts from "../Discounts/Discounts";
import Templates from "../Templates/Templates";

import Posts from "../Posts/Posts";
import SchedulesDisabledList from "../SchedulesDisabled/SchedulesDisabled";
import DiscountsEmail from "../DiscountsEmail/DiscountsEmail";
import { NotificationType } from "../../Models/SystemNotifications/SystemNotification.Interface";

class CompaniesManager implements ICompaniesManager {
    companies: ICompaniesAdapter;

    constructor() {
        this.companies = new CompaniesAdapterMongoose();
    }

    public async toggleFixedPost(data: FixCompanyPostParams): Promise<void> {
        await validateSessionUser({
            sessionUser: data.sessionUser,
        });

        const { company } = await validatePermissionsByCompany({
            companyId: data.companyId,
            sessionUser: data.sessionUser,
        });

        const checkPost = await Posts.posts.findOne({
            id: data.postId,
        });

        if (!checkPost) {
            throw new Error("El post que intenta actualizar no existe");
        }

        checkPost.fixed = !checkPost.fixed;

        await checkPost.save();

        if (checkPost.fixed) {
            company.fixedPostIds?.push(checkPost.id);
        } else {
            company.fixedPostIds = company.fixedPostIds?.filter(
                (item) => String(item) !== String(checkPost.id)
            );
        }

        await company.save();
    }

    public async moveFixedPost(data: MoveCompanyFixedPostParams): Promise<void> {
        await validateSessionUser({
            sessionUser: data.sessionUser,
        });

        const { company } = await validatePermissionsByCompany({
            companyId: data.companyId,
            sessionUser: data.sessionUser,
        });

        const checkPost = await Posts.posts.findOne({
            id: data.postId,
        });

        if (!checkPost) {
            throw new Error("El post que intenta actualizar no existe");
        }

        if (!checkPost.fixed) {
            throw new Error("El post no es fijo");
        }

        const currentPosition = company.fixedPostIds?.findIndex(
            (item) => String(item) === String(data.postId)
        );

        if (currentPosition === undefined || currentPosition < 0) {
            throw new Error("No es posible mover el post");
        }

        company.fixedPostIds = company.fixedPostIds?.filter((item) => String(item) !== String(data.postId));

        company.fixedPostIds?.splice(currentPosition + data.moveLength, 0, data.postId);

        await company.save();
    }

    public async setNotificationAuto(data: SetNotificationAutoParams): 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 this.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());
        }

        companyCheck.automaticNotifications = isNull<boolean>(data.status, false);
        await companyCheck.save();
    }

    public async setPublishedStatus(data: SetOrganizationPublishedStatusParams): 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 this.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());
        }

        companyCheck.published = data.published;
        await companyCheck.save();
    }

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

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

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

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

        if (String(companyCheck.ownerId) !== data.sessionUser) {
            throw new Error(
                "No tienes permisos para realizar esta acción. Solo el dueño puede eliminar la organización."
            );
        }

        try {
            //eliminar heatmaps
            await HeatMapList.deleteHeatMapByCompany({
                companyId: data.companyId,
            });
            //eliminar appointments
            await AppointmentList.deleteAppointmentsByCompany({
                companyId: data.companyId,
            });
            //eliminar los employees services
            await EmployeesServicesList.deleteEmployeeServiceByCompany({
                companyId: data.companyId,
            });
            //eliminar schedules enabled.
            await SchedulesEnabledList.deleteSchedulesEnabledByCompany({
                companyId: data.companyId,
            });

            //eliminar schedules disabled.
            await SchedulesDisabledList.deleteSchedulesDisabledByCompany({
                companyId: data.companyId,
            });

            //eliminar scheduyles.
            await SchedulesList.deleteSchedulesByCompany({
                companyId: data.companyId,
            });
            //eliminar employees
            await EmployeesList.deleteEmployeesByCompany({
                companyId: data.companyId,
            });
            //eliminar metrics
            await MetricsList.deleteMetricsByCompany({
                companyId: data.companyId,
            });
            //eliminar services
            await ServiceList.deleteServicesByCompany({
                companyId: data.companyId,
            });
            //eliminar cuenta corriente de los clientes
            await ClientAccount.deleteClientsAccountsByCompany({
                companyId: data.companyId,
            });
            //eliminar pagos
            await Payments.deletePaymentsByCompany({
                companyId: data.companyId,
            });
            //eliminar clients
            await ClientsList.deleteClientsByCompany({
                companyId: data.companyId,
            });
            //eliminar conversations
            await MessageConversationList.deleteConversationsByCompany({
                companyId: data.companyId,
            });

            //eliminar las repeticiones
            await RepeatsList.deleteRepeatsByCompany({
                companyId: data.companyId,
            });

            //eliminar los templates
            await Templates.deleteTemplatesByCompany({
                companyId: data.companyId,
            });

            try {
                //eliminar wap server asignations
                const companyServer = companyCheck.wapServerId;

                if (companyServer) {
                    await WapServerList.deleteBot({
                        companyId: data.companyId,
                        sessionUser: data.sessionUser,
                    });

                    const server = await WapServerList.servers.findOne({
                        id: companyServer,
                    });

                    if (server) {
                        server.countBots -= 1;
                        await server.save();
                    }
                }
            } catch (e) {
                console.log(e);
            }

            //eliminar los descuentos por email
            await DiscountsEmail.deleteDiscountsEmailByCompany({
                companyId: data.companyId,
            });

            //eliminar descuentos
            await Discounts.deleteDiscountsByCompany({
                companyId: data.companyId,
            });

            const companyName = `${companyCheck.name}`;

            //eliminar imagenes de la company
            await this.deleteCompanyFiles(data.companyId);
            //eliminar company
            await this.companies.delete(data.companyId);

            await NotificationsManager.sendSystemNotification({
                subject: "Organización eliminada",
                message: `La organización ${companyName} ha sido eliminada.`,
                userId: data.sessionUser,
            });
        } catch (e) {
            console.log(e);
        }
    }

    private async deleteCompanyFiles(companyId: string): Promise<void> {
        const rootDir =
            process.env.NODE_ENV !== "production"
                ? process.env.UPLOAD_COMPANY_FILES_ROOT_DEV
                : process.env.UPLOAD_COMPANY_FILES_ROOT_PRO;

        if (!rootDir) {
            throw new Error("No se ha encontrado la ruta de los archivos");
        }

        const companyDir = path.join(__dirname, rootDir, companyId);

        try {
            await rm(companyDir, { recursive: true, force: true });
        } catch (err) {
            console.error(`Error al eliminar el directorio ${companyDir}:`, err);
        }
    }

    public getSlug(name: string): string {
        return name
            .replaceAll(".", "")
            .replaceAll(",", "")
            .replaceAll(":", "")
            .replaceAll(";", "")
            .replaceAll("!", "")
            .replaceAll("<", "")
            .replaceAll(">", "")
            .replaceAll("'", "")
            .replaceAll('"', "")
            .replaceAll("#", "")
            .replaceAll("$", "")
            .replaceAll("%", "")
            .replaceAll("&", "")
            .replaceAll("/", "")
            .replaceAll("(", "")
            .replaceAll(")", "")
            .replaceAll("=", "")
            .replaceAll("¿", "")
            .replaceAll("?", "")
            .replaceAll("*", "")
            .replaceAll("+", "")
            .replaceAll("-", "")
            .replaceAll("|", "")
            .replaceAll("{", "")
            .replaceAll("}", "")
            .replaceAll("[", "")
            .replaceAll("]", "")
            .split(" ")
            .filter((item) => item.length > 0)
            .reduce((acc, item) => acc + "-" + item.toLowerCase(), "")
            .substring(1);
    }

    public async createCompany(data: CreateCompanyParams): Promise<ICompany> {
        const owner = UsersManager.users.findOne({ _id: data.ownerId });

        if (!owner) throw new Error("El propietario de la empresa no es un usuario registrado.");

        const checkCompany = await this.isValidName(data);

        if (!checkCompany) {
            throw new Error("El nombre de la organización no es válido. Por favor, pruebe con otro nombre.");
        }

        await MetricsList.calculateMetrics({ userId: data.ownerId });
        const canAdd = await MetricsList.canAddOrganization(data.ownerId);

        if (!canAdd) {
            throw new Error("Ha alcanzado el limite de organizaciones permitidas de acuerdo a su plan.");
        }

        const invalidNames = [
            "admin",
            "components",
            "fonts",
            "helpers",
            "hooks",
            "landing",
            "messenger",
            "theme",
            "user",
        ];

        if (invalidNames.includes(data.name.toLowerCase())) {
            throw new Error("El nombre de la organización no es válido. Por favor, pruebe con otro nombre.");
        }

        const newCompany = await this.companies.create({
            ...data,
            ...{
                slug: this.getSlug(data.name),
                name: data.name.toLocaleLowerCase(),
            },
        });

        if (!newCompany.id) {
            throw new Error("Ha ocurrido un error al crear la compañia.");
        }

        await NotificationsManager.sendSystemNotification({
                userId: String(data.ownerId),
                subject: "Se ha creado una nueva compañia",
                message: `Se ha creado la compañia ${data.name} exitosamente.`,
                type: NotificationType.COMPANY,
                code: String((newCompany as any)._id)
            });

        await MetricsList.addOrganization({
            userId: data.ownerId,
            quantity: 1,
        });

        const newEmplyee = await EmployeesList.createEmployee({
            companyId: newCompany.id,
            userId: String(newCompany.ownerId),
            roles: [EmployeeRoles.OWNER, EmployeeRoles.ADMIN],
            sessionUser: String(newCompany.ownerId),
        });

        //Valida el empleado como aceptado por ambas partes. Solo paso employeeId ya que
        //un employee solo pertenece a una empresa.
        await EmployeesList.validateFull({
            employeeId: String(newEmplyee.id),
            sessionUser: String(newCompany.ownerId),
        });

        await NotificationsManager.sendSystemNotification({
                userId: String(data.ownerId),
                subject: "Administrador asignado",
                message: `Su usuario ha sido asignado como administrador de la compañia ${data.name}.`,
                type: NotificationType.COMPANY,
                code: String((newCompany as any)._id)
            });

        return newCompany;
    }

    public async updateCompany(data: UpdateCompanyParams): Promise<void> {
        await validateSessionUser({ sessionUser: data.sessionUser });
        await validatePermissionsByCompany({ companyId: data.id, sessionUser: data.sessionUser });

        const companyCheck = await this.companies.findOne({ _id: data.id });
        
        if (!companyCheck) {
            throw new Error("La compañia no existe");
        }

        if (data.name) {
            const otherCompany = await this.companies.findOne({
                name: data.name,
            });
            if (otherCompany && otherCompany.id !== data.id) {
                throw new Error(
                    "El nuevo nombre de la organización no es válido. Por favor, pruebe con otro nombree ya que su elección actual esta en uso"
                );
            }

            data.name = data.name.toLocaleLowerCase();
        }

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

        let updateData = {
            ...data,
        };

        if (data.name) {
            updateData = {
                ...updateData,
                ...{ name: data.name.toLocaleLowerCase() },
                ...{ slug: this.getSlug(data.name) },
            };
        }

        await this.companies.update(updateData);
    }

    public async isValidName(data: FindCompaniesParams): Promise<boolean> {
        if (!data.name) return false;
        const checkSlug = this.getSlug(data.name);
        const checkCompany = await this.companies.findOne({ slug: checkSlug });

        if (checkCompany) {
            return false;
        }

        return true;
    }

    public async getById(data: OranizationsViewByIdParams): Promise<MyOranizationsView> {
        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 company = await this.companies.findOne({ _id: data.id });

        //TODO: chequear si es el dueño. y si esta aceptado

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

        const employ = await EmployeesList.employees.findOne({
            companyId: data.id,
            userId: String(data.sessionUser),
        });

        if (!employ) {
            throw new Error("El usuario no participa de la organización");
        }

        const defColor = "#" + String(process.env.DEFAULT_HEADER_COLOR);
        const defFontColor = "#" + String(process.env.DEFAULT_HEADER_FONT_COLOR);
        const defFontShadowColor = "#" + String(process.env.DEFAULT_HEADER_FONT_SHADOW_COLOR);

        const isAdmin = await EmployeesList.checkPermission(company, data.sessionUser, EmployeeRoles.ADMIN);

        return {
            id: isNull<string>(company.id, ""),
            name: isNull<string>(company.name, ""),
            slug: isNull<string>(company.slug, ""),
            description: isNull<string>(company.description, ""),
            categoryId: isNull<number>(company.categoryId, 50000),
            ownerId: company.ownerId,
            employeeId: String(employ.id),
            isOwner: String(company.ownerId) == String(data.sessionUser) ? true : false,
            isAdmin: isAdmin,
            hostOk: employ.hostOk ? true : false,
            guestOk: employ.guestOk ? true : false,
            fullOk: employ.hostOk && employ.guestOk ? true : false,
            /*headerFile: isNull<string>(company.headerFile, ""),*/

            headerFile: getHeader(company.id, isNull<string>(company.headerFile, ""), ""),

            headerColor: isNull<string>(company.headerColor, defColor),
            headerFontColor: isNull<string>(company.headerFontColor, defFontColor),
            headerFontShadowColor: isNull<string>(company.headerFontShadowColor, defFontShadowColor),
            iconFile: isNull<string>(company.iconFile, ""),
            logoFile: isNull<string>(company.logoFile, ""),
            phoneAreaCode: isNull<string>(company.phoneAreaCode, ""),
            phoneNumber: isNull<string>(company.phoneNumber, ""),
            heatMapFraction: isNull<number>(company.heatMapFraction, 60),
            appointmentTime: isNull<number>(company.appointmentTime, 24),
            cancellationTime: isNull<number>(company.cancellationTime, 24),
            street: isNull<string>(company.street, ""),
            streetNumber: isNull<string>(company.streetNumber, ""),
            builingFloor: isNull<string>(company.builingFloor, ""),
            buildingApartament: isNull<string>(company.buildingApartament, ""),
            block: isNull<string>(company.block, ""),
            city: isNull<string>(company.city, ""),
            state: isNull<string>(company.state, ""),
            country: isNull<string>(company.country, ""),
            zipCode: isNull<string>(company.zipCode, ""),
            latitude: isNull<number>(company.latitude, 0),
            longitude: isNull<number>(company.longitude, 0),
            published: isNull<COMPANY_PUBLISHED_STATUS>(company.published, COMPANY_PUBLISHED_STATUS.PRIVATE),
            automaticNotifications: isNull<boolean>(company.automaticNotifications, false),
            appointmentAlert: isNull<string>(company.appointmentAlert, ""),

            templateWapNotifId: isNull<string>(company.templateWapNotifId, ""),
            templateWapAltaId: isNull<string>(company.templateWapAltaId, ""),
            templateEmailNotifId: isNull<string>(company.templateEmailNotifId, ""),
            templateEmailAltaId: isNull<string>(company.templateEmailAltaId, ""),
            onboardingStep: company.onboardingStep,
            onboardingCompleted: company.onboardingCompleted,
        };
    }

    public async getByClientId(data: ClientOranizationsViewParams): Promise<ClientOrganizationView[]> {
        const clientCompanies = await ClientsList.clients.find({
            userId: data.clientId,
        });

        const returnData: ClientOrganizationView[] = [];

        //Organizaciones del cliente.
        for (const orgItem of clientCompanies) {
            const company = await this.companies.findOne({
                _id: orgItem.companyId,
            });

            if (!company) {
                continue;
            }

            returnData.push({
                id: isNull<string>(company._id, ""),
                name: isNull<string>(company.name, ""),
                slug: isNull<string>(company.slug, ""),
                description: isNull<string>(company.description, ""),
                iconFile: isNull<string>(company.iconFile, ""),
                logoFile: isNull<string>(company.logoFile, ""),
            });
        }

        return returnData;
    }

    public async getByUserId(data: MyOranizationsViewParams): Promise<MyOranizationsView[]> {
        const ownerCompanies = await this.companies.find({
            ownerId: data.sessionUser,
        });

        const returnData: MyOranizationsView[] = [];

        const defColor = "#" + String(process.env.DEFAULT_HEADER_COLOR);
        const defFontColor = "#" + String(process.env.DEFAULT_HEADER_FONT_COLOR);
        const defFontShadowColor = "#" + String(process.env.DEFAULT_HEADER_FONT_SHADOW_COLOR);

        //Organizaciones del usuario
        for (const company of ownerCompanies) {
            const companyDoc = await this.companies.findOne({
                _id: company.id,
            });

            if (!companyDoc) {
                continue;
            }

            const employ = await EmployeesList.employees.findOne({
                companyId: String(company.id),
                userId: String(data.sessionUser),
            });

            if (!employ) {
                continue;
            }

            const isAdmin = await EmployeesList.checkPermission(companyDoc, employ.id, EmployeeRoles.ADMIN);

            returnData.push({
                id: isNull<string>(company.id, ""),
                name: isNull<string>(company.name, ""),
                slug: isNull<string>(company.slug, ""),
                description: isNull<string>(company.description, ""),
                categoryId: isNull<number>(company.categoryId, 50000),
                ownerId: company.ownerId,
                employeeId: employ ? String(employ.id) : "",
                isOwner: true,
                isAdmin: isAdmin,
                hostOk: true,
                guestOk: true,
                fullOk: true,
                //headerFile: isNull<string>(company.headerFile, ""),

                headerFile: getHeader(
                    isNull<string>(company.id, ""),
                    isNull<string>(company.headerFile, ""),
                    ""
                ),

                headerColor: isNull<string>(company.headerColor, defColor),
                headerFontColor: isNull<string>(company.headerFontColor, defFontColor),
                headerFontShadowColor: isNull<string>(company.headerFontShadowColor, defFontShadowColor),
                iconFile: isNull<string>(company.iconFile, ""),
                logoFile: isNull<string>(company.logoFile, ""),
                phoneAreaCode: isNull<string>(company.phoneAreaCode, ""),
                phoneNumber: isNull<string>(company.phoneNumber, ""),
                heatMapFraction: isNull<number>(company.heatMapFraction, 60),
                appointmentTime: isNull<number>(company.appointmentTime, 24),
                cancellationTime: isNull<number>(company.cancellationTime, 24),
                street: isNull<string>(company.street, ""),
                streetNumber: isNull<string>(company.streetNumber, ""),
                builingFloor: isNull<string>(company.builingFloor, ""),
                buildingApartament: isNull<string>(company.buildingApartament, ""),
                block: isNull<string>(company.block, ""),
                city: isNull<string>(company.city, ""),
                state: isNull<string>(company.state, ""),
                country: isNull<string>(company.country, ""),
                zipCode: isNull<string>(company.zipCode, ""),
                latitude: isNull<number>(company.latitude, 0),
                longitude: isNull<number>(company.longitude, 0),
                published: isNull<COMPANY_PUBLISHED_STATUS>(
                    company.published,
                    COMPANY_PUBLISHED_STATUS.PRIVATE
                ),
                automaticNotifications: isNull<boolean>(company.automaticNotifications, false),
                appointmentAlert: isNull<string>(company.appointmentAlert, ""),
                templateWapNotifId: isNull<string>(company.templateWapNotifId, ""),
                templateEmailNotifId: isNull<string>(company.templateEmailNotifId, ""),
                templateWapAltaId: isNull<string>(company.templateWapAltaId, ""),
                templateEmailAltaId: isNull<string>(company.templateEmailAltaId, ""),
                onboardingStep: company.onboardingStep,
                onboardingCompleted: company.onboardingCompleted,
            });
        }

        //Organizaciones en las que participa como colaborador.
        const orgCollaborator = await EmployeesList.employees.find({
            userId: data.sessionUser,
        });

        for (const item of orgCollaborator) {
            if (item.companyId) {
                const company = await this.companies.findOne({
                    _id: item.companyId,
                });

                if (company) {
                    if (String(company.ownerId) == String(data.sessionUser)) {
                        continue;
                    }

                    const isAdmin = await EmployeesList.checkPermission(
                        company,
                        data.sessionUser,
                        EmployeeRoles.ADMIN
                    );

                    returnData.push({
                        id: isNull<string>(company.id, ""),
                        name: isNull<string>(company.name, ""),
                        slug: isNull<string>(company.slug, ""),
                        description: isNull<string>(company.description, ""),
                        categoryId: isNull<number>(company.categoryId, 50000),
                        ownerId: company.ownerId,
                        employeeId: String(item.id),
                        isOwner: false,
                        isAdmin: isAdmin,
                        hostOk: item.hostOk ? true : false,
                        guestOk: item.guestOk ? true : false,
                        fullOk: item.hostOk && item.guestOk ? true : false,
                        headerFile: isNull<string>(company.headerFile, ""),
                        iconFile: isNull<string>(company.iconFile, ""),
                        logoFile: isNull<string>(company.logoFile, ""),
                        phoneAreaCode: isNull<string>(company.phoneAreaCode, ""),
                        phoneNumber: isNull<string>(company.phoneNumber, ""),
                        heatMapFraction: isNull<number>(company.heatMapFraction, 60),
                        appointmentTime: isNull<number>(company.appointmentTime, 24),
                        cancellationTime: isNull<number>(company.cancellationTime, 24),
                        street: isNull<string>(company.street, ""),
                        streetNumber: isNull<string>(company.streetNumber, ""),
                        builingFloor: isNull<string>(company.builingFloor, ""),
                        buildingApartament: isNull<string>(company.buildingApartament, ""),
                        block: isNull<string>(company.block, ""),
                        city: isNull<string>(company.city, ""),
                        state: isNull<string>(company.state, ""),
                        country: isNull<string>(company.country, ""),
                        zipCode: isNull<string>(company.zipCode, ""),
                        latitude: isNull<number>(company.latitude, 0),
                        longitude: isNull<number>(company.longitude, 0),
                        published: isNull<COMPANY_PUBLISHED_STATUS>(
                            company.published,
                            COMPANY_PUBLISHED_STATUS.PRIVATE
                        ),
                        automaticNotifications: isNull<boolean>(company.automaticNotifications, false),
                        appointmentAlert: isNull<string>(company.appointmentAlert, ""),
                        templateWapNotifId: isNull<string>(company.templateWapNotifId, ""),
                        templateEmailNotifId: isNull<string>(company.templateEmailNotifId, ""),
                        templateWapAltaId: isNull<string>(company.templateWapAltaId, ""),
                        templateEmailAltaId: isNull<string>(company.templateEmailAltaId, ""),
                        onboardingStep: company.onboardingStep,
                        onboardingCompleted: company.onboardingCompleted,
                    });
                }
            }
        }

        return returnData;
    }

    public async paginate(filters: PaginateCompaniesParams): Promise<PaginateCompaniesResults> {
        return await this.companies.paginate(filters);
    }

    public async setCompanyHeader(data: SetCompanyFileParams): 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 this.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());
        }

        if (!data.file) {
            throw new Error("No se ha enviado el archivo");
        }

        const rootDir =
            process.env.NODE_ENV !== "production"
                ? process.env.UPLOAD_COMPANY_FILES_ROOT_DEV
                : process.env.UPLOAD_COMPANY_FILES_ROOT_PRO;

        const uploadPath = path.join(__dirname, rootDir + data.companyId);

        if (!fs.existsSync(uploadPath)) {
            fs.mkdirSync(uploadPath, { recursive: true });
        }

        const fileExtension = data.file.originalname.split(".").pop();
        const newFileName = "header." + fileExtension;

        const filePath = path.join(uploadPath, newFileName);

        const bufferImage = await resizeAndCrop(sharp(data.file.buffer), 1138, 248);

        bufferImage.webp().toFile(filePath);

        //await sharp(data.file.buffer).resize({ height: 150 }).webp().toFile(filePath);

        companyCheck.headerFile = newFileName;
        await companyCheck.save();
    }

    public async setCompanyIcon(data: SetCompanyFileParams): 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 this.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());
        }

        if (!data.file) {
            throw new Error("No se ha enviado el archivo");
        }

        const rootDir =
            process.env.NODE_ENV !== "production"
                ? process.env.UPLOAD_COMPANY_FILES_ROOT_DEV
                : process.env.UPLOAD_COMPANY_FILES_ROOT_PRO;

        const uploadPath = path.join(__dirname, rootDir + data.companyId);

        if (!fs.existsSync(uploadPath)) {
            fs.mkdirSync(uploadPath, { recursive: true });
        }

        const fileExtension = data.file.originalname.split(".").pop();
        const newFileName = "icon." + fileExtension;

        const filePath = path.join(uploadPath, newFileName);
        const uint8Array = new Uint8Array(data.file.buffer);
        fs.writeFileSync(filePath, uint8Array);

        companyCheck.iconFile = newFileName;
        await companyCheck.save();
    }

    public async deleteCompanyHeader(data: DeleteCompanyFileParams): 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 this.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());
        }

        if (!companyCheck.headerFile) {
            throw new Error("No hay ningun archivo para borrar");
        }

        const rootDir =
            process.env.NODE_ENV !== "production"
                ? process.env.UPLOAD_COMPANY_FILES_ROOT_DEV
                : process.env.UPLOAD_COMPANY_FILES_ROOT_PRO;

        const uploadPath = path.join(__dirname, rootDir + data.companyId);

        const filePath = path.join(uploadPath, companyCheck.headerFile);

        if (!fs.existsSync(filePath)) {
            throw new Error("El archivo no existe");
        }

        fs.unlinkSync(filePath);

        companyCheck.headerFile = undefined;
        await companyCheck.save();
    }

    public async deleteCompanyIcon(data: DeleteCompanyFileParams): 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 this.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());
        }

        if (!companyCheck.iconFile) {
            throw new Error("No hay ningun archivo para borrar");
        }

        const rootDir =
            process.env.NODE_ENV !== "production"
                ? process.env.UPLOAD_COMPANY_FILES_ROOT_DEV
                : process.env.UPLOAD_COMPANY_FILES_ROOT_PRO;

        const uploadPath = path.join(__dirname, rootDir + data.companyId);

        const filePath = path.join(uploadPath, companyCheck.iconFile);

        if (!fs.existsSync(filePath)) {
            throw new Error("El archivo no existe");
        }

        fs.unlinkSync(filePath);

        companyCheck.iconFile = undefined;
        await companyCheck.save();
    }

    public async filterAll(data: PaginateCompaniesParams): Promise<TextObjectFilterResult[]> {
        const companies = await this.companies.paginateFilterAll(data);
        const returnData: TextObjectFilterResult[] = [];

        for (const company of companies.data) {
            returnData.push({
                id: isNull<string>(company.id, ""),
                name: company.name,
                description: company.description,
            });
        }

        return returnData;
    }

    public getCompanyAddress(data: ICompany): string {
        const address = `${isNull<string>(data.street, "")} ${isNull<string>(
            data.streetNumber,
            ""
        )} ${isNull<string>(data.builingFloor, "")} ${isNull<string>(
            data.buildingApartament,
            ""
        )} ${isNull<string>(data.block, "")} - ${isNull<string>(data.city, "")}, ${isNull<string>(
            data.state,
            ""
        )}, ${isNull<string>(data.country, "")} - CP: ${isNull<string>(data.zipCode, "")}`;
        return address;
    }

    public async sysAdminPaginate(data: SysAdminPaginateCompaniesParams): Promise<PaginateCompaniesResults> {
        return await this.companies.sysAdminPaginate(data);
    }

    public async sysAdminUpdate(data: SysAdminUpdateCompanyParams): Promise<void> {
        const companyCheck = await this.companies.findOne({ _id: data.id });
        if (!companyCheck) {
            throw new Error("La compañia no existe");
        }

        if (data.name) {
            const otherCompany = await this.companies.findOne({ name: data.name.toLocaleLowerCase() });
            if (otherCompany && otherCompany.id !== data.id) {
                throw new Error("El nuevo nombre de la organización está en uso.");
            }
            data.name = data.name.toLocaleLowerCase();
        }

        let updateData: any = { ...data, sessionUser: "" };
        if (data.name) {
            updateData = {
                ...updateData,
                slug: this.getSlug(data.name)
            };
        }

        await this.companies.update(updateData as UpdateCompanyParams);
    }

    public async sysAdminSetBanned(data: SysAdminSetCompanyBannedParams): Promise<void> {
        const companyCheck = await this.companies.findOne({ _id: data.id });
        if (!companyCheck) {
            throw new Error("La compañia no existe");
        }
        await this.companies.update({
            id: data.id,
            banned: data.banned,
            sessionUser: "" // sysadmin override
        });
    }
}

const CompaniesList = new CompaniesManager();

export default CompaniesList;
