// 审批单同步到台账

const objectql = require('@steedos/objectql');

async function getCollection(name) {
    const adapter = objectql.getDataSource('default').adapter
    await adapter.connect()
    return adapter.collection(name)
}

const InstanceRecordQueue = {};

async function init(){
    const adapter = objectql.getDataSource('default').adapter
    await adapter.connect()
    InstanceRecordQueue.collection =  adapter.collection('instance_record_queue')
}

init()

var runQuoted = async function (objectName, recordId) {
    await objectql.runQuotedByObjectFieldFormulas(objectName, recordId);
    await objectql.runQuotedByObjectFieldSummaries(objectName, recordId);
}

const getObjectFields = async function (objectApiName) {
    return await objectql.getObject(objectApiName).getFields()
}

const getObjectNameFieldKey = async function (objectApiName) {
    return await objectql.getObject(objectApiName).getNameFieldKey()
}

const getObjectConfig = async function (objectApiName) {
    return await objectql.getObject(objectApiName).toConfig()
}

const makeNewID = async function (objectApiName) {
    return await objectql.getObject(objectApiName)._makeNewID()
}

const objectInsert = async function (objectApiName, data) {
    return await objectql.getObject(objectApiName).insert(data)
}

const objectUpdate = async function (objectApiName, id, data) {
    return await objectql.getObject(objectApiName).update(id, data);
}

const objectUpdateMany = async function (objectApiName, filters, data) {
    return await objectql.getObject(objectApiName).updateMany(filters, data);
}

const objectRemove = async function (objectApiName, id) {
    return await objectql.getObject(objectApiName).delete(id);
}

const getRelateds = async function (objectApiName) {
    return await objectql.getObject(objectApiName).getRelateds();
}

const objectFindOne = async function (objectApiName, query) {
    const result = await objectql.getObject(objectApiName).find(query);
    if (result && result.length > 0) {
        return result[0];
    }
    return null;
}

const objectFind = async function (objectApiName, query) {
    return await objectql.getObject(objectApiName).find(query);
}

var _eval = require('eval');
var isConfigured = false;

var sendWorker = function (task, interval) {
    if (InstanceRecordQueue.debug) {
        console.log('InstanceRecordQueue: Send worker started, using interval: ' + interval);
    }

    return setInterval(async function () {
        try {
           await task();
        } catch (error) {
            console.log('InstanceRecordQueue: Error while sending: ', error);
        }
    }, interval);
};

const getRelatedObjectField = function (relatedObjectsKeys, key) {
    return _.find(relatedObjectsKeys, function (relatedObjectsKey) {
        return key.startsWith(relatedObjectsKey + '.');
    })
};

const getFormTableField = function (formTableFieldsCode, key) {
    return _.find(formTableFieldsCode, function (formTableFieldCode) {
        return key.startsWith(formTableFieldCode + '.');
    })
};

const getFormField = function (_formFields, _fieldCode) {
    var formField = null;
    _.each(_formFields, function (ff) {
        if (!formField) {
            if (ff.code === _fieldCode) {
                formField = ff;
            } else if (ff.type === 'section') {
                _.each(ff.fields, function (f) {
                    if (!formField) {
                        if (f.code === _fieldCode) {
                            formField = f;
                        }
                    }
                })
            } else if (ff.type === 'table') {
                _.each(ff.fields, function (f) {
                    if (!formField) {
                        if (f.code === _fieldCode) {
                            formField = f;
                        }
                    }
                })
            }
        }
    });
    return formField;
}

const getFileFieldValue = async function (recordFieldId, fType) {
    var collection, files, query, value;
    if (_.isEmpty(recordFieldId)) {
        return;
    }
    if (fType === 'image') {
        collection = 'images';
    } else if (fType === 'file') {
        collection = 'files';
    }
    if (_.isString(recordFieldId)) {
        query = {
            _id: {
                $in: [recordFieldId]
            }
        };
    } else {
        query = {
            _id: {
                $in: recordFieldId
            }
        };
    }

    //TODO 同步附
    console.log('TODO, 同步附件')
    value = [];
//     files = await Creator.Collections["cfs." + collection + ".filerecord"].find(query).toArray();
//     for (const f of files) {
//         console.log('TODO, 同步附件')
//         var newFile;
//         newFile = new FS.File();
//         await new Promise((resolve, reject) => {
//             newFile.attachData(f.createReadStream('files'), {
//                 type: f.original.type
//             }, function (err) {
//                 if (err) return reject(err);
//                 var metadata;
//                 newFile.name(f.name());
//                 newFile.size(f.size());
//                 metadata = {
//                     owner: f.metadata.owner
//                 };
//                 newFile.metadata = metadata;
//                 newFile._id = Creator.Collections.instances._makeNewID();
//                 cfs[collection].insert(newFile);
//                 value.push(newFile._id);
//                 resolve();
//             });
//         });
//     }
    if (value.length > 0) {
        if (_.isString(recordFieldId)) {
            return value[0];
        } else {
            return value;
        }
    }
};

