const _ = require('lodash');
const { getCollection } = require('../utils/collection');

const WorkflowManager = {};

WorkflowManager.getPositionsFilterByUsers = async function (spaceId, orgId, roleId) {
    if (!spaceId || !orgId || !roleId) {
        return [];
    }

    const flowPositionsCollection = await getCollection('flow_positions');
    return await flowPositionsCollection.find({
        space: spaceId,
        role: roleId,
        org: orgId
    }, {
        projection: {
            users: 1
        }
    }).toArray();
};

WorkflowManager.uniqUsers = function (users) {
    if (_.isEmpty(users)) {
        return [];
    }

    const users_str = users.map(u => JSON.stringify(u));
    const uniqueUsers_str = _.uniq(users_str);
    return uniqueUsers_str.map(us => JSON.parse(us));
};

WorkflowManager.getRoleUsersbyOrgAndRole = async function (spaceId, orgId, roleId) {
    let roleUsers = [];
    const orgPositions = await WorkflowManager.getPositionsFilterByUsers(spaceId, orgId, roleId);

    for (const orgPosition of orgPositions) {
        const roleUserIds = orgPosition.users;
        roleUsers = roleUsers.concat(await WorkflowManager.getUsers(spaceId, roleUserIds, true));
    }

    if (orgPositions.length === 0) {
        const organization = await WorkflowManager.getOrganization(orgId);
        if (organization && organization.parent) {
            const parentRoleUsers = await WorkflowManager.getRoleUsersbyOrgAndRole(spaceId, organization.parent, roleId);
            roleUsers = roleUsers.concat(parentRoleUsers);
        }
    }

    return roleUsers;
};

WorkflowManager.getRoleUsersByOrgAndRoles = async function (spaceId, orgId, roleIds) {
    let roleUsers = [];
    
    for (const roleId of roleIds) {
        const users = await WorkflowManager.getRoleUsersbyOrgAndRole(spaceId, orgId, roleId);
        roleUsers = roleUsers.concat(users);
    }
    
    return roleUsers;
};

WorkflowManager.getRoleUsersByOrgsAndRoles = async function (spaceId, orgIds, roleIds) {
    let roleUsers = [];
    
    if (!orgIds || !roleIds) {
        return roleUsers;
    }

    for (const orgId of orgIds) {
        const users = await WorkflowManager.getRoleUsersByOrgAndRoles(spaceId, orgId, roleIds);
        roleUsers = roleUsers.concat(users);
    }
    
    return roleUsers;
};

WorkflowManager.getRoleUsersByUsersAndRoles = async function (spaceId, userIds, roleIds) {
    let roleUsers = [];
    
    if (!userIds || !roleIds) {
        return roleUsers;
    }

    const users = await WorkflowManager.getUsers(spaceId, userIds, true);
    
    for (const user of users) {
        const usersByRoles = await WorkflowManager.getRoleUsersByOrgsAndRoles(spaceId, user.organizations, roleIds);
        roleUsers = roleUsers.concat(usersByRoles);
    }
    
    return roleUsers;
};

WorkflowManager.getSpaceRoles = async function (spaceId) {
    if (!spaceId) {
        return;
    }

    const flowRolesCollection = await getCollection('flow_roles');
    return await flowRolesCollection.find({
        space: spaceId
    }).toArray();
};

WorkflowManager.getRole = async function (spaceId, roleId) {
    if (!roleId || !spaceId) {
        return;
    }

    const flowRolesCollection = await getCollection('flow_roles');
    return await flowRolesCollection.findOne({
        _id: roleId,
        space: spaceId
    });
};

WorkflowManager.getRoles = async function (spaceId, roleIds) {
    if (!roleIds || !spaceId) {
        return;
    }

    const flowRolesCollection = await getCollection('flow_roles');
    return await flowRolesCollection.find({
        _id: { $in: roleIds },
        space: spaceId
    }).toArray();
};

WorkflowManager.getSpacePositions = async function (spaceId) {
    const flowPositionsCollection = await getCollection('flow_positions');
    return await flowPositionsCollection.find({
        space: spaceId
    }).toArray();
};

