"use strict";

var q = require("q");

var duty = require("duty");
var later = require("later");
var moment = require('moment');

var smsHelper = null;
var emailHelper = require('../email/EmailHelper');
var RuleHelper = require('../../shared/utils/RuleHelper');
var IncludeRuleSetUtils = require('./ServerIncludeRuleSetUtils');
var UtilDate = require('../utils/UtilDate');
var UtilArray = require('../../shared/utils/UtilArray');
var SharedConstants = require('../../shared/constants/SharedConstants');
var SearchService = require('../search/SearchService');
var SearchUtils = require('./SearchUtils');
var UtilElasticSearch = require('../utils/UtilElasticSearch');
var PhoneService = require('../phone/PhoneService');
var AjaxServer = require('./AjaxServer');
var UtilPackage = require('../utils/UtilPackage');
var UtilBase64 = require('../utils/UtilBase64');
var _ = require('lodash/lang');
var pjson = require('../../package.json');
var NodeVM = require('vm2').NodeVM;
const SearchResultsProcessor   = require('../../shared/utils/SearchResultsProcessor');
const ruleService = require('../database/dataServices/RuleService');
const { promiseTimeout, checkExitSignal,  clearExistingTimeoutFunction } = require('../utils/UtilTimeout');
const { setActiveProcess } = require('../utils/UtilProcess');
const { RulesetError } = require('../../shared/utils/UtilRules');
const lodashCloneDeep = require('lodash.clonedeep');

/*
 * Including ruleset processing.
 * Include ruleset syntax: #include "rulesetName";
 */
function addIncludesToRuleset(ruleFlow, userId, callback) {
    var includeNames = IncludeRuleSetUtils.getIncludeNames(ruleFlow);
    var duplicate = IncludeRuleSetUtils.getDuplicateIncludes(ruleFlow);

    if (duplicate) {
        callback({
            status: SharedConstants.STATUS_ERROR,
            message: "Included ruleset(s) contains duplicate: " + duplicate
        });
    } else if(includeNames && includeNames.length) {
        var remainIncludeNames = [];
        //remove commented #include
        for (var i = 0; i < includeNames.length; i++) {
            var beforeLength = ruleFlow.length;
            var commentLineExp = "//[\t ]*#include[\t ]+['\"]" + includeNames[i] + "['\"][\t ]*.*";
            ruleFlow = ruleFlow.replace(new RegExp(commentLineExp, "g"), "");

            if (beforeLength === ruleFlow.length) {
                //Keep not commented include name
                remainIncludeNames.push(includeNames[i]);
            }
        }
        if (remainIncludeNames.length) {
            IncludeRuleSetUtils.loadIncludeDocs(remainIncludeNames, userId, function (includeRuleDocs) {
                if (includeRuleDocs && includeRuleDocs.length === remainIncludeNames.length) {
                    for (var id = 0; id < includeRuleDocs.length; id++) {
                        var includeDoc = includeRuleDocs[id];

                        //replace #include by script for JS
                        var expJS = "#include[\t ]+['\"]" + includeDoc.name + "['\"][\t ]*,";
                        ruleFlow = ruleFlow.replace(new RegExp(expJS, "g"), includeDoc.ruleSetSnippet + ',');
                    }

                    callback({
                        status: SharedConstants.STATUS_SUCCESS,
                        ruleFlow: ruleFlow
                    });
                } else {
                    var missedIncludes;
                    if (includeRuleDocs && includeRuleDocs.length) {
                        missedIncludes = [];
                        for (var i = 0; i < remainIncludeNames.length; i++) {
                            var res = false;
                            for (var j = 0; j < includeRuleDocs.length; j++) {
                                if (includeRuleDocs[j].name === remainIncludeNames[i]) {
                                    res = true;
                                    break;
                                }
                            }
                            if (!res) {
                                missedIncludes.push(remainIncludeNames[i]);
                            }
                        }
                    } else {
                        missedIncludes = remainIncludeNames;
                    }
                    logger.error("There are missing ruleset includes for this document: " +
                        missedIncludes +
                        ". Please make sure the ruleset include documents exist and " +
                        "the correct access keys are configured for them.");
                    callback({
                        status: SharedConstants.STATUS_ERROR,
                        message: "Ruleset Include(s) not found: " + missedIncludes
                    });
                }
            });
        } else {
            if (typeof callback === 'function') {
                callback({
                    status: SharedConstants.STATUS_SUCCESS,
                    ruleFlow: ruleFlow
                });
            }
        }
    } else {
        if (typeof callback === 'function') {
            callback({
                status: SharedConstants.STATUS_SUCCESS,
                ruleFlow: ruleFlow
            });
        }
    }
}