const getRecordFieldValue = async function (oField, wField, ofValue, wfValue, spaceId, enableAmisform) {
    if (enableAmisform && wField.steedos_field) {
        return wfValue
    }
    let value;
    var oFieldType = oField.data_type || oField.type;
    // 表单选人选组字段 至 对象 lookup master_detail类型字段同步
    if (['user', 'group'].includes(wField.type) && ['lookup', 'master_detail'].includes(oFieldType)
        && (['users', 'organizations'].includes(oField.reference_to) || ('space_users' == oField.reference_to && 'user' == oField.reference_to_field))) {
        if (!_.isEmpty(wfValue)) {
            if (oField.multiple && wField.is_multiselect) {
                value = _.compact(_.pluck(wfValue, 'id'))
            } else if (!oField.multiple && !wField.is_multiselect) {
                value = wfValue.id
            }
        }
    }
    else if (!oField.multiple && ['lookup', 'master_detail'].includes(oFieldType) && _.isString(oField.reference_to) && _.isString(wfValue)) {
        var oCollection = objectql.getObject(oField.reference_to)
        var referObjectNameFieldKey = await getObjectNameFieldKey(oField.reference_to);
        if (oCollection && referObjectNameFieldKey) {
            // 先认为此值是referObject _id字段值
            let referToField = oField.reference_to_field || '_id'
            var referData = await objectFindOne(oField.reference_to, { filters: [[referToField, '=', wfValue]], fields: [referToField] });
            if (referData) {
                value = referData[referToField];
            }

            // 其次认为此值是referObject NAME_FIELD_KEY值
            if (!referData) {
                var nameFieldKey = referObjectNameFieldKey;
                var referData = await objectFindOne(oField.reference_to, { filters: [['space', '=', spaceId], [nameFieldKey, '=', wfValue]], fields: ['_id'] });
                if (referData) {
                    value = referData._id;
                }
            }
        }
    }
    else if (oFieldType === "boolean") {
        var tmp_field_value = wfValue;
        if (['true', '是'].includes(tmp_field_value)) {
            value = true;
        } else if (['false', '否'].includes(tmp_field_value)) {
            value = false;
        } else {
            value = tmp_field_value;
        }
    }
    else if (oFieldType === 'number' || oFieldType === 'currency' || oFieldType === 'percent') {
        if (typeof (Number(wfValue)) === 'number' && !isNaN(Number(wfValue))) {
            value = Number(wfValue);
        }
    }
    else if (['lookup', 'master_detail'].includes(oFieldType) && wField.type === 'odata') {
        let referToField = oField.reference_to_field || '_id'
        if (oField.multiple && wField.is_multiselect) {
            value = _.compact(_.pluck(wfValue, referToField))
        } else if (!oField.multiple && !wField.is_multiselect) {
            if (!_.isEmpty(wfValue)) {
                value = wfValue[referToField]
            }
        } else {
            value = wfValue;
        }
    } else if (oFieldType == 'image' && wField.type == 'image') {
        var ids = ofValue;
        if (_.isString(ids)) {
            ids = [ids];
        }
        if (ids) {
            await new Promise((resolve, reject) => {
                cfs.images.remove({
                    '_id': { $in: ids }
                }, function(err) {
                    if (err) return reject(err);
                    resolve();
                });
            });
        }

        value = await getFileFieldValue(wfValue, 'image')

    } else if (oFieldType == 'file' && wField.type == 'file') {
        var ids = ofValue;
        if (_.isString(ids)) {
            ids = [ids];
        }
        if (ids) {
            await new Promise((resolve, reject) => {
                cfs.files.remove({
                    '_id': { $in: ids }
                }, function(err) {
                    if (err) return reject(err);
                    resolve();
                });
            });
        }
        value = await getFileFieldValue(wfValue, 'file')
    } else if (['lookup', 'master_detail'].includes(oFieldType) && wField.type == 'lookup') {
        value = wfValue ? wfValue : null
    }
    // 日期、日期时间
    else if (oFieldType === 'date' || oFieldType === 'datetime' || oFieldType === 'time') {
        if (wfValue && _.isDate(new Date(wfValue))) {
            value = new Date(wfValue);
        }
    }
    else if (oField.multiple && oFieldType == 'select' && wField.type == 'multiSelect') {
        value = wfValue ? wfValue.split(',') : null;
    }
    else {
        value = wfValue;
    }
    return value;
};