WorkflowManager.getUserRoles = async function (spaceId, orgId, userId) {
    const userRoles = [];
    const flowPositionsCollection = await getCollection('flow_positions');
    const userPositions = await flowPositionsCollection.find({
        space: spaceId,
        users: userId
    }, {
        projection: {
            role: 1
        }
    }).toArray();

    for (const userPosition of userPositions) {
        const role = await WorkflowManager.getRole(spaceId, userPosition.role);
        if (role) {
            userRoles.push(role);
        }
    }

    return userRoles;
};

WorkflowManager.getOrganizationsUsers = async function (spaceId, orgs) {
    let orgUsers = [];
    
    for (const org of orgs) {
        const users = await WorkflowManager.getUsers(spaceId, org.users, true);
        orgUsers = orgUsers.concat(users);
    }
    
    return orgUsers;
};

WorkflowManager.getSpaceOrganizations = async function (spaceId) {
    const organizationsCollection = await getCollection('organizations');
    return await organizationsCollection.find({
        space: spaceId
    }).toArray();
};

WorkflowManager.getOrganizationChildrens = async function (spaceId, orgId) {
    const organizationsCollection = await getCollection('organizations');
    return await organizationsCollection.find({
        space: spaceId,
        parents: orgId
    }).toArray();
};

WorkflowManager.getOrganizationsChildrens = async function (spaceId, orgIds) {
    let chidrenOrgs = [];
    
    for (const orgId of orgIds) {
        const children = await WorkflowManager.getOrganizationChildrens(spaceId, orgId);
        chidrenOrgs = chidrenOrgs.concat(children);
    }
    
    return chidrenOrgs;
};

WorkflowManager.getOrganization = async function (orgId) {
    if (!orgId) {
        return;
    }

    const organizationsCollection = await getCollection('organizations');
    return await organizationsCollection.findOne({_id: orgId}, {
        projection: { created: 0, created_by: 0, modified: 0, modified_by: 0 }
    });
};

WorkflowManager.getOrganizations = async function (orgIds) {
    if (!orgIds) {
        return [];
    }

    if (typeof orgIds === 'string') {
        const org = await WorkflowManager.getOrganization(orgIds);
        return org ? [org] : [];
    }

    const organizationsCollection = await getCollection('organizations');
    return await organizationsCollection.find({
        _id: { $in: orgIds }
    }, {
        projection: { created: 0, created_by: 0, modified: 0, modified_by: 0 }
    }).toArray();
};

WorkflowManager.getCompany = async function (companyId) {
    if (!companyId) {
        return {};
    }

    const companyCollection = await getCollection('company');
    return await companyCollection.findOne({_id: companyId}, {
        projection: { created: 0, created_by: 0, modified: 0, modified_by: 0 }
    }) || {};
};

WorkflowManager.getCompanys = async function (companyIds) {
    if (!companyIds) {
        return [];
    }

    if (typeof companyIds === 'string') {
        const company = await WorkflowManager.getCompany(companyIds);
        return company ? [company] : [];
    }

    const companyCollection = await getCollection('company');
    return await companyCollection.find({
        _id: { $in: companyIds }
    }, {
        projection: { created: 0, created_by: 0, modified: 0, modified_by: 0 }
    }).toArray();
};

WorkflowManager.getUser = async function (spaceId, userId, notNeedDetails) {
    if (!userId || !spaceId) {
        return;
    }

    if (typeof userId !== 'string') {
        return await WorkflowManager.getUsers(spaceId, userId, notNeedDetails);
    }

    const spaceUsersCollection = await getCollection('space_users');
    const spaceUser = await spaceUsersCollection.findOne({
        space: spaceId,
        user: userId
    }, {
        projection: { created: 0, created_by: 0, modified: 0, modified_by: 0 }
    });

    if (!spaceUser) {
        return;
    }

    spaceUser.id = spaceUser.user;

    if (notNeedDetails !== true) {
        spaceUser.organization = await WorkflowManager.getOrganization(spaceUser.organization);
        if (!spaceUser.organization) {
            return;
        }

        spaceUser.company = await WorkflowManager.getCompany(spaceUser.company_id);
        spaceUser.companys = await WorkflowManager.getCompanys(spaceUser.company_ids);
        spaceUser.roles = await WorkflowManager.getUserRoles(spaceId, spaceUser.organization.id, spaceUser.id);
    }

    return spaceUser;
};