function processRuleFlow(document, documentContext, ruleFlowString, ruleScope, callback) {
    var thisScope = lodashCloneDeep(module.exports);
    if (ruleScope) {
        for (var key in ruleScope) {
            thisScope[key] = ruleScope[key];
        }
    }

    if(!documentContext) {
        documentContext = {};
    }
    documentContext.logger = logger;
    documentContext.RSLogger = RSLogger;
    documentContext.appVersion = pjson.version;

    var rsLogger = new RSLogger(documentContext);
    documentContext.rsLogger = rsLogger;
    thisScope.rsLogger = rsLogger;

    const addIncludeCallback = (processedRuleFlowResult) => {
        if(processedRuleFlowResult.status === SharedConstants.STATUS_ERROR) {
            callback(processedRuleFlowResult);
        } else {
            var ruleset = processedRuleFlowResult.ruleFlow;
            if (ruleset) {
                const { getBuiltInWhitelist, getExternalWhitelist, getRoot } = require("./../utils/UtilSandbox");

                var vm = new NodeVM({
                    sandbox: {},
                    require: {
                        external: getExternalWhitelist(),
                        builtin: getBuiltInWhitelist(),
                        root: getRoot()
                    }
                });

                // run the ruleset script via sandbox.
                // Global, built-in and external libraries wont be available not unless specified in the above NodeVM
                try {
                    var vmRuleSet = vm.run("module.exports = \n " + ruleset + ";", getRoot() + "/vm.js");
                    RuleHelper.processJsonRuleset(document, documentContext, vmRuleSet, thisScope, result => {
                        var status = result ? result.status : SharedConstants.STATUS_SUCCESS;
                        if (!status || status.toLowerCase() !== SharedConstants.STATUS_ERROR) {
                            rsLogger.storeMessages();
                        }

                        callback(result);
                    });
                } catch(e) {
                    const ruleSetName = documentContext.ruleSetDoc.objectEventDescriptor;
                    const errorMessage = `Error in Ruleset ${ruleSetName}: ${e.message}`;

                    const result = {
                        status:  SharedConstants.STATUS_ERROR,
                        message: errorMessage
                    };

                    callback(result);
                }
            } else {
                callback();
            }
        }
    };

    addIncludesToRuleset(ruleFlowString, documentContext.user.documentId, addIncludeCallback); 
}

function pushError(msg, callback) {
    logger.debug(msg);
    if (typeof callback === 'function') {
        callback(msg);
    }
};

function getRuleSetId(event, templateId, userId) {

    logger.info("Starting get RuleSetId...");
    var deferred = q.defer();

    ruleService.getRuleSetId(userId, event, templateId).then(
        function successFunc(documentRecord) {
            if (documentRecord && documentRecord.length >0) {
                var foundId = documentRecord[0].documentId;
                logger.info("Get ruleset done: " + foundId);
                deferred.resolve(foundId);
            } else {
                deferred.reject(new Error("RS Id not found."));
            }
        },
        function errorFunc(err) {
            deferred.reject(new Error("RS Id not found."));
            logger.info("Get ruleset not found with query: " + JSON.stringify(query));
        }
    );

    return deferred.promise;
}


