const _ = require('underscore');
const { getObject } = require("@steedos/objectql");
const lodash = require('lodash');

const { _makeNewID, getCollection } = require('./collection');

async function getByAdminSpaceIds(spaceIds, companyId, isCompanyAdmin, flowId) {
    let filters = _makeInFilters('space', spaceIds);
    let spaceFilters = filters;
    if (isCompanyAdmin && companyId) {
        filters = `(${filters}) and ((company_id eq '${companyId}') or (company_id eq null))`;
    }
    
    const spaceUsersPromise = getObject('space_users').find({
        filters: spaceFilters,
        fields: ['name', 'email', 'space', 'organization', 'organizations', 'user', 'user_accepted', 'company_id']
    })

    const usersPromise = spaceUsersPromise.then(async (spaceUsers) => {
        let userIds = _.pluck(spaceUsers, 'user');
        let usersCollection = await getCollection('users');
        return await usersCollection.find({
            _id: {
                $in: userIds
            }
        }, {
            fields: {
                'photo': 1,
                'google_id': 1,
                'imo_uid': 1,
                'company': 1,
                'name': 1,
                'locale': 1,
                'steedos_id': 1,
                'primary_email_verified': 1,
                'is_paid_user': 1,
                'mobile': 1,
                'email': 1,
                'created': 1,
                'modified': 1,
                'created_by': 1,
                'modified_by': 1,
                'email_notification': 1,
                'qq_open_id': 1
            }
        }).toArray();
    });

    let flowsFilters = filters;
    if (flowId) {
        flowsFilters = `(${flowsFilters}) and (_id eq '${flowId}')`;
    }

    const flowsPromise = getObject('flows').find({
        filters: flowsFilters,
        fields: ['name', 'name_formula', 'code_formula', 'space', 'description', 'is_valid', 'form', 'flowtype', 'state', 'is_deleted', 'created', 'created_by', 'help_text', 'current_no', 'current', 'perms', 'error_message', 'app', 'distribute_optional_users', 'company_id']
    })
    
    let formsPromise;
    if (flowId) {
        formsPromise = flowsPromise.then(async (flows) => {
            let formsFilters = filters;
            if (flows.length > 0) {
                const formIds = _.pluck(flows, 'form');
                if (formIds && formIds.length > 0) {
                    formsFilters = `(${formsFilters}) and (_id eq '${formIds[0]}')`
                }
            } else {
                formsFilters = `(1 eq 0)`
            }
            return await getObject('forms').find({
                filters: formsFilters,
                fields: ['name', 'state', 'is_deleted', 'is_valid', 'space', 'description', 'help_text', 'created', 'created_by', 'error_message', 'current', 'enable_workflow', 'enable_view_others', 'app', 'category', 'is_subform', 'instance_style', 'company_id']
            })
        })
    } else {
        formsPromise = getObject('forms').find({
            filters: filters,
            fields: ['name', 'state', 'is_deleted', 'is_valid', 'space', 'description', 'help_text', 'created', 'created_by', 'error_message', 'current', 'enable_workflow', 'enable_view_others', 'app', 'category', 'is_subform', 'instance_style', 'company_id']
        })
    }

    const rolesPromise = getObject('flow_roles').find({
        filters: filters
    });
    const organizationsPromise = getObject('organizations').find({
        filters: spaceFilters
    });
    const positionsPromise = getObject('flow_positions').find({
        filters: filters
    });
    const categoriesPromise = getObject('categories').find({
        filters: _makeInFilters('space', spaceIds)
    });

    const hrRolesPromise = getObject('roles').find({
        filters: filters
    })

    let [spaceUsers, users, forms, flows, organizations, positions, roles, categories, hrRoles] = await Promise.all([
        spaceUsersPromise,
        usersPromise,
        formsPromise,
        flowsPromise,
        organizationsPromise,
        positionsPromise,
        rolesPromise,
        categoriesPromise,
        hrRolesPromise
    ]);

    if (_.isArray(roles)) {
        await Promise.all(roles.map(async (role) => {
            if (role && role.company_id) {
                let company = await getObject('company').findOne(role.company_id, { fields: ['name'] }) //, 'abbreviation'
                if (company && (company.name || company.abbreviation)) {
                    role.name = `${role.name} (${company.abbreviation || company.name})`
                }
            }
        }))
    }

    return {
        SpaceUsers: spaceUsers,
        Users: users,
        Forms: forms,
        Flows: flows,
        Organizations: organizations,
        Positions: positions,
        Roles: roles,
        Categories: categories,
        HrRoles: hrRoles
    }
}