const updateLookupOrMasterDetailFieldValues = async function (field_map_back, values, ins, objectInfo, record) {
    var field_map_back = field_map_back || [];
    var objectFields = objectInfo.fields;

    for (const fm of field_map_back) {
        if (values.hasOwnProperty(fm.workflow_field)) {
            if (fm.object_field.indexOf('.') > -1) {
                var temObjFields = fm.object_field.split('.');
                if (temObjFields.length === 2) {
                    var objField = temObjFields[0];
                    var referObjField = temObjFields[1];
                    var oField = objectFields[objField];
                    if (oField && !oField.multiple && ['lookup', 'master_detail'].includes(oField.type) && _.isString(oField.reference_to)) {
                        var oCollection = objectql.getObject(oField.reference_to)
                        if (oCollection && record && record[objField]) {
                            var referSetObj = {};
                            referSetObj[referObjField] = values[fm.workflow_field];
                            await objectUpdate(oField.reference_to, record[objField], referSetObj)
                        }
                    }
                }
            }
        } else if (fm.workflow_field.startsWith('instance.')) {
            var insField = fm.workflow_field.split('instance.')[1];
            if (InstanceRecordQueue.syncInsFields.includes(insField)) {
                if (fm.object_field.indexOf('.') > -1) {
                    var temObjFields = fm.object_field.split('.');
                    if (temObjFields.length === 2) {
                        var objField = temObjFields[0];
                        var referObjField = temObjFields[1];
                        var oField = objectFields[objField];
                        if (oField && !oField.multiple && ['lookup', 'master_detail'].includes(oField.type) && _.isString(oField.reference_to)) {
                            var oCollection = objectql.getObject(oField.reference_to)
                            if (oCollection && record && record[objField]) {
                                var referSetObj = {};
                                referSetObj[referObjField] = ins[insField];
                                await objectUpdate(oField.reference_to, record[objField], referSetObj)
                            }
                        }
                    }
                }
            }
        }
    }
}

InstanceRecordQueue.Configure = function (options) {
    var self = this;
    options = _.extend({
        sendTimeout: 60000, // Timeout period
    }, options);

    // Block multiple calls
    if (isConfigured) {
        throw new Error('InstanceRecordQueue.Configure should not be called more than once!');
    }

    isConfigured = true;

    // Add debug info
    if (InstanceRecordQueue.debug) {
        console.log('InstanceRecordQueue.Configure', options);
    }

    // Universal send function
    var _querySend = function (doc) {
        if (InstanceRecordQueue.sendDoc) {
            InstanceRecordQueue.sendDoc(doc);
        }

        return {
            doc: [doc._id]
        };
    };

    self.serverSend = function (doc) {
        doc = doc || {};
        return _querySend(doc);
    };

    var isSendingDoc = false;

    if (options.sendInterval !== null) {
        var sendDoc = async function (doc) {
            // Reserve doc
            var now = +new Date();
            var timeoutAt = now + options.sendTimeout;
            var reserved = await InstanceRecordQueue.collection.update({
                _id: doc._id,
                sent: false, // xxx: need to make sure this is set on create
                sending: {
                    $lt: now
                }
            }, {
                $set: {
                    sending: timeoutAt,
                }
            });

            // Make sure we only handle docs reserved by this instance
            if (reserved) {
                // Send
                var result = await self.serverSend(doc);

                if (!options.keepDocs) {
                    // Pr. Default we will remove docs
                    await InstanceRecordQueue.collection.remove({
                        _id: doc._id
                    });
                } else {
                    // Update
                    await InstanceRecordQueue.collection.update({
                        _id: doc._id
                    }, {
                        $set: {
                            // Mark as sent
                            sent: true,
                            // Set the sent date
                            sentAt: new Date(),
                            // Not being sent anymore
                            sending: 0
                        }
                    });
                }
            } // Else could not reserve
        }; // EO sendDoc

        sendWorker(async function () {
            if (isSendingDoc) {
                return;
            }
            // Set send fence
            isSendingDoc = true;

            var batchSize = options.sendBatchSize || 1;

            var now = +new Date();

            // Find docs that are not being or already sent
            var pendingDocs = await InstanceRecordQueue.collection.find({
                $and: [
                    // Message is not sent
                    {
                        sent: false
                    },
                    // And not being sent by other instances
                    {
                        sending: {
                            $lt: now
                        }
                    },
                    // And no error
                    {
                        errMsg: {
                            $exists: false
                        }
                    }
                ]
            }, {
                // Sort by created date
                sort: {
                    createdAt: 1
                },
                limit: batchSize
            }).toArray();
            for (const doc of pendingDocs) {
                try {
                    await sendDoc(doc);
                } catch (error) {
                    console.error(error.stack);
                    console.log('InstanceRecordQueue: Could not send doc id: "' + doc._id + '", Error: ' + error.message);
                    await InstanceRecordQueue.collection.update({
                        _id: doc._id
                    }, {
                        $set: {
                            // error message
                            errMsg: error.message
                        }
                    });
                }
            }

            // Remove the send fence
            isSendingDoc = false;
        }, options.sendInterval || 15000); // Default every 15th sec
    } else {
        if (InstanceRecordQueue.debug) {
            console.log('InstanceRecordQueue: Send server is disabled');
        }
    }
};