function createSystemAction(document, dueDate, ruleSetId, userId) {
    if (document) {
        logger.info("Creating system action on server side...");

        //deleting the objectId field _id - createSystemAction, createScheduleNewSystemAction and
        //createAndProcessSystemAction functions are used outside formbird and document argument may contain _id
        delete document._id;

        var systemActionRecord = {};
        systemActionRecord.document = document;
        systemActionRecord.dueDate = dueDate;
        systemActionRecord.ruleSetId = ruleSetId;
        systemActionRecord.actionedDate = null;
        systemActionRecord.userId = userId;

        systemActionRecord.systemHeader = {};
        systemActionRecord.systemHeader.systemType = SharedConstants.SYSTEM_TYPE_SYSTEM_ACTION;
        systemActionRecord.systemHeader.summaryName = SharedConstants.SYSTEM_TYPE_SYSTEM_ACTION;
        systemActionRecord.systemHeader.summaryDescription = SharedConstants.SYSTEM_TYPE_SYSTEM_ACTION +
            " for document: " + document.documentId;
        systemActionRecord.systemHeader.excludeGeneralSearch = true;

        return DataService.insert(systemActionRecord, userId);
    } else {
        var deferred = q.defer();
        var error = "Error in createSystemAction: No document provided.";
        logger.error(error);
        deferred.reject(new Error(error));

        return deferred.promise;
    }
}

function processSystemAction(systemActionDocument, user, callback) {
    if (systemActionDocument && user) {
        logger.info("Processing SystemAction " + systemActionDocument.documentId + '. RuleSetId ' + systemActionDocument.ruleSetId);

        try {
            // Get Ruleset document from system action document
            DataService.findOne(systemActionDocument.ruleSetId, user.documentId)
                .then(
                function successFunc(documentRecord) {
                    logger.info('RuleSet document for processing systemaction: ' + systemActionDocument.ruleSetId + ' found.');

                    var ruleSetDSL = documentRecord.ruleSetDSL;
                    if (ruleSetDSL && ruleSetDSL.length) {
                        try {
                            var documentContext = {};
                            documentContext.user = user;
                            processRuleFlow(systemActionDocument.document, documentContext, ruleSetDSL, module.exports,
                                function (result) {
                                    callback(null, result);
                                });
                        } catch (e) {
                            var err = 'Error : ' + e.message;
                            logger.info(err);
                            markSystemActionDone(systemActionDocument, user.documentId, err, callback);
                            callback(err, null);
                        }
                    } else {
                        var err = 'Exception : Could not find RuleSet ' + systemActionDocument.ruleSetId;
                        logger.info(err);
                        callback(err, null);
                    }
                },
                function errorFunc(e) {
                    var err = "Error in processSystemAction. Ruleset not found : " + e.message;
                    logger.info(err);
                    callback(err, null);
                }
            );
        } catch (e) {
            var err = "processSystemAction Error : " + e;
            logger.info(err);
            callback(err, null);
        }
    } else {
        var err = "System action document or user is not provided.";
        logger.error(err);
        callback(err, null);
    }
}

function processUnactionedSystemActions() {
    return RuleDataService.processUnactionedSystemActions(processSystemActionAsync);
}

async function processSystemActionAsync(systemAction) {
    setActiveProcess('systemAction', true);

    let statusDescription = null;

    let executionTimeoutInSeconds = config.get("systemActionRulesetExecutionTimeoutSeconds");
    if (!executionTimeoutInSeconds) {
        executionTimeoutInSeconds = 3000;
    }

    const executionTimeout = executionTimeoutInSeconds * 1000;

    try {
        const user = await RuleDataService.getUser(systemAction.userId);
        const process = new Promise((resolve, reject) => {
            processSystemAction(systemAction, user, function (err, data) {
                if (err) {
                    reject(new Error(err));
                } else {
                    resolve(data);
                }
            });
        });

        await promiseTimeout(executionTimeout, process);
        clearExistingTimeoutFunction();
    } catch (err) {
        statusDescription = err.message;
        var error = "Error in processSystemActionAsync: " + statusDescription;
        logger.error(error);
    } finally {
        await RuleDataService.markSystemActionDone(systemAction, statusDescription);

        setActiveProcess('systemAction', false);
        checkExitSignal();
    }

    return Promise.resolve();
}

function createAndProcessSystemAction(document, checkDate, rsEvent, templateId, user, callback) {
    if (user) {
        getRuleSetId(rsEvent, templateId, user.documentId)
            .then(
            function (rsid) {
                logger.info("Starting create system action...");
                return createSystemAction(document, checkDate, rsid, user.documentId);
            }
        )
            .then(
            function (systemActionDocument) {
                callback(null, systemActionDocument);
            },
            function (error) {
                logger.error("createAndProcessSystemAction function could not run properly: " + error.message);
                callback(error, null);
            }
        );
    } else {
        var msg = "User is not provided.";
        logger.debug(msg);
        if (typeof callback === 'function') {
            callback(null, msg);
        }
    }
}