async function checkSpaceUserBeforeUpdate(spaceId, userId, roles) {
    let space = await getObject('spaces').findOne(spaceId);
    if (!space) {
        throw new Error('该工作区不存在或已经被删除');
    }

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

    let spaceUser = (await getObject('space_users').find({
        filters: `(space eq '${spaceId}') and (user eq '${userId}')`
    }))[0];
    if (!spaceUser) {
        throw new Error('该用户不存在于该工作区中');
    }

    if (!_.includes(roles, 'workflow_admin')) { // 校验是否是单位管理员
        throw new Error('该用户无操作权限');
    }
};

async function updateForm(formId, form, forms, flows, currentUserId) {
    let formCollection = await getCollection('forms');
    let flowCollection = await getCollection('flows');
    let ff = await formCollection.findOne({_id: formId});
    let spaceId = ff.space;
    let now = new Date();
    let current = {};
    let formUpdateObj = {};

    let pass = false;
    // 根据APP 判断表单当前版本是否走过申请单 或者 records
    if (ff.app === 'workflow') {
        let instancesCollection = await getCollection('instances');
        let insCount = await instancesCollection.countDocuments({
            space: spaceId,
            form: formId,
            form_version: form['current']['id'] || form['current']['_id']
        });
        if (insCount > 0) {
            pass = true;
        }
    } else if (ff.app === 'creator') {
        let recordsCollection = await getCollection('records');
        let recordsCount = await recordsCollection.countDocuments({
            space: spaceId,
            form: formId,
            form_version: form['current']['id'] || form['current']['_id']
        });
        if (recordsCount > 0) {
            pass = true;
        }
    }

    if (pass === true && ff["current"]["start_date"]) { // 升级表单
        formUpdateObj.$push = {
            'historys': ff["current"]
        };
        current._id = _makeNewID();
        current._rev = ff["current"]["_rev"] + 1;
        current.created = now;
        current.created_by = currentUserId;
        if (ff.state === 'enabled') {
            current.start_date = now;
        }
        // 更新流程版本
        let flowCursor = await flowCollection.find({form: formId});
        let flowList = await flowCursor.toArray();
        
        for (let flow of flowList) {
            let up = false;
            let instancesCollection = await getCollection('instances');
            let insCount = await instancesCollection.countDocuments({
                space: spaceId,
                flow: flow._id,
                flow_version: flow.current._id
            });
            if (insCount) {
                up = true;
            }
            let flowUpdateObj = {
                $set: {}
            };
            if (up === true && flow.current.start_date) { // 升级流程
                flowUpdateObj.$push = {
                    'historys': flow.current
                };
                let flowCurrent = {
                    '_id': _makeNewID(),
                    'created': now,
                    'created_by': currentUserId,
                    'steps': flow.current.steps,
                    '_rev': flow.current._rev + 1,
                    'flow': flow._id,
                    'form_version': current._id,
                    'modified': now,
                    'modified_by': currentUserId
                };
                if (flow.state === "enabled") {
                    flowCurrent.start_date = now;
                }
                flowUpdateObj.$set['current'] = flowCurrent;
            } else {
                flowUpdateObj.$set = {
                    'current.form_version': current._id,
                    'current.modified': now,
                    'current.modified_by': currentUserId
                }
            }
            flowUpdateObj.$set['modified'] = now;
            flowUpdateObj.$set['modified_by'] = currentUserId;
            await flowCollection.updateOne({_id: flow._id}, flowUpdateObj);
            flows.push(await flowCollection.findOne({_id: flow._id}));
        }
    } else {
        current = ff.current;
    }

    current.modified = now;
    current.modified_by = currentUserId;
    current.form = formId;
    current.fields = _formatFieldsID(form["current"]["fields"]);
    current.form_script = form["current"]["form_script"];
    current.name_forumla = form["current"]["name_forumla"];
    current.style = form["current"]["style"];
    current.mode = form["current"]["mode"];
    current.wizard_mode = form["current"]["wizard_mode"];
    current.amis_schema = form["current"]["amis_schema"]

    formUpdateObj.$set = {
        'current': current,
        'name': form["name"],
        'modified': now,
        'modified_by': currentUserId,
        'is_valid': form["is_valid"],
        'style': form["style"],
        'mode': form["mode"],
        'wizard_mode': form["wizard_mode"],
        'description': form["description"],
        'help_text': form["help_text"],
        'error_message': form["error_message"],
        'category': form["category"],
        'instance_style': form["instance_style"]
    }

    await formCollection.updateOne({_id: formId}, formUpdateObj);
    forms.push(await formCollection.findOne({_id: formId}));
}