//TODO 同步附件
InstanceRecordQueue.syncAttach = async function (sync_attachment, insId, spaceId, newRecordId, objectName) {
    console.log(`TODO 同步附件 syncAttach`)
    // if (sync_attachment == "lastest") {
    //     const files = await cfs.instances.find({
    //         'metadata.instance': insId,
    //         'metadata.current': true
    //     }).toArray();

    //     for (const f of files) {
    //         if (!f.hasStored('instances')) {
    //             console.error('syncAttach-file not stored: ', f._id);
    //             continue;
    //         }
    //         var newFile = new FS.File(),
    //             cmsFileId = Creator.getCollection('cms_files')._makeNewID();
            
    //         await new Promise((resolve, reject) => {
    //             newFile.attachData(f.createReadStream('instances'), {
    //                 type: f.original.type
    //             }, function (err) {
    //                 if (err) return reject(err);
    //                 newFile.name(f.name());
    //                 newFile.size(f.size());
    //                 var metadata = {
    //                     owner: f.metadata.owner,
    //                     owner_name: f.metadata.owner_name,
    //                     space: spaceId,
    //                     record_id: newRecordId,
    //                     object_name: objectName,
    //                     parent: cmsFileId
    //                 };

    //                 newFile.metadata = metadata;
    //                 cfs.files.insert(newFile);
    //                 resolve();
    //             });
    //         });

    //         await new Promise((resolve, reject) => {
    //             newFile.once('stored', function (storeName) {
    //                 Creator.getCollection('cms_files').insert({
    //                     _id: cmsFileId,
    //                     parent: {
    //                         o: objectName,
    //                         ids: [newRecordId]
    //                     },
    //                     size: newFile.size(),
    //                     name: newFile.name(),
    //                     extention: newFile.extension(),
    //                     space: spaceId,
    //                     versions: [newFile._id],
    //                     owner: f.metadata.owner,
    //                     created_by: f.metadata.owner,
    //                     modified_by: f.metadata.owner
    //                 });
    //                 resolve();
    //             });
    //             newFile.once('error', function (error) {
    //                 console.error('syncAttach-error: ', error);
    //                 reject(error);
    //             });
    //         });
    //     }
    // } else if (sync_attachment == "all") {
    //     var parents = [];
    //     const files = await cfs.instances.find({
    //         'metadata.instance': insId
    //     }).toArray();

    //     for (const f of files) {
    //         if (!f.hasStored('instances')) {
    //             console.error('syncAttach-file not stored: ', f._id);
    //             continue;
    //         }
    //         var newFile = new FS.File(),
    //             cmsFileId = f.metadata.parent;

    //         if (!parents.includes(cmsFileId)) {
    //             parents.push(cmsFileId);
    //             await Creator.getCollection('cms_files').insert({
    //                 _id: cmsFileId,
    //                 parent: {
    //                     o: objectName,
    //                     ids: [newRecordId]
    //                 },
    //                 space: spaceId,
    //                 versions: [],
    //                 owner: f.metadata.owner,
    //                 created_by: f.metadata.owner,
    //                 modified_by: f.metadata.owner
    //             });
    //         }

    //         await new Promise((resolve, reject) => {
    //             newFile.attachData(f.createReadStream('instances'), {
    //                 type: f.original.type
    //             }, function (err) {
    //                 if (err) return reject(err);
    //                 newFile.name(f.name());
    //                 newFile.size(f.size());
    //                 var metadata = {
    //                     owner: f.metadata.owner,
    //                     owner_name: f.metadata.owner_name,
    //                     space: spaceId,
    //                     record_id: newRecordId,
    //                     object_name: objectName,
    //                     parent: cmsFileId
    //                 };

    //                 newFile.metadata = metadata;
    //                 cfs.files.insert(newFile);
    //                 resolve();
    //             });
    //         });

    //         await new Promise((resolve, reject) => {
    //             newFile.once('stored', function (storeName) {
    //                 if (f.metadata.current == true) {
    //                     Creator.getCollection('cms_files').update(cmsFileId, {
    //                         $set: {
    //                             size: newFile.size(),
    //                             name: newFile.name(),
    //                             extention: newFile.extension(),
    //                         },
    //                         $addToSet: {
    //                             versions: newFile._id
    //                         }
    //                     });
    //                 } else {
    //                     Creator.getCollection('cms_files').update(cmsFileId, {
    //                         $addToSet: {
    //                             versions: newFile._id
    //                         },
    //                         $set: {
    //                             modified: new Date()
    //                         }
    //                     });
    //                 }
    //                 resolve();
    //             });
    //             newFile.once('error', function (error) {
    //                 console.error('syncAttach-error: ', error);
    //                 reject(error);
    //             });
    //         });
    //     }
    // }
}