//11203#c36916 - old rulesets are using this function
function createScheduleNewSystemAction(document, checkDate, rsTargetSystemType, rsEvent, templateId, user, callback) {
    createAndProcessSystemAction(document, checkDate, rsTargetSystemType,
        rsEvent, templateId, user, function(err, systemActionDocument) {
            //switch the arguments, rulesets calling createScheduleNewSystemAction are using this order
            callback(systemActionDocument, err);
    });
}

function getRelatedEmailAddresses(relatedFieldObject, emailField, userId, callback) {
    if (relatedFieldObject) {
        var ids = [];
        for (var idx = 0; idx < relatedFieldObject.length; idx++) {
            ids.push(relatedFieldObject[idx].documentId);
        }

        DataService.findAll({"documentId": {$in: ids}}, userId)
            .then(function successFunc(items) {
                if (items && items.length) {
                    var results = null;
                    for (var i = 0; i < items.length; i++) {

                        logger.info("Items: " + JSON.stringify(items[i][emailField]));

                        if (i === 0) {
                            results = items[i][emailField];
                        } else {
                            results += "," + items[i][emailField];
                        }
                    }

                    callback(results);
                } else {
                    callback(null, "No emails");
                }
            }, function errorFunc(err) {
                callback(null, "Error. No emails");
            });
    } else {
        logger.error("No emails.");
        if (typeof callback === 'function') {
            callback(null, "No emails.");
        }
    }
}

function createDocumentFromTemplate(fieldValues, templateId, userId, options, callback) {
    logger.info("Creating document from template...");

    if (_.isFunction(options)){
        callback = options;
        options = null;
    }

    var doc = {};
    doc.systemHeader = {};

    for (var key in fieldValues) {
        doc[key] = fieldValues[key];
    }

    doc.systemHeader.templateId = templateId;

    DataService.insert(doc, userId, options).then(
        function successFunc(data) {
            callback(null, data);
        },
        function errFunc(err) {
            callback(err);
        });
}

function attachRulesetToTemplate(query, event, rulesetDSL, userId, callback) {
    if (query) {
        query['systemHeader.systemType'] = "template";

        DataService.findAll(query, userId).then(
            function successFunc(records) {
                if (records && records.length) {
                    var index = 0;
                    var updatedRecords = [];
                    var updateTemplate = function (id, updates) {
                        DataService.update(id, updates, userId).then(
                            function successFunc(updatedRecord) {
                                updatedRecords.push(updatedRecord);
                                index++;
                                if (index === records.length) {
                                    callback(updatedRecords);
                                }
                            },
                            function errorFunc(error) {
                                index++;
                                logger.error("Error attaching ruleset to template: " + JSON.stringify(error));
                                if (index === records.length) {
                                    callback(updatedRecords);
                                }
                            }
                        );
                    };

                    var rule = {};
                    rule.name = event;
                    rule.ruleFlow = rulesetDSL;

                    //update each template because of doPostSaveEvent
                    for (var i = 0; i < records.length; i++) {
                        var record = records[i];

                        logger.debug("Found template: " + record.documentId);

                        if (!record.rules) {
                            record.rules = [];
                            record.rules.push(rule);
                        } else {
                            var hasMatchedRule = false;
                            for (var j = 0; j < record.rules.length; j++) {
                                var ruleItem = record.rules[j];
                                if (ruleItem.name === rule.name) {
                                    ruleItem.ruleFlow = rule.ruleFlow;
                                    hasMatchedRule = true;
                                    break;
                                }
                            }
                            if (!hasMatchedRule) {
                                record.rules.push(rule);
                            }
                        }

                        logger.debug("Starting updating template to attach ruleset...");

                        var idToUpdate = record.documentId;
                        updateTemplate(idToUpdate, record);
                    }
                } else {
                    logger.info("No templates found.");
                    callback();
                }
            },
            function errorFunc(err) {
                logger.info("No record found.");
                callback();
            }
        );
    } else {
        logger.debug("No query provided.");
    }
}

