'use strict';

var q = require("q");

var RuleUtils = require('./ServerRuleUtils');
var SharedConstants = require('../../shared/constants/SharedConstants');
var UtilElasticSearch = require('../utils/UtilElasticSearch');
var UtilRules = require('../../shared/utils/UtilRules');
const ruleService = require('../database/dataServices/RuleService');
var lodashLang = require("lodash/lang");

function processRuleFlow(document, documentContext, ruleFlowString, ruleScope, callback) {
    RuleUtils.processRuleFlow(document, documentContext, ruleFlowString, ruleScope, function (result) {
        callback(result);
    });
}

var loadRulesetById = function(docId, userId) {
    var deferred = q.defer();

    DataService.findOne(docId, userId).then(
        function successFunc(doc) {
            deferred.resolve(doc);
        },
        function errorFunc(err) {
            err.documentId = docId;
            deferred.reject(err);
        }
    );

    return deferred.promise;
};


var loadRule = function (document, documentContext, eventName) {
    var deferred = q.defer();

    var userId = documentContext.user.documentId;
    DataService.findOne(document.systemHeader.templateId, userId).then(
        function successFunc(foundTemplate) {
            if(foundTemplate) {
                var relName = UtilRules.getRelatedName(eventName);
                var rulesetRelValues = foundTemplate[relName];
                if (rulesetRelValues) {
                    logger.info("Using number of related rulesets: " + rulesetRelValues.length);

                    var promises = [];
                    for (var i = 0; i < rulesetRelValues.length; i++) {
                        promises.push(loadRulesetById(rulesetRelValues[i].documentId, userId));
                    }
                    q.all(promises).then(
                        function successFunc(ruleDocs) {
                            logger.info("Found related rulesets: " + ruleDocs.length);
                            deferred.resolve(ruleDocs);
                        },
                        function errorFunc(err) {
                            logger.warn("Not found related rulesets." + err.message);
                            deferred.reject(err);
                        }
                    );
                } else {
                    deferred.resolve();
                }
            } else {
                deferred.reject(new Error("No ruleset found."));
            }
        },
        function errorFunc(err) {
          deferred.reject(err);
        }
    );

    return deferred.promise;
};

var RuleContext = function(eventName, processDoc, documentContext) {
    var that = this;
    that.eventName = eventName;
    that.document = processDoc;
    that.documentContext = documentContext;
    if (documentContext && documentContext.eventData && documentContext.eventData[eventName]) {
        documentContext.eventData[eventName].eventUpdateCount++;
    }
};

function executeRuleSet(eventName, document, documentContext, rulesetDoc) {
    logger.info("Executing " + eventName + " on ruleset: " + rulesetDoc.documentId);

    var deferred = q.defer();

    if (!documentContext) {
        documentContext = {};
    }

    var defaultSearch =config.get("dataServices").defaultSearch;
    documentContext.databaseMode = defaultSearch ? defaultSearch.providerName : "elastic";

    documentContext.eventName = eventName;
    documentContext.ruleSetId = document.documentId;
    documentContext.ruleSetDoc = rulesetDoc;
    documentContext.ruleSetLogTemplateId = config.get("ruleSetLogTemplateId");

    var isRulesSaveOnDoc = documentContext.eventData &&
        documentContext.eventData[eventName] &&
        documentContext.eventData[eventName].documentId === document.documentId;
    if (!isRulesSaveOnDoc) {
        if (!documentContext.eventData) {
            documentContext.eventData = {};
        }
        documentContext.eventData.currentEventName = eventName;
        documentContext.eventData[eventName] = {
            fromEventName: eventName,
            documentId: document.documentId,
            eventUpdateCount: 0
        };
    }

    var ruleContext = new RuleContext(eventName, document, documentContext);

    processRuleFlow(document, documentContext, rulesetDoc.ruleSetDSL, ruleContext, function (ruleResult) {
        logger.info("SERVER RULES:  Result of " + eventName + " rule engine: " + JSON.stringify(ruleResult));

        if (ruleResult && ruleResult.status && ruleResult.status.toLowerCase() !== SharedConstants.STATUS_SUCCESS) {
            deferred.reject(ruleResult);
        } else {
            deferred.resolve(ruleResult);
        }
    });

    return deferred.promise;
}

function executeEvent(document, documentContext, eventName, callback) {
    logger.info("Doing " + eventName + " event for doc: " + document.documentId);

    if (!document.systemHeader.templateId) {
        if (callback) {
            callback();
        }
    } else {
        loadRule(document, documentContext, eventName).then(
            function successFunc(rulesetDocs) {
                var sequence = q.when();
                if (!rulesetDocs) { 
                    callback();
                    return;
                } 
                
                if (lodashLang.isArray(rulesetDocs)) {
                    rulesetDocs.forEach(function (rulesetDoc) {
                        sequence = sequence.then(function () {
                            return executeRuleSet(eventName, document, documentContext, rulesetDoc);
                        });
                    });
                } else {
                    sequence = sequence.then(function () {
                        return executeRuleSet(eventName, document, documentContext, rulesetDocs);
                    });
                }

                sequence.then(
                    function allPromiseSuccessFunc(results) {
                        callback(results);
                    },
                    function errorFunc(err) {
                        callback(err);
                    }
                );
            },
            function errorFunc(err) {
                logger.info("SERVER RULES:  No " + eventName + " ruleset document found.");
                if (callback) {
                    callback({
                        status: SharedConstants.STATUS_ERROR,
                        message: `${eventName} rule does not exist: ${err.documentId}`
                    });
                }
            }
        );
    }
}

module.exports = {
    /*
     * Process Rule flow string
     * @document - document object
     * @documentContext - additional objects will be used in rule
     * @ruleFlowString - rule flow as string
     * @scope - additional scope objects will be plugged in to rule scope
     */
    processRuleFlow: function (document, documentContext, ruleFlowString, ruleScope, callback) {
        RuleUtils.processRuleFlow(document, documentContext, ruleFlowString, ruleScope, function (result) {
            callback(result);
        });
    },

    doPostSaveEvent: function (document, documentContext, callback) {
        executeEvent(document, documentContext, SharedConstants.RULESET_RELATIONSHIPS.POSTSAVE_EVENT_NAME.name, callback);
    },

    doPreSaveServer: function (document, documentContext, callback) {
        executeEvent(document, documentContext, SharedConstants.RULESET_RELATIONSHIPS.PRESAVE_SERVER_EVENT_NAME.name, callback);
    }
};