InstanceRecordQueue.syncInsFields = ['name', 'submitter_name', 'applicant_name', 'applicant_organization_name', 'applicant_organization_fullname', 'state',
    'current_step_name', 'flow_name', 'category_name', 'submit_date', 'finish_date', 'final_decision', 'applicant_organization', 'applicant_company'
];

InstanceRecordQueue.syncValues = async function (field_map_back, values, ins, objectInfo, field_map_back_script, record, enableAmisform) {
    var
        obj = {},
        tableFieldCodes = [],
        tableFieldMap = [],
        tableToRelatedMap = {};

    field_map_back = field_map_back || [];

    var spaceId = ins.space;

    var form = await objectql.getObject("forms").findOne(ins.form);
    var formFields = null;
    if (form.current._id === ins.form_version) {
        formFields = form.current.fields || [];
    } else {
        var formVersion = _.find(form.historys, function (h) {
            return h._id === ins.form_version;
        })
        formFields = formVersion ? formVersion.fields : [];
    }

    var objectFields = objectInfo.fields;
    var objectFieldKeys = _.keys(objectFields);
    var relatedObjects = await getRelateds(objectInfo.name);
    var relatedObjectsKeys = _.pluck(relatedObjects, 'object_name');
    var formTableFields = _.filter(formFields, function (formField) {
        return formField.type === 'table'
    });
    var formTableFieldsCode = _.pluck(formTableFields, 'code');

    for (const fm of field_map_back) {
        //workflow 的子表到creator object 的相关对象
        var relatedObjectField = getRelatedObjectField(relatedObjectsKeys, fm.object_field);
        var formTableField = getFormTableField(formTableFieldsCode, fm.workflow_field);
        if (relatedObjectField) {
            var oTableCode = fm.object_field.split('.')[0];
            var oTableFieldCode = fm.object_field.split('.')[1];
            var tableToRelatedMapKey = oTableCode;
            if (!tableToRelatedMap[tableToRelatedMapKey]) {
                tableToRelatedMap[tableToRelatedMapKey] = {}
            }

            if (formTableField) {
                var wTableCode = fm.workflow_field.split('.')[0];
                tableToRelatedMap[tableToRelatedMapKey]['_FROM_TABLE_CODE'] = wTableCode
            }

            tableToRelatedMap[tableToRelatedMapKey][oTableFieldCode] = fm.workflow_field
        }
        // 判断是否是子表字段
        else if (fm.workflow_field.indexOf('.$.') > 0 && fm.object_field.indexOf('.$.') > 0) {
            var wTableCode = fm.workflow_field.split('.$.')[0];
            var oTableCode = fm.object_field.split('.$.')[0];
            if (values.hasOwnProperty(wTableCode) && _.isArray(values[wTableCode])) {
                tableFieldCodes.push(JSON.stringify({
                    workflow_table_field_code: wTableCode,
                    object_table_field_code: oTableCode
                }));
                tableFieldMap.push(fm);
            }
        }
        else if (values.hasOwnProperty(fm.workflow_field)) {
            var wField = null;

            _.each(formFields, function (ff) {
                if (!wField) {
                    if (ff.code === fm.workflow_field) {
                        wField = ff;
                    } else if (ff.type === 'section') {
                        _.each(ff.fields, function (f) {
                            if (!wField) {
                                if (f.code === fm.workflow_field) {
                                    wField = f;
                                }
                            }
                        })
                    }
                }
            })

            var oField = objectFields[fm.object_field];

            if (oField) {
                if (!wField) {
                    console.log('fm.workflow_field: ', fm.workflow_field)
                }
                const ofValue = record ? record[fm.object_field] : null
                const wfValue = values[fm.workflow_field]
                obj[fm.object_field] = await getRecordFieldValue(oField, wField, ofValue, wfValue, spaceId, enableAmisform)
            } else {
                if (fm.object_field.indexOf('.') > -1) {
                    var temObjFields = fm.object_field.split('.');
                    if (temObjFields.length === 2) {
                        var objField = temObjFields[0];
                        var referObjField = temObjFields[1];
                        var oField = objectFields[objField];
                        if (!oField.multiple && ['lookup', 'master_detail'].includes(oField.type) && _.isString(oField.reference_to)) {
                            var oCollection = objectql.getObject(oField.reference_to)
                            if (oCollection && record && record[objField]) {
                                var referSetObj = {};
                                referSetObj[referObjField] = values[fm.workflow_field];
                                await objectUpdate(oField.reference_to, record[objField], referSetObj)
                            }
                        }
                    }
                }
            }
        }
        else {
            if (fm.workflow_field.startsWith('instance.')) {
                var insField = fm.workflow_field.split('instance.')[1];
                if (InstanceRecordQueue.syncInsFields.includes(insField)) {
                    if (fm.object_field.indexOf('.') < 0) {
                        obj[fm.object_field] = ins[insField];
                    } else {
                        var temObjFields = fm.object_field.split('.');
                        if (temObjFields.length === 2) {
                            var objField = temObjFields[0];
                            var referObjField = temObjFields[1];
                            var oField = objectFields[objField];
                            if (!oField.multiple && ['lookup', 'master_detail'].includes(oField.type) && _.isString(oField.reference_to)) {
                                var oCollection = objectql.getObject(oField.reference_to)
                                if (oCollection && record && record[objField]) {
                                    var referSetObj = {};
                                    referSetObj[referObjField] = ins[insField];
                                    await objectUpdate(oField.reference_to, record[objField], referSetObj)
                                }
                            }
                        }
                    }
                }
            } else {
                if (ins[fm.workflow_field]) {
                    obj[fm.object_field] = ins[fm.workflow_field];
                }
            }
        }
    }

    for (const tfc of _.uniq(tableFieldCodes)) {
        var c = JSON.parse(tfc);
        obj[c.object_table_field_code] = [];
        for (const tr of values[c.workflow_table_field_code]) {
            var newTr = {};
            _.each(tr, function (v, k) {
                tableFieldMap.forEach(function (tfm) {
                    if (tfm.workflow_field == (c.workflow_table_field_code + '.$.' + k)) {
                        var oTdCode = tfm.object_field.split('.$.')[1];
                        newTr[oTdCode] = v;
                    }
                })
            })
            if (!_.isEmpty(newTr)) {
                obj[c.object_table_field_code].push(newTr);
            }
        }
    }

    var relatedObjs = {};
    var getRelatedFieldValue = function (valueKey, parent) {
        return valueKey.split('.').reduce(function (o, x) {
            return o[x];
        }, parent);
    };

    for (const [key, map] of Object.entries(tableToRelatedMap)) {
        var tableCode = map._FROM_TABLE_CODE;
        if (!tableCode) {
            console.warn('tableToRelated: [' + key + '] missing corresponding table.')
        } else {
            var relatedObjectName = key;
            var relatedObjectValues = [];
            var relatedObjectFields = await getObjectFields(relatedObjectName)
            for (const tableValueItem of values[tableCode]) {
                let relateRecord;
                if (tableValueItem._id) {
                    relateRecord = await objectFindOne(relatedObjectName, { filters: [['_id', '=', tableValueItem._id]] });
                }
                var relatedObjectValue = {};
                for (const [fieldKey, valueKey] of Object.entries(map)) {
                    if (fieldKey != '_FROM_TABLE_CODE') {
                        if (valueKey.startsWith('instance.')) {
                            relatedObjectValue[fieldKey] = getRelatedFieldValue(valueKey, { 'instance': ins });
                        }
                        else {
                            var relatedObjectFieldValue, formFieldKey;
                            if (valueKey.startsWith(tableCode + '.')) {
                                formFieldKey = valueKey.split(".")[1];
                                relatedObjectFieldValue = getRelatedFieldValue(valueKey, { [tableCode]: tableValueItem });
                            } else {
                                formFieldKey = valueKey;
                                relatedObjectFieldValue = getRelatedFieldValue(valueKey, values)
                            }
                            var formField = getFormField(formFields, formFieldKey);
                            var relatedObjectField = relatedObjectFields[fieldKey];
                            if (!relatedObjectField || !formField) {
                                continue;
                            }
                            const ofValue = relateRecord ? relateRecord[fieldKey] : null;
                            relatedObjectValue[fieldKey] = await getRecordFieldValue(relatedObjectField, formField, ofValue, relatedObjectFieldValue, spaceId, enableAmisform);
                        }
                    }
                }
                relatedObjectValue['_table'] = {
                    _id: tableValueItem["_id"],
                    _code: tableCode
                };
                relatedObjectValues.push(relatedObjectValue);
            }
            relatedObjs[relatedObjectName] = relatedObjectValues;
        }
    }

    if (field_map_back_script) {
        _.extend(obj, InstanceRecordQueue.evalFieldMapBackScript(field_map_back_script, ins));
    }
    // 过滤掉非法的key
    var filterObj = {};

    _.each(_.keys(obj), function (k) {
        if (objectFieldKeys.includes(k)) {
            filterObj[k] = obj[k];
        }
    })

    return {
        mainObjectValue: filterObj,
        relatedObjectsValue: relatedObjs
    };
}