function findDocuments(query, userId, options, callback) {

    if (_.isFunction(options)){
        callback = options;
        options = null;
    }
    var query = SearchService.getDataSource().getFindDocumentsQuery(query);
    SearchService.search(query, userId).then(function (results) {
        logger.warn("===== Use processSearchResults function to process search results.  Example, " +
          "processSearchResults(results, elementProcessingCallback, completionCallback )   =====");
        callback(null, results);
    }, function (err) {
        callback(err);
    });
}

function findVersionDocument(versionId, userId, options, callback) {
    if (_.isFunction(options)){
        callback = options;
        options = null;
    }
    DataService.findAll({'systemHeader.versionId': versionId}, userId, null, true, options)
        .then(function success(results) {
            if (!results || results.length === 0) {
                var err = 'No result for versionId ' + versionId;
                callback(err, null);
            } else {
                callback(null, results[0]);
            }
        },function error(err) {
            callback(err, null);
        });
}


function findDocumentsByElastic(query, userId, searchContext, callback) {
    if (_.isFunction(searchContext)) {
        callback = searchContext;
        searchContext = {};
    }
    if (!searchContext) {
        searchContext = {};
    }
    searchContext.shouldExtractSource = false;

    SearchUtils.findByElastic(query, userId, searchContext, function (err, list) {
        callback(err, list);
    });
}

function updateDocument(id, change, userId, options, callback) {
    logger.info("Updating document from ruleset...");
    if (_.isFunction(options)){
        callback = options;
        options = null;
    }
    if(!options) {
        options = {};
    }

    //document context was passed from rules context
    if (this.documentContext && this.documentContext.eventData) {
        var preSaveServerName = SharedConstants.RULESET_RELATIONSHIPS.PRESAVE_SERVER_EVENT_NAME.name;
        if (this.documentContext.eventData.currentEventName === preSaveServerName &&
            this.documentContext.eventData[preSaveServerName] &&
            this.documentContext.eventData[preSaveServerName].documentId === id) {

            //should not run update document in its presaveserver rule
            logger.warn("Should not run update document in its presaveserver rule due to inconsistency data " +
                "because the latest document was loaded for update but then it will be changed in the presaveserver. " +
                "Moreover, this call will be looped and the mapping id in ES is duplicated. " +
                "Instead of calling updateDocument, " +
                "let set value for ntf.document.fieldName then it would be saved along with the document.");
            callback();
            return;
        }

        options.eventData = this.documentContext.eventData;
    }

    DataService.update(id, change, userId, options).then(
        function successF(doc) {
            callback(null, doc);
        }, function errorF(err) {
            callback(err);
        }
    );
}

function deleteDocument(id, options, callback) {
    logger.info("Deleting document from ruleset...");
    let userId;
    if (_.isFunction(options)){
        callback = options;
        options = null;
    }else if (_.isObject(options)){
        userId = options.userId;
    }
    else{
        userId = options;
        options = null;
    }

    DataService.destroy(id, userId, options).then(
        function successF(doc) {
            callback();
        }, function errorF(err) {
            callback(err);
        }
    );
}

function newJob(user, rsDoc, rsLogTplId, messages) {
    duty( "store_messages", {
        user: user,
        rsDoc: rsDoc,
        rsLogTplId: rsLogTplId,
        messages: messages
    });
};

function writeLog(userId, rsDoc, rsLogTplId, messages, callback) {
    var query = {
        'query': {
            'bool': {
                'must':[
                    {'match':{'userId': userId}},
                    {'match':{'ruleSetId': rsDoc.documentId}}
                ]
            }
        }
    };

    findDocumentsByElastic(query, userId, function (err, docs) {
        if (err) {
            logger.info('Error on rsLogger.info: ' + err.message);
        } else if (docs && docs.length === 0) {
            var nowDate = new Date();
            var newDoc = {
                'userId': userId,
                'ruleSetName': rsDoc.name,
                'ruleSetId': rsDoc.documentId,
                'logDate': UtilDate.parseDate(nowDate, 'YYYY-MM-DD HH:mm:ss'),
                'log': messages
            };

            createDocumentFromTemplate(newDoc, rsLogTplId, userId, callback);
        } else if (docs && docs.length) {
            var change = {
                'log': docs[0].log + '\n' + messages
            };
            updateDocument(docs[0].documentId, change, userId, callback);
        }
    });
};