WorkflowManager.getUsers = async function (spaceId, userIds, notNeedDetails) {
    if (typeof userIds === 'string') {
        const user = await WorkflowManager.getUser(spaceId, userIds, notNeedDetails);
        return user ? [user] : [];
    }

    const users = [];
    if (userIds && spaceId) {
        const spaceUsersCollection = await getCollection('space_users');
        const spaceUsersCursor = await spaceUsersCollection.find({
            space: spaceId,
            user: { $in: userIds }
        }, {
            projection: { created: 0, created_by: 0, modified: 0, modified_by: 0 }
        });

        const spaceUsers = await spaceUsersCursor.toArray();

        if (notNeedDetails === true) {
            for (const spaceUser of spaceUsers) {
                spaceUser.id = spaceUser.user;
                users.push(spaceUser);
            }
        } else {
            for (const spaceUser of spaceUsers) {
                spaceUser.id = spaceUser.user;
                spaceUser.organization = await WorkflowManager.getOrganization(spaceUser.organization);
                spaceUser.organizations = await WorkflowManager.getOrganizations(spaceUser.organizations);
                spaceUser.company = await WorkflowManager.getCompany(spaceUser.company_id);
                spaceUser.companys = await WorkflowManager.getCompanys(spaceUser.company_ids);

                if (spaceUser.organization) {
                    spaceUser.roles = await WorkflowManager.getUserRoles(spaceId, spaceUser.organization.id, spaceUser.id);
                    users.push(spaceUser);
                }
            }
        }
    }

    return users;
};

WorkflowManager.getHrRolesUsers = async function (spaceId, hrRoleIds) {
    let hrRolesUsers = [];

    if (!_.isArray(hrRoleIds)) {
        return [];
    }

    const rolesCollection = await getCollection('roles');
    const hrRoles = await rolesCollection.find({
        space: spaceId,
        _id: { $in: hrRoleIds }
    }).toArray();

    for (const hrRole of hrRoles) {
        hrRolesUsers = hrRolesUsers.concat(hrRole.users || []);
    }

    const spaceUsersCollection = await getCollection('space_users');
    const spaceUsers = await spaceUsersCollection.find({
        space: spaceId,
        user: { $in: hrRolesUsers }
    }, {
        projection: { created: 0, created_by: 0, modified: 0, modified_by: 0 }
    }).toArray();

    for (const spaceUser of spaceUsers) {
        spaceUser.id = spaceUser.user;
    }

    return spaceUsers;
};

WorkflowManager.getFormulaUsers = async function (spaceId, userIds) {
    const users = await WorkflowManager.getUsers(spaceId, userIds);

    for (const user of users) {
        user.organization.id = user.organization._id;

        user.organizations = {
            id: user.organizations.map(o => o._id),
            name: user.organizations.map(o => o.name),
            fullname: user.organizations.map(o => o.fullname)
        };

        user.company.id = user.company._id;

        user.companys = {
            id: user.companys.map(c => c._id),
            name: user.companys.map(c => c.name),
            code: user.companys.map(c => c.code)
        };

        if (!user.hr) {
            user.hr = {};
        }

        user.roles = user.roles ? user.roles.map(r => r.name) : [];
    }

    return users;
};

WorkflowManager.getFormulaUserObjects = async function (spaceId, userIds) {
    if (!userIds) {
        return {
            organization: {},
            hr: {}
        };
    }
    return WorkflowManager.getFormulaUserObject(spaceId, userIds);
};

WorkflowManager.getFormulaUserObject = async function (spaceId, userId) {
    if (!userId) {
        return {
            organization: {},
            hr: {}
        };
    }

    if (Array.isArray(userId)) {
        return WorkflowManager.getFormulaUsers(spaceId, userId);
    } else {
        const users = await WorkflowManager.getFormulaUsers(spaceId, [userId]);
        return users[0];
    }
};

WorkflowManager.getFormulaOrgObjects = async function (orgIds) {
    if (!orgIds) {
        return;
    }
    return WorkflowManager.getFormulaOrgObject(orgIds);
};

WorkflowManager.getFormulaOrgObject = async function (orgId) {
    if (Array.isArray(orgId)) {
        const orgs = await WorkflowManager.getOrganizations(orgId);
        for (const org of orgs) {
            org.id = org._id;
        }
        return orgs;
    } else {
        const org = await WorkflowManager.getOrganization(orgId);
        if (!org) {
            return null;
        }
        org.id = orgId;
        return org;
    }
};