InstanceRecordQueue.evalFieldMapBackScript = function (field_map_back_script, ins) {
    var script = "module.exports = function (instance) { " + field_map_back_script + " }";
    var func = _eval(script, "field_map_script");
    var values = func(ins);
    if (_.isObject(values)) {
        return values;
    } else {
        console.error("evalFieldMapBackScript: 脚本返回值类型不是对象");
    }
    return {}
}

InstanceRecordQueue.syncRelatedObjectsValue = async function (mainRecordId, relatedObjects, relatedObjectsValue, spaceId, ins, locked = false) {
    var insId = ins._id;

    for (const relatedObject of relatedObjects) {
        var tableMap = {};
        for (const relatedObjectValue of (relatedObjectsValue[relatedObject.object_name] || [])) {
            var table_id = relatedObjectValue._table._id;
            var table_code = relatedObjectValue._table._code;
            if (!tableMap[table_code]) {
                tableMap[table_code] = []
            };
            tableMap[table_code].push(table_id);
            var oldRelatedRecord = await objectFindOne(relatedObject.object_name, {
                filters: [
                    [relatedObject.foreign_key, '=', mainRecordId], 
                    [['_id', '=', relatedObjectValue._table._id], 'or', [['_table._id', '=', relatedObjectValue._table._id], ['_table._code', '=', relatedObjectValue._table._code]]]
                ]
            })
            if (oldRelatedRecord) {
                await objectUpdate(relatedObject.object_name, oldRelatedRecord._id, relatedObjectValue)
            } else {
                relatedObjectValue[relatedObject.foreign_key] = mainRecordId;
                relatedObjectValue.space = spaceId;
                relatedObjectValue.owner = relatedObjectValue.owner || ins.applicant;
                relatedObjectValue.created_by = ins.applicant;
                relatedObjectValue.modified_by = ins.applicant;
                relatedObjectValue._id = await makeNewID(relatedObject.object_name);
                relatedObjectValue.locked = locked
                await objectInsert(relatedObject.object_name, relatedObjectValue)
            }
        }
        //清理申请单上被删除子表记录对应的相关表记录
        for (let [tableCode, tableIds] of Object.entries(tableMap)) {
            tableIds = _.compact(tableIds);
            var docsForRemove = await objectFind(relatedObject.object_name, { filters: [[relatedObject.foreign_key, '=', mainRecordId], ['_table._code', '=', tableCode]] });
            for (const doc of docsForRemove) {
                if (!tableIds.includes(doc._table._id)) {
                    await objectRemove(relatedObject.object_name, doc._id)
                }
            }
        }
    }
}