duty.register("store_messages",
    function (data, done) {
        writeLog(data.user.documentId, data.rsDoc, data.rsLogTplId, data.messages, function () {
            logger.info("Store messages done.");
            done(null, { ok: 1 });
        });
    }, {
        delay: 15000, //wait for 15s to start job after the queue has been emptied
        timeout: 60000, //1 min timeout for each job to avoid job remains zoombie forever
        concurrency: 1 //number of parallel processes allowed
    }
);

var RSLogger = function (docContext) {
    this.messages = [];
    this.user = docContext.user;
    this.ruleSetDoc = docContext.ruleSetDoc;
    this.ruleSetLogTplId = docContext.ruleSetLogTemplateId;

    this.storeMessages = function () {
        var messages = this.buildMessage();

        if (!messages || messages.length === 0) {
            return;
        }

        newJob(this.user, this.ruleSetDoc, this.ruleSetLogTplId, messages);
        this.messages = [];
    };

    this.buildMessage = function () {
        var logLines = "";
        for (var i = 0; i < this.messages.length; i++) {
            logLines += this.messages[i] + '\n';
        }
        return logLines;
    };

    this.addMessage = function (msg, type) {
        var nowDate = new Date();
        var logLine = UtilDate.parseDate(nowDate, 'YYYY-MM-DD HH:mm:ss') + ': [' + type + '] ' + msg;
        this.messages.push(logLine);
    };

    this.info = function (msg) {
        this.addMessage(msg, 'info');
    };

    this.warn = function (msg) {
        this.addMessage(msg, 'warn');
    };

    this.error = function (msg) {
        this.addMessage(msg, 'error');
    };

    this.debug = function (msg) {
        this.addMessage(msg, 'debug');
    };

    this.setUser = function (user) {
        this.user = user;
    };

    this.getUser = function () {
        return this.user;
    };
};

function getConfiguration(path) {
    return config.get(path);
};

function sendMessageBirdSMS(targetNum, message, callback) {
    logger.warn("DEPRECATED: Please use the sendSMS function and configure the provider to MessageBird");
    if(!smsHelper) {
        smsHelper = require('../utils/SmsHelper');
    }

    smsHelper.sendMessageSMS(targetNum, message, callback);
};

function sendSMS(targetNum, message, callback) {
    PhoneService.sendSMS(targetNum, message, callback);
};

function getImageBase64(imageUrl, callback) {
    UtilBase64.getImageBase64(imageUrl).then(
        function successFunc(data) {
            callback(null, data);
        },
        function errorFunc(err) {
            callback(err);
        }
    );
}

function scheduleSystemAction(systemActionDocument, user) {
    var result = {};
    var msg = null;
    try {
        var dueDate = systemActionDocument.dueDate ? new Date(systemActionDocument.dueDate) : null;
        if (!dueDate || dueDate < new Date()) {
            // If dueDate earlier than now, make for 20 sec hence (testing)
            dueDate = new Date();
            dueDate.setSeconds(dueDate.getSeconds() + 3);
        }

        msg = 'System action set to run at ' + dueDate.toString();
        result.status = SharedConstants.STATUS_SUCCESS;
    }
    catch (e) {
        msg = 'ScheduleSystemAction Error : ' + e.message;
        result.status = SharedConstants.STATUS_ERROR;
    }

    result.message = msg;
    return result;
}

function getRuleSetIdByName(name, userId) {

    logger.info("Starting get getRuleSetIdByName...");
    var deferred = q.defer();

    ruleService.getRuleSetIdByName(userId, name).then(
        function successFunc(documentRecord) {
            if (documentRecord && documentRecord.length >0) {
                var foundId = documentRecord[0].documentId;
                logger.info("Get ruleset done: " + foundId);
                deferred.resolve(foundId);
            } else {
                var error = "RS Id not found.";
                logger.error(error);
                deferred.reject(new Error(error));
            }
        },
        function errorFunc(err) {
            var error = "RS Id not found. " + err;
            deferred.reject(new Error(error));
            logger.info("Get ruleset not found with query: " + JSON.stringify(query));
        }
    );

    return deferred.promise;
}