async function checkBeforeFlow(spaceId, formId) {
    if (await getObject('spaces').count({
        filters: `(_id eq '${spaceId}')`
    }) === 0) {
        throw new Error('工作区不存在或已经被删除')
    }

    if (await getObject('forms').count({
        filters: `(_id eq '${formId}')`
    }) === 0) {
        throw new Error('表单不存在')
    }
}

async function getFlow(flowId) {
    let flow = await getObject('flows').findOne(flowId);
    if (!flow) {
        throw new Error('流程不存在')
    }
    return flow;
}

async function getForm(formId) {
    let form = await getObject('forms').findOne(formId);
    if (!form) {
        throw new Error('表单不存在')
    }
    return form;
}

async function getSpaceUser(userId, spaceId) {
    let spaceUser = (await getObject('space_users').find({
        filters: `(space eq '${spaceId}') and (user eq '${userId}')`
    }))[0];
    if (!spaceUser) {
        throw new Error('用户不属于当前工作区')
    }
    if (!spaceUser.user_accepted) {
        throw new Error('用户在当前工作区是停用状态')
    }
    return spaceUser;
}

async function isSpaceAdmin(spaceId, userId, roles) {
    let space = await getObject('spaces').findOne(spaceId);
    if (!space) {
        throw new Error('未找到工作区');
    }

    if (!space.admins.includes(userId) && !roles.includes('workflow_admin')) {
        throw new Error('用户无操作权限');
    }
}

async function makeSteps(userId, fields = [], language) {
    let blank_ayy = [];
    let stepEnd = _makeNewID();
    let steps = [];
    let start_step = {};
    start_step._id = _makeNewID();
    start_step.approver_orgs = blank_ayy;
    start_step.approver_roles = blank_ayy;
    start_step.approver_users = blank_ayy;
    start_step.fields_modifiable = blank_ayy;
    start_step.name = language === 'zh-CN' ? '开始' : 'Start';
    start_step.step_type = "start";
    start_step.posx = -1;
    start_step.posy = -1;
    let p = {};
    p["__form"] = "editable";
    for (const f of fields) {
        p[f.code] = 'editable'
        if (f.type == 'table') {
            for (const tf of (f.fields || [])) {
                p[tf.code] = 'editable';
            }
        }
    }
    start_step.permissions = p;
    let lines = [];
    let line = {};
    line._id = _makeNewID();
    line.name = "";
    line.to_step = stepEnd;
    line.order = 1;
    line.state = "submitted";
    lines.push(line);
    start_step.lines = lines;
    steps.push(start_step);

    let end_step = {};
    end_step._id = stepEnd;
    end_step.approver_orgs = blank_ayy;
    end_step.approver_roles = blank_ayy;
    end_step.approver_users = blank_ayy;
    end_step.fields_modifiable = blank_ayy;
    end_step.lines = blank_ayy;
    end_step.name = language === 'zh-CN' ? '结束' : 'End';
    end_step.step_type = "end";
    end_step.posx = -1;
    end_step.posy = -1;
    end_step.permissions = {};
    steps.push(end_step);

    return steps;
}