InstanceRecordQueue.sendDoc = async function (doc) {
    if (InstanceRecordQueue.debug) {
        console.log("sendDoc");
        console.log(doc);
    }

    var insId = doc.info.instance_id,
        records = doc.info.records;
    var fields = {
        flow: 1,
        values: 1,
        applicant: 1,
        space: 1,
        form: 1,
        form_version: 1,
        traces: 1
    };
    InstanceRecordQueue.syncInsFields.forEach(function (f) {
        fields[f] = 1;
    })
    
    var ins = await objectql.getObject('instances').findOne(insId, {
        fields: fields
    })
    
    var values = ins.values,
        spaceId = ins.space;

    const flowId = ins.flow
    const flow = await objectql.getObject('flows').findOne(flowId, { fields: { enable_amisform: 1 } })
    const enableAmisform = flow.enable_amisform

    if (records && !_.isEmpty(records)) {
        // 此情况属于从creator中发起审批，或者已经从Apps同步到了creator
        var objectName = records[0].o;
        var ow = await objectql.getObject('object_workflows').findOne({
            object_name: objectName,
            flow_id: ins.flow
        });
        
        var objectCollection = objectql.getObject(objectName),
            sync_attachment = ow.sync_attachment;
        syncDirection = ow.sync_direction || 'both';
        var objectInfo = await getObjectConfig(objectName);
        
        const recordsToProcess = await objectFind(objectName, { filters: [['_id', 'in', records[0].ids]] });
        
        for (const record of recordsToProcess) {
            try {
                if (!['both', 'ins_to_obj'].includes(syncDirection)) {
                    continue;
                }
                var lock_record_after_approval = ow.lock_record_after_approval || false;
                var syncValues = await InstanceRecordQueue.syncValues(ow.field_map_back, values, ins, objectInfo, ow.field_map_back_script, record, enableAmisform)
                var setObj = syncValues.mainObjectValue;

                var instance_state = ins.state;
                if (ins.state === 'completed') {
                    setObj.locked = lock_record_after_approval
                    if (ins.final_decision) {
                        instance_state = ins.final_decision;
                    }
                }
                setObj['instances.0.state'] = setObj.instance_state = instance_state;
                await objectUpdate(objectName, record._id, setObj);

                var relatedObjects = await getRelateds(ow.object_name);
                var relatedObjectsValue = syncValues.relatedObjectsValue;
                await InstanceRecordQueue.syncRelatedObjectsValue(record._id, relatedObjects, relatedObjectsValue, spaceId, ins);

                // 以最终申请单附件为准，旧的record中附件删除
                
                const cfColl = await getCollection('cms_files');
                await cfColl.deleteMany({
                    'parent': {
                        o: objectName,
                        ids: [record._id]
                    }
                });

                const cfsColl = await getCollection('cfs.files.filerecord');
                await cfsColl.deleteMany({
                    'metadata.record_id': record._id
                })
                
                // 同步新附件
                await InstanceRecordQueue.syncAttach(sync_attachment, insId, record.space, record._id, objectName);
                // 执行公式
                await runQuoted(objectName, record._id);
            } catch (error) {
                console.error(error);

                await objectUpdateMany(objectName, [['_id', '=', record._id], ['instances._id', '=', insId]], {
                    'instances.$.state': 'pending',
                    'instance_state': 'pending'
                })

                const cfColl = await getCollection('cms_files');
                await cfColl.deleteMany({
                    'parent': {
                        o: objectName,
                        ids: [record._id]
                    }
                });

                const cfsColl = await getCollection('cfs.files.filerecord');
                await cfsColl.deleteMany({
                    'metadata.record_id': record._id
                })

                throw error;
            }
        }
    } else {
        // 此情况属于从apps中发起审批
        const owsColl = await getCollection('object_workflows');
        const ows = await owsColl.find({
            flow_id: ins.flow
        }).toArray();
        
        for (const ow of ows) {
            try {
                var sync_attachment = ow.sync_attachment,
                    newRecordId = await makeNewID(ow.object_name),
                    objectName = ow.object_name,
                    syncDirection = ow.sync_direction || 'both';
                lock_record_after_approval = ow.lock_record_after_approval || false;

                if (!['both', 'ins_to_obj'].includes(syncDirection)) {
                    continue;
                }

                var objectInfo = await getObjectConfig(ow.object_name);
                var syncValues = await InstanceRecordQueue.syncValues(ow.field_map_back, values, ins, objectInfo, ow.field_map_back_script, null, enableAmisform);
                var newObj = syncValues.mainObjectValue;

                newObj._id = newRecordId;
                newObj.space = spaceId;
                newObj.name = newObj.name || ins.name;

                newObj.locked = true;

                var instance_state = ins.state;
                if (ins.state === 'completed' && ins.final_decision) {
                    instance_state = ins.final_decision;
                    newObj.locked = lock_record_after_approval;
                }
                newObj.instances = [{
                    _id: insId,
                    state: instance_state
                }];
                newObj.instance_state = instance_state;

                newObj.owner = newObj.owner || ins.applicant;
                newObj.created_by = ins.applicant;
                newObj.modified_by = ins.applicant;
                
                var r = await objectInsert(ow.object_name, newObj);
                const insColl = await getCollection('instances');
                if (r) {
                    await insColl.update({_id: ins._id}, {
                        $push: {
                            record_ids: {
                                o: objectName,
                                ids: [newRecordId]
                            }
                        },
                        $set: {
                            modified: new Date()
                        }
                    });
                    
                    var relatedObjects = await getRelateds(ow.object_name);
                    var relatedObjectsValue = syncValues.relatedObjectsValue;
                    await InstanceRecordQueue.syncRelatedObjectsValue(newRecordId, relatedObjects, relatedObjectsValue, spaceId, ins, newObj.locked);
                    
                    // workflow里发起审批后，同步时也可以修改相关表的字段值 #1183
                    var record = await objectFindOne(objectName, { filters: [['_id', '=', newRecordId]] });
                    await updateLookupOrMasterDetailFieldValues(ow.field_map_back, values, ins, objectInfo, record)
                }

                // 附件同步
                await InstanceRecordQueue.syncAttach(sync_attachment, insId, spaceId, newRecordId, objectName);
                // 执行公式
                await runQuoted(objectName, newRecordId);
            } catch (error) {
                console.error(error);

                await objectRemove(ow.object_name, newRecordId);
                const insColl = await getObject('instances');
                await insColl.update(ins._id, {
                    $pull: {
                        record_ids: {
                            o: objectName,
                            ids: [newRecordId]
                        }
                    },
                    $set: {
                        modified: new Date()
                    }
                });

                const cfColl = await getCollection('cms_files');
                await cfColl.deleteMany({
                    'parent': {
                        o: objectName,
                        ids: [newRecordId]
                    }
                });

                const cfsColl = await getCollection('cfs.files.filerecord');
                await cfsColl.deleteMany({
                    'metadata.record_id': newRecordId
                })
                throw error;
            }
        }
    }

    if (doc._id) {
        await InstanceRecordQueue.collection.update({_id: doc._id}, {
            $set: {
                'info.sync_date': new Date()
            }
        });
    }
}

module.exports = InstanceRecordQueue;