WorkflowManager.getInstanceFormVersion = async function () {
    const instance = Template.instance().view.template.steedosData.instance;
    const formsCollection = await getCollection('forms');
    const form = await formsCollection.findOne({_id: instance.form});

    const form_version = {};
    const form_fields = [];

    if (form.current._id === instance.form_version) {
        Object.assign(form_version, form.current);
    } else {
        const historyVersion = form.historys.find(h => h._id === instance.form_version);
        if (historyVersion) {
            Object.assign(form_version, historyVersion);
        }
    }

    for (const field of form_version.fields) {
        if (field.type === 'section') {
            form_fields.push(field);
            if (field.fields) {
                for (const f of field.fields) {
                    form_fields.push(f);
                }
            }
        } else if (field.type === 'table') {
            const tableField = { ...field };
            tableField.sfields = tableField.fields;
            delete tableField.fields;
            form_fields.push(tableField);
        } else {
            form_fields.push(field);
        }
    }

    form_version.fields = form_fields;
    return form_version;
};

WorkflowManager.getFormVersion = async function (id, versionId) {
    const formsCollection = await getCollection('forms');
    const form = await formsCollection.findOne({ _id: id });

    let form_version = form.current;

    if (form_version._id !== versionId) {
        form_version = form.historys.find(h => h._id === versionId);
    }

    const form_fields = [];

    for (const field of form_version.fields) {
        if (field.type === 'section') {
            form_fields.push(field);
            if (field.fields) {
                for (const f of field.fields) {
                    form_fields.push(f);
                }
            }
        } else {
            form_fields.push(field);
        }
    }

    form_version.fields = form_fields;
    return form_version;
};

WorkflowManager.getInstanceFlowVersion = async function (instance) {
    if (!instance) {
        instance = Template.instance().view.template.steedosData.instance;
    }

    const flowsCollection = await getCollection('flows');
    const flow = await flowsCollection.findOne({_id: instance.flow});

    if (flow.current._id === instance.flow_version) {
        return flow.current;
    } else {
        return flow.historys.find(h => h._id === instance.flow_version);
    }
};

WorkflowManager.getInstanceStep = async function (stepId) {
    const flow = await WorkflowManager.getInstanceFlowVersion();

    if (!flow) {
        return null;
    }

    for (const step of flow.steps) {
        if (step._id === stepId) {
            const g_step = { ...step };
            g_step.id = step._id;
            return g_step;
        }
    }

    return null;
};

WorkflowManager.canAdmin = function (fl, curSpaceUser, organizations) {
    const perms = fl.perms;
    let hasAdminRight = false;

    if (perms) {
        if (perms.users_can_admin && perms.users_can_admin.includes(curSpaceUser.user)) {
            hasAdminRight = true;
        } else if (perms.orgs_can_admin && perms.orgs_can_admin.length > 0) {
            if (curSpaceUser && curSpaceUser.organizations && _.intersection(curSpaceUser.organizations, perms.orgs_can_admin).length > 0) {
                hasAdminRight = true;
            } else if (organizations) {
                hasAdminRight = _.some(organizations, org => 
                    org.parents && _.intersection(org.parents, perms.orgs_can_admin).length > 0
                );
            }
        }
    }

    return hasAdminRight;
};

WorkflowManager.canMonitor = function (fl, curSpaceUser, organizations) {
    const perms = fl.perms;
    let hasMonitorRight = false;

    if (perms) {
        if (perms.users_can_monitor && perms.users_can_monitor.includes(curSpaceUser.user)) {
            hasMonitorRight = true;
        } else if (perms.orgs_can_monitor && perms.orgs_can_monitor.length > 0) {
            if (curSpaceUser && curSpaceUser.organizations && _.intersection(curSpaceUser.organizations, perms.orgs_can_monitor).length > 0) {
                hasMonitorRight = true;
            } else if (organizations) {
                hasMonitorRight = _.some(organizations, org => 
                    org.parents && _.intersection(org.parents, perms.orgs_can_monitor).length > 0
                );
            }
        }
    }

    return hasMonitorRight;
};