// * document  = the document object being processed
// * checkDate = the date-time the system action should run
// * name = the string value of the field of the same name of the RuleSet to run
// * user  = the user to use for the system action
// * callback  = function to call on completion of the System Action creation
//   * err = any error that occurred; null if successful
//   * doc = the newly created system action
function scheduleNewSystemAction(document, checkDate, name, user, callback) {

    getRuleSetIdByName(name, user.documentId)
        .then(
            function (rsid) {
                logger.info("Starting create system action...");
                return createSystemAction(document, checkDate, rsid, user.documentId);
            }
        )
        .then(
            function (systemActionDocument) {
                logger.info("System action scheduled: " + systemActionDocument.documentId);
                callback(null, systemActionDocument);
            },
            function (error) {
                logger.error("createScheduleNewSystemAction function could not run properly: " + error);
                callback(error, null);
            }
        );

}

// * options = {
//     valueType: the type of value. Valid values are applicationId,
//                  branchApplicationId or tagApplicationId.
//     applicationValue: value to search for in the field matching valueType
// }
// * userId = userId of logged in user
// * callback = function to call on completion of the deleteApplication
//   * err = any error that occurred
//   * result = results of each document delete operation
const deleteApplication = (options, userId, callback) => {
    logger.info(`Deleting documents of ${options.valueType} - ${options.applicationValue}`);
    UtilPackage.deletePackage(options.valueType, options.applicationValue, userId).then(result => {
        callback(null, result);
    }).catch(err => {
        callback(err);
    });
};


const undeleteDocument = (id, options, callback) => {
    logger.info("Undeleting document from ruleset...");
    let userId;
    if (_.isFunction(options)){
        callback = options;
        options = null;
    }else if (_.isObject(options)){
        userId = options.userId;
    }
    else{
        userId = options;
        options = null;
    }

    DataService.undestroy(id, userId, options).then(
        function successF(doc) {
            callback();
        }, function errorF(err) {
            callback(err);
        }
    );
};

module.exports.processRuleFlow = processRuleFlow;

module.exports.q = q;

module.exports.later = later;

module.exports.moment = moment;

module.exports.emailHelper = emailHelper;

module.exports.processUnactionedSystemActions = processUnactionedSystemActions;

module.exports.createSystemAction = createSystemAction;

module.exports.getRuleSetId = getRuleSetId;

module.exports.scheduleSystemAction = scheduleSystemAction;

module.exports.scheduleNewSystemAction = scheduleNewSystemAction;

module.exports.processSystemAction = processSystemAction;

module.exports.createAndProcessSystemAction = createAndProcessSystemAction;

module.exports.createScheduleNewSystemAction = createScheduleNewSystemAction;

module.exports.getRelatedEmailAddresses = getRelatedEmailAddresses;

module.exports.createDocumentFromTemplate = createDocumentFromTemplate;

module.exports.attachRulesetToTemplate = attachRulesetToTemplate;

module.exports.findDocuments = findDocuments;

module.exports.findDocumentsByElastic = findDocumentsByElastic;

module.exports.findVersionDocument = findVersionDocument;

module.exports.updateDocument = updateDocument;

module.exports.RSLogger = RSLogger;

module.exports.RulesetError = RulesetError;

module.exports.getConfiguration = getConfiguration;

module.exports.sendMessageBirdSMS = sendMessageBirdSMS;

module.exports.sendSMS = sendSMS;

module.exports.PhoneService = PhoneService;

module.exports.addIncludesToRuleset = addIncludesToRuleset;

module.exports.ajax = AjaxServer.ajax;

module.exports.getImageBase64 = getImageBase64;

module.exports.deleteDocument = deleteDocument;

module.exports.undeleteDocument = undeleteDocument;

module.exports.deleteApplication = deleteApplication;

module.exports.processSearchResults = SearchResultsProcessor.processSearchResults;