function _makeInFilters(fieldName, fieldValueArray) {
    let filters = _.map(fieldValueArray, function (v) {
        return `(${fieldName} eq '${v}')`
    }).join(' or ')
    return filters;
}

function _formatFieldsID(fields) {
    _.each(fields, function (f) {
        if (!f._id && f.id) {
            f._id = f.id;
            delete f.id;
            if (f.type == 'section' || f.type == 'table') {
                _formatFieldsID(f.fields);
            }
        }
    });
    return fields;
}

async function transformObjectFieldsToFormFields(objFields, codePrefix) {
    let formFieldsMap = {};
    for (const f of objFields) {
        const formField = await _transformObjectFieldToFormField(f, codePrefix);
        if (formField) {
            formFieldsMap[formField.code] = formField;
        }
    }
    return formFieldsMap;
}

async function _transformObjectFieldToFormField(objField, codePrefix = '') {
    let formField = {
        "name": objField.label,
        "code": `${codePrefix}${objField.name}`,
        "is_wide": objField.is_wide || false,
        "is_list_display": false,
        "is_searchable": objField.sortable || false,
        "is_required": objField.required || false,
        "is_multiselect": objField.multiple || false,
        "default_value": objField.defaultValue,
        "_id": _makeNewID(),
        "steedos_field": lodash.pickBy(objField, (v,k)=>{
            return !lodash.includes(['_id', '_name', 'is_system', 'space', 'company_id', 'company_ids', 'object_name', 'owner', 'created', 'modified', 'created_by', 'modified_by'], k)
        })
    };
    switch (objField.type) {
        case 'text':
            formField.type = "input";
            break;
        case 'textarea':
            formField.type = "input";
            formField.is_textarea = true;
            break;
        case 'html':
            formField.type = "html";
            break;
        case 'select':
            if (_.isArray(objField.options)) {
                formField.type = objField.multiple ? "multiSelect" : "select";
                formField.options = _.map(objField.options, function (optionItem) {
                    return optionItem.label + ":" + optionItem.value;
                }).join('\n');
            }
            break;
        case 'color':
            formField.type = "input";
            break;
        case 'boolean':
            formField.type = "checkbox";
            break;
        case 'toggle':
            formField.type = "checkbox";
            break;
        case 'date':
            formField.type = "date";
            break;
        case 'datetime':
            formField.type = "dateTime";
            break;
        case 'time':
            formField.type = "time";
            break;
        case 'number':
            formField.type = "number";
            formField.digits = objField.scale;
            break;
        case 'currency':
            formField.type = "number";
            formField.digits = objField.scale;
            break;
        case 'percent':
            formField.type = "number";
            formField.digits = objField.scale;
            formField.is_percent = true;
            break;
        case 'password':
            formField.type = "password";
            break;
        case 'lookup':
        case 'master_detail':
            let refObjName = objField.reference_to;
            const refToField = objField.reference_to_field
            if (_.isString(refObjName)) {
                // 人员需转为选择人员字段、部门需转为选择部门字段
                if ('users' == refObjName || ('space_users' == refObjName && 'user' == refToField)) {
                    formField.type = 'user'
                } else if ('organizations' == refObjName) {
                    formField.type = 'group'
                } else {
                    formField.type = 'lookup';
                    formField.reference_to = objField.reference_to;
                    formField.reference_to_field = objField.reference_to_field;
                    formField.config = JSON.stringify({filters: objField.filters, amis: objField.amis});
                }
            }
            
            break;
        case 'autonumber':
            formField.type = "input";
            break;
        case 'url':
            formField.type = "url";
            break;
        case 'email':
            formField.type = "email";
            break;
        case 'code':
            formField.type = "input";
            formField.is_textarea = true;
            break;
        case 'image':
        case 'file':
            formField.type = objField.type;
            break;
        case 'formula':
        case 'summary':
            switch (objField.data_type) {
                case 'boolean':
                    formField.type = "checkbox";
                    break;
                case 'number':
                case 'currency':
                    formField.type = "number";
                    formField.digits = objField.scale;
                    break;
                case 'percent':
                    formField.type = "number";
                    formField.digits = objField.scale;
                    formField.is_percent = true;
                    break;
                case 'text':
                    formField.type = "input";
                    break;
                case 'date':
                    formField.type = "date";
                    break;
                case 'datetime':
                    formField.type = "dateTime";
                    break;
                default:
                    break;
            }
            break;
        default:
            break;
    }
    if (formField.type) {
        return formField;
    }
}