WorkflowManager.hasInstancePermissions = async function (user, instance) {
    if (user && instance) {
        const spacesCollection = await getCollection('spaces');
        const space = await spacesCollection.findOne({
            _id: instance.space
        }, {
            projection: {
                admins: 1
            }
        });

        if (space && space.admins && space.admins.includes(user._id)) {
            return true;
        }
    }

    const approvedUsers = _.union(
        instance.outbox_users,
        instance.inbox_users,
        instance.cc_users,
        [instance.submitter],
        [instance.applicant],
        [instance.created_by],
        [instance.modified_by]
    );

    if (approvedUsers.includes(user._id)) {
        return true;
    }

    const instancesCollection = await getCollection('instances');
    const originalInstancesCursor = await instancesCollection.find({
        related_instances: {
            $all: [instance._id]
        }
    }, {
        projection: {
            outbox_users: 1,
            inbox_users: 1,
            cc_users: 1,
            submitter: 1,
            applicant: 1
        }
    });

    const originalInstances = await originalInstancesCursor.toArray();

    for (const ins of originalInstances) {
        const havePermissionUsers = _.union(
            ins.outbox_users,
            ins.inbox_users,
            ins.cc_users,
            [ins.submitter],
            [ins.applicant]
        );

        if (havePermissionUsers.includes(user._id)) {
            return true;
        }
    }

    const spaceUsersCollection = await getCollection('space_users');
    const spaceUser = await spaceUsersCollection.findOne({
        space: instance.space,
        user: user._id
    });

    if (spaceUser) {
        const organizationsCollection = await getCollection('organizations');
        const organizations = await organizationsCollection.find({
            _id: { $in: spaceUser.organizations }
        }).toArray();

        const flowsCollection = await getCollection('flows');
        const flow = await flowsCollection.findOne({
            _id: instance.flow
        });

        if (flow) {
            return WorkflowManager.canMonitor(flow, spaceUser, organizations) || 
                   WorkflowManager.canAdmin(flow, spaceUser, organizations);
        }
    }

    return false;
};

WorkflowManager.getNameForUser = async function (userId) {
    const usersCollection = await getCollection('users');
    return await usersCollection.findOne({
        _id: userId
    }, {
        projection: {
            name: 1
        }
    });
};

WorkflowManager.hasFlowAdminPermission = async function (flow_id, space_id, user_id) {
    const spacesCollection = await getCollection('spaces');
    const space = await spacesCollection.findOne({
        _id: space_id
    }, {
        projection: {
            admins: 1
        }
    });

    if (!space) {
        return false;
    }

    if (space.admins && space.admins.includes(user_id)) {
        return true;
    }

    const spaceUsersCollection = await getCollection('space_users');
    const space_user = await spaceUsersCollection.findOne({
        space: space_id,
        user: user_id
    }, {
        projection: {
            organizations: 1,
            user: 1
        }
    });

    if (space_user) {
        const organizationsCollection = await getCollection('organizations');
        const organizations = await organizationsCollection.find({
            _id: { $in: space_user.organizations }
        }, {
            projection: {
                parents: 1
            }
        }).toArray();

        const flowsCollection = await getCollection('flows');
        const fl = await flowsCollection.findOne({
            _id: flow_id
        }, {
            projection: {
                perms: 1
            }
        });

        if (fl && organizations) {
            return WorkflowManager.canAdmin(fl, space_user, organizations);
        }
    }

    return false;
};

WorkflowManager.getMyAdminOrMonitorFlows = async function (spaceId, userId) {
    const flow_ids = [];
    const spaceUsersCollection = await getCollection('space_users');
    const curSpaceUser = await spaceUsersCollection.findOne({
        space: spaceId,
        user: userId
    }, { projection: { user: 1, organizations: 1 } });

    if (curSpaceUser) {
        const organizationsCollection = await getCollection('organizations');
        const organizations = await organizationsCollection.find({
            _id: { $in: curSpaceUser.organizations }
        }, { projection: { parents: 1 } }).toArray();

        const flowsCollection = await getCollection('flows');
        const flows = await flowsCollection.find({
            space: spaceId
        }, { projection: { perms: 1 } }).toArray();

        for (const fl of flows) {
            if (WorkflowManager.canMonitor(fl, curSpaceUser, organizations) || 
                WorkflowManager.canAdmin(fl, curSpaceUser, organizations)) {
                flow_ids.push(fl._id);
            }
        }
    }

    return flow_ids;
};

WorkflowManager.getContactInfoForUser = async function (userId, spaceId) {
    const spaceUsersCollection = await getCollection('space_users');
    return await spaceUsersCollection.findOne({
        space: spaceId,
        user: userId
    }, {
        projection: {
            work_phone: 1,
            mobile: 1
        }
    });
};

module.exports = WorkflowManager;