function transformObjectFields(fields, objFieldsMap) {
    let newFields = [];
    for (const f of fields) {
        let field = objFieldsMap[f.name];
        if (field) {
            newFields.push({
                ...field,
                required: !!f.required
            })
        }
    }
    return newFields;
}

function getObjectFieldsByNames(fieldNames = [], objFieldsMap = {}) {
    let newFields = [];
    for (const fName of fieldNames) {
        let field = objFieldsMap[fName];
        if (field) {
            newFields.push({
                ...field
            })
        }
    }
    return newFields;
}

function getNeededFields(fields) {
    return {
        name: fields.name,
        label: fields.label,
        sort_no: fields.sort_no,
        required: fields.required
    }
}

async function getBusinessFields(objectName) {
    if (!objectName) {
        return []
    }
    let instanceFields = [];
    let objFields = await getObject(objectName).getFields();
    let baseFields = await getObject('base').getFields();
    for (const key in objFields) {
        if (Object.hasOwnProperty.call(objFields, key)) {
            let f = objFields[key]
            if (f && !baseFields[key]) {
                instanceFields.push(getNeededFields(f))
            }
        }
    }

    return instanceFields
}

async function updateStartStepPermission(flowDoc, formFields = []) {
    if (!flowDoc) {
        return;
    }
    const flowObj = getObject('flows');
    for (const s of flowDoc.current.steps) {
        if (s.step_type == 'start') {
            let perms = { "__form": "editable" };
            for (const f of formFields) {
                perms[f.code] = 'editable';
                if (f.type == 'table') {
                    for (const tf of (f.fields || [])) {
                        perms[tf.code] = 'editable';
                    }
                }
            }
            s.permissions = perms;
            await flowObj.directUpdate(flowDoc._id, { 'current.steps': flowDoc.current.steps })
        }
    }
}

async function transformObjectDetailFieldsToFormTableFields(instance_table_fields) {
    const tables = [];
    for (const tf of instance_table_fields) {
        let detailFieldFullname = tf.detail_field_fullname;
        let detailObjName = detailFieldFullname.split('.')[0];
        const detailObj = getObject(detailObjName);
        const detailObjConfig = await detailObj.toConfig();
        let tLabel = tf.label || detailObjConfig.label;
        let tableFields = getObjectFieldsByNames(tf.field_names, detailObjConfig.fields);
        const tFieldsMap = await transformObjectFieldsToFormFields(tableFields, `${detailObjName}_`);
        const tFields = Object.values(tFieldsMap);
        tables.push({
            "type": "table",
            "name": tLabel,
            "code": detailObjName,
            "is_wide": true,
            "is_required": false,
            "fields": tFields,
            "_id": _makeNewID(),
            "relatedId": detailFieldFullname
        });
    }
    return tables
}

module.exports = {
    getByAdminSpaceIds,
    checkSpaceUserBeforeUpdate,
    updateForm,
    checkBeforeFlow,
    getFlow,
    getForm,
    getSpaceUser,
    isSpaceAdmin,
    makeSteps,
    formatFieldsID: _formatFieldsID,
    transformObjectFieldsToFormFields,
    transformObjectFields,
    getObjectFieldsByNames,
    getBusinessFields,
    updateStartStepPermission,
    transformObjectDetailFieldsToFormTableFields
}