'use strict';

var lodashObj = require("lodash/object");
var lodashLang = require("lodash/lang");
var utilError = require('../../shared/utils/UtilError');
var utilHttp = require('../utils/UtilHttp');
var utilHttpStatus =  require('../../shared/utils/UtilHttpStatus');
var sharedConstants = require('../../shared/constants/SharedConstants');
var smsHelper = null;
var PhoneService = null;
var SearchService = require('../search/SearchService');
var ErrorCodes = require('../../shared/constants/ErrorCodes');
var UtilHost = require('../utils/UtilHost');
var OfflineDataReader;

var RESTRICTED_DOCUMENT_TYPES = [sharedConstants.SYSTEM_TYPE_ACCOUNT_SECURITY];
const SearchResultsProcessor   = require('../../shared/utils/SearchResultsProcessor');
const { throws } = require("assert");

function isDocumentTypeRestricted(documentType){
    for(var i = 0 ; i < RESTRICTED_DOCUMENT_TYPES.length ; i++){
        if(RESTRICTED_DOCUMENT_TYPES[i] === documentType){
            return true;
        }
    }

    return false;
}

// check whether this is a MongoDB function that can be converted to ElasticSearch.
// Queries that have syntax that don't easily translate to MongoDB will need to be
// changed by the implementor
const isSimpleQuery = query => {
    return Object.keys(query).every(key => {
        return typeof query[key] !== "object";
    });
};

const doFindAll = (query, userId, allVersion, req, res) => {
    logger.warn("DEPRECATED: Please use the documentSearch function.");

    if (!isSimpleQuery(query)) {
        var msg = "MongoDB Query could not be converted to ElasticSearch in findAll.";
        logger.error(msg);
        res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(msg);
        return;
    }

    const esQuery = SearchService.getDataSource().getDoFindAllQuery(query);
    SearchService.search(esQuery, userId, allVersion).then(
        function successFunc(data) {
            SearchResultsProcessor.processSearchResults(data,
              function elementProcessing(doc){},
              function complete(docs){
                res.json(docs);
              }
            );
        }, function errorFunc(err) {
            res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(err);
        });
};

var offlineConfig = config.get('offline') ? config.get('offline') : {};
if (!offlineConfig.syncPageSize) {
    //default
    offlineConfig.syncPageSize =100;
}


const getHttpStatusCode = (error) => {

  if (error.type === sharedConstants.ERROR_TYPE_NOTFOUND) {
    return utilHttpStatus.NOT_FOUND;
  } else if (error.name === utilError.FORBIDDEN_ERROR) {
    return utilHttpStatus.FORBIDDEN;
  } else if (error.name === sharedConstants.ERROR_TYPE_INTERNAL) {
    return utilHttpStatus.INTERNAL_SERVER_ERROR;
  } else if (error.type === sharedConstants.ERROR_TYPE_GONE) {
    return utilHttpStatus.GONE;
  } else {
    return utilHttpStatus.INTERNAL_SERVER_ERROR;
  }
};

const sendError = (error, res, msg) => {
    const errorMessage = msg || error.message;

    if (error.type === sharedConstants.ERROR_TYPE_NOTFOUND) {
        res.status(utilHttpStatus.NOT_FOUND).send(errorMessage).end();
    } else if (error.name === utilError.FORBIDDEN_ERROR) {
        res.status(utilHttpStatus.FORBIDDEN).send(errorMessage).end();
    } else if (error.name === sharedConstants.ERROR_TYPE_INTERNAL) {
        res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(errorMessage).end();
    } else if (error.type === sharedConstants.ERROR_TYPE_GONE) {
        res.status(utilHttpStatus.GONE).send(errorMessage).end();
    } else {
        let msg = "Internal server error. Please contact administrator.";
        if (errorMessage) {
            msg = errorMessage;
        }

        res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(msg).end();
    }
};

var find = function(req, res, method) {

    var userId = req.user ? req.user.documentId : null;

    if (!userId) {
        var msg = "No logged in user in call to find";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    var id = req.params.id;

    var query = req.query;
    if (method === sharedConstants.METHOD_POST) {
        query = req.body;
    }

    var options = initOptions(query);

    if (query && query.expectedDocumentType) {
      options.expectedDocumentType = query.expectedDocumentType;
    }

    logger.debug("Getting document with id: " + id);
    DataService.findOne(id, userId, options)
    .then(function successFunc(documentRecord) {
        if (documentRecord) {

            //Perform doctype restriction evaluation here so that it will be independent of database implementation.
            if(isDocumentTypeRestricted(documentRecord.systemHeader.systemType)){
                return res.status(utilHttpStatus.FORBIDDEN).send("Document type restricted.").end();
            }

            logger.debug("Found document: " + documentRecord.documentId);
            delete documentRecord._id;

        } else {
            logger.warn("Undefined document returned in findOne");
        }

        // only send the object as json if it is defined
        utilHttp.sendJson(res, documentRecord);
    }, function errorFunc(error) {
        sendError(error, res);
    });
};

exports.findPost = function(req, res) {
    find(req, res, sharedConstants.METHOD_POST);
};

exports.find = find;

exports.templateAndDocument = async function(req, res) {
    var userId = req.user ? req.user.documentId : null;

    if (!userId) {
        var msg = 'No logged in user in call to find';
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    var id = req.params.id;
    var templateId = req.params.templateId;
    var query = req.query;
    var options = initOptions(query);

    let resObject = {};

    try {

        try {
          resObject.document = await DataService.findOne(id, userId, options);
        } catch (error) {
            if (error.type === sharedConstants.ERROR_TYPE_GONE
                || error.type !== sharedConstants.ERROR_TYPE_NOTFOUND) {
                // in case override template
                throw error;
            }

          resObject.template = await DataService.findTemplate(id, userId, options);
        }

        // Target template/document not found
        if (!resObject.document && !resObject.template) {
            throw {
                type: sharedConstants.ERROR_TYPE_NOTFOUND,
                message: `Template or document: ${id} not found`
            };
        }

        if (
            !resObject.template &&
            !templateId &&
            resObject.document &&
            resObject.document.systemHeader &&
            resObject.document.systemHeader.systemType === sharedConstants.SYSTEM_TYPE_TEMPLATE
        ) {
            resObject.template = resObject.document;
            resObject.document = null;
            resObject.templateReturnStatus =  utilHttpStatus.OK;
        }

        if (resObject.document) {
            const tplId = templateId || resObject.document.systemHeader.templateId;

            if (!tplId) {
                throw {
                    type: sharedConstants.ERROR_TYPE_NOTFOUND,
                    message: 'No Template for document found by override ' +
                        `template id or systemHeader.templateId: ${id}`
                };
            }

            try {
              resObject.template = await DataService.findTemplate(tplId, userId, options);
              if (!resObject.template) {
                resObject.templateReturnStatus =  utilHttpStatus.NOT_FOUND;
              } else {
                resObject.templateReturnStatus =  utilHttpStatus.OK;
              }
            } catch (error) {
              resObject.templateReturnStatus =  getHttpStatusCode(error);
              resObject.templateError = error;
            }
        } else if (resObject.template) {
          resObject.templateReturnStatus =  utilHttpStatus.OK;
        }

        res.json(resObject);

    } catch (error) {
      sendError(error, res);
    }
};

var findAll = function(req, res, method) {
    var userId = req.user ? req.user.documentId : null;

    if (!userId) {
        var msg = "No logged in user in call to findAll";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    var query = req.query;

    if (method === sharedConstants.METHOD_POST) {
        query = req.body;
    }

    if (query.query) {
        try {
            query = JSON.parse(query.query);
        } catch (e) {
            query = query.query;
        }
    }

    var allVersions = query.allVersions;

    doFindAll(query, userId, allVersions, req, res);
};

exports.findAllPost = function(req, res) {
    findAll(req, res, sharedConstants.METHOD_POST);
};

exports.findAll = findAll;

exports.findVersions = function(req, res) {
    var userId = req.user ? req.user.documentId : null;

    if (!userId) {
        var msg = "No logged in user in call to findVersions";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    const versionIds = req.body.versionIds;
    const includeAllVersions = req.body.includeAllVersions;
    DataService.findVersions(versionIds, userId, null, includeAllVersions).then(
        function successFunc(versions) {
            if (versions) {
                versions.forEach(function (version) {
                    delete version._id;
                });
            }
            res.json(versions);
        },
        function errorFunc(err) {
            res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(err);
        }
    );
};

exports.syncDocuments = function(req, res) {

    var userId = req.user ? req.user.documentId : null;

    if (!userId) {
        var msg = "No logged in user in call to syncDocuments";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    if (!OfflineDataReader) {
        OfflineDataReader = require('../offline/OfflineDataReader');
    }

    var startTime = req.params.startTime;
    var pageNumber = req.params.pageNumber;
    var numberPerPage = offlineConfig.syncPageSize;

    OfflineDataReader.getOfflineTemplateDocuments(userId, startTime, pageNumber, numberPerPage).then(
        function successFunc(offlineTemplateDocuments) {
            res.json(offlineTemplateDocuments);
        }, function errorFunc(err) {
            res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(err);
        });
};

exports.getSyncDocumentCount = function (req, res) {

    var userId = req.user ? req.user.documentId : null;

    if (!userId) {
        var msg = "No logged in user in call to syncDocumentCounts";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    if (!OfflineDataReader) {
        OfflineDataReader = require('../offline/OfflineDataReader');
    }

    var startTime = req.params.startTime;

    OfflineDataReader.getOfflineSyncCount(userId, startTime)
        .then(function successFunc(docCount) {
            logger.info("Got documents to sync for offline use of count: " + docCount);

            var pages = Math.ceil(docCount / offlineConfig.syncPageSize);
            var result = {
                docCount: docCount,
                syncPageSize: offlineConfig.syncPageSize,
                pages: pages
            };
            logger.info(JSON.stringify(result));
            res.json(result);
        }, function errorFunc(err) {
            res.write(err);
            res.end();
        });
};

exports.insert = function(req, res) {
    var documentRecord = req.body;

    var reqOptions =   getOptionsFromRequest(req);
    var options = initOptions(reqOptions);

    var user = req.user;
    var userId = user ? user.documentId : null;

    options.fullDomainUrl = UtilHost.getFullDomainURL(req);

    DataService.insert(documentRecord, userId, options)
        .then(function successFunc(data) {
            logger.debug("Returning response to client for insert of document: " + data.documentId);
            res.json(data);
        }, function errorFunc(err) {

            var msg;
            if (err && err.errorCode) {
                if (err.errorCode === ErrorCodes.ERROR_DUPLICATE_ACCOUNT_EMAIL) {
                    msg = "Email is taken. Please use another one to create account.";

                } else if (err.errorCode === ErrorCodes.ERROR_DUPLICATE_ACCOUNT_API_KEY) {
                    msg = "API/Host key is taken. Please use another one to create account.";

                } else if (err.errorCode === ErrorCodes.ERROR_DUPLICATE_ACCOUNT_PUBLIC_URL) {
                    msg = "Public URL is taken. Please use another one to create account.";

                } else {
                    msg = "There is a problem with account creation. " +
                        "Please see an administrator and quote error code: " + err.errorCode;
                }
            } else {
                msg = "Saving new document failed with: " + (err && err.message ? err.message : err);
            }

            logger.error(msg);
            sendError(err, res, msg);
        }
    );
};

function doUpdate(req, res, updateFunc) {

    var userId = req.params.userId;

    if (userId) {
        logger.warn("Use of the userId in the URL params of DocumentController.update is deprecated. " +
            "The logged in userId will be used");
    }

    userId = req.user ? req.user.documentId : null;

    if (!userId) {
        var msg = "No logged in user in call to update";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    var id = req.params.id;
    var template = req.params.template;

    if (id && id !== "undefined") {
        var updateData = req.body;

        logger.debug("Performing update of document: " + id);
        var reqOptions =   getOptionsFromRequest(req);
        var options = initOptions(reqOptions);
        options.templateId = template;
        options.fullDomainUrl = UtilHost.getFullDomainURL(req);

        updateFunc(id, updateData, userId, options).then(
            function successFunc(data) {
                logger.info("Successfully updated document: " + id);
                res.json(data);
            }, function errorFunc(err) {
                var msg = "Error updating document: " + req.params.id + ": " + JSON.stringify(err);
                logger.error(msg);
                res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(msg);
            }
        );
    } else {
        var msg = "Id not passed to update function";
        logger.error(msg);
        res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(msg);
    }
}

function jsonPatchUpdate(req, res) {

    var userId = req.params.userId;
    if (userId) {
        logger.warn("Use of the userId in the URL params of DocumentController.jsonPatchUpdate is deprecated. " +
            "The logged in userId will be used");
    }

    userId = req.user ? req.user.documentId : null;

    if (!userId) {
        var msg = "No logged in user in call to update";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    var pushedData = req.body;
    var updatedDocument = pushedData.document;
    var options = pushedData.options;

    var paramErrMsg = null;
    if(updatedDocument == null){
        paramErrMsg = "Bad update request: Updated document is missing.";
    }

    if(paramErrMsg != null){
        res.status(utilHttpStatus.BAD_REQUEST).send(paramErrMsg);
        return;
    }

    options = initOptions(req.options, options);
    options.fullDomainUrl = UtilHost.getFullDomainURL(req);

    var documentId = req.params.id; //if this is missing then updatedDocument.documentId is used by update

    DataService.jsonPatchUpdate(documentId, updatedDocument, userId, options).then(
        function successFunc(data) {
            logger.info("Successfully updated document: " + documentId);
            res.json(data);
        }, function errorFunc(error) {
            var details = error.message ? error.message : error;
            var msg = "Saving existing document " + documentId + " failed with: " + details;
            logger.error(msg);
            sendError(error, res, msg);
        }
    );

}

exports.deepDiffUpdate = jsonPatchUpdate;

exports.jsonPatchUpdate = jsonPatchUpdate;

exports.update = function(req, res) {
    doUpdate(req, res, DataService.update);
};

var search = function(req, res, method) {

    var query = req.query;
    var options;
    let reqOptions;
    if (method === sharedConstants.METHOD_POST) {
        query = req.body;
        reqOptions =  getOptionsFromRequest(req);

    }
    else{
        var pushedData = req.body;
        if (pushedData){
            options = pushedData.options;
        }
        reqOptions = initOptions(req.options, options);
    }

    options = initOptions(reqOptions);

    var userId = query.userId;
    if (userId) {
        logger.warn("Use of the userId in the URL params of DocumentController.search is deprecated. " +
            "The logged in userId will be used");
    }

    userId = req.user ? req.user.documentId : null;

    if (!userId) {
        var msg = "No logged in user in call to update";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    SearchService.search(query, userId, null, options).then(
        function successFunc(data) {
            res.json(data);
        }, function errorFunc(err) {
            res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(err);
        });
};

exports.searchPost = function(req, res) {
    search(req, res, sharedConstants.METHOD_POST);
};

exports.search = search;
exports.generatePit = function(req, res) {
    SearchService.generatePit().then(pit => {
        res.json(pit);
    }).catch(err => {
        res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(err.message);
    });
};

exports.audit = function(req, res) {
    var user = req.user;
    var userId = user ? user.documentId : null;

    if (!userId) {
        var msg = "No logged in user in call to audit";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    var id = req.query.id;

    const auditService = require('../database/dataServices/AuditService');
    auditService.findAuditDocument(id, userId)
        .then(function successFunc(data) {
            res.json(data);
        }, function errorFunc(err) {
            var msg = "Error getting document versions: " + err;
            logger.error(msg);
            res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(msg);
        });
};

exports.destroy = function(req, res) {
    var user = req.user;
    var userId = user ? user.documentId : null;

    if (!userId) {
        var msg = "No logged in user in call to destroy";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    var query = req.query;
    var options = initOptions(query);

    var id = req.params.id;
    options.templateId = req.params.templateId;
    options.previousVersionId = req.params.previousVersionId;
    DataService.destroy(id, userId, options).then(
        function successFunc(data) {
            logger.info("Successfully deleted document: " + data.documentId);
            res.json(data.documentId);
        }, function errorFunc(err) {
            var msg = "Error deleting document: " + err;
            logger.error(msg);
            res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(msg);
        }
    );
};

exports.undestroy = (req, res) => {
    let user = req.user;
    const userId = user ? user.documentId : null;

    if (!userId) {
        const msg = "No logged in user in call to undestroy";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    const id = req.params.id;
    DataService.undestroy(id, userId).then(
        function successFunc(data) {
            logger.info("Successfully undeleted document: " + data.documentId);
            res.json(data);
        }, function errorFunc(err) {
            const msg = "Error undeleting document: " + err;
            logger.error(msg);
            res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(msg);
        }
    );
};

exports.findByQuery = function(req, res) {
    var userId = req.user ? req.user.documentId : null;
    if (!userId) {
        var msg = "No logged in user in call to find by query";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    var query = JSON.parse(req.params.query);

    doFindAll(query, userId, false, req, res);
};

const getIncludeDocs = async (req, res, method) => {
    const userId = req.user ? req.user.documentId : null;

    if (!userId) {
        var msg = "No logged in user in call to getIncludeDocs";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    try {
        let includeNames = req.query.includeNames;
        if (method === sharedConstants.METHOD_POST) {
          includeNames = req.body.includeNames;
        } else {
          includeNames = includeNames ? JSON.parse(includeNames) : null;
        }

        if (!includeNames) {
            res.json([]);
            return;
        }

        const ruleService = require('../database/dataServices/RuleService');
        const docs = await ruleService.getIncludeDocs(userId, includeNames);
        res.json(docs);
    } catch (err) {
        res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(err.message);
    }
};
exports.getIncludeDocs = getIncludeDocs;

exports.getIncludeDocsPost = function(req, res) {
  getIncludeDocs(req, res, sharedConstants.METHOD_POST);
};


/**
 * Used to queries documents from client rules with a provided user and query
 * @param req (included provided user and query)
 * @param res (return result: error, objects)
 * @deprecated because we need to move finding multiple documents in rules from mongo to elastic
 */
exports.findForRules = function(req, res) {

    var userId = req.params.userId;
    if (userId) {
        logger.warn("userId passed in URL parameter for findForRules will be ignored. The logged in user will be used");
    }

    userId = req.user ? req.user.documentId : null;

    if (!userId) {
        var msg = "No logged in user in call to findForRules";
        logger.error(msg);
        res.status(utilHttpStatus.UNAUTHORIZED).send(msg);
        return;
    }

    var query = JSON.parse(req.query.q);

    doFindAll(query, userId, false, req, res);
};

function initOptions(options, updateOptions){
    if (!options) {
        options = {};
    }
    return lodashObj.extend(options, updateOptions);
}

function getOptionsFromRequest(req){
  var reqOptions =   req.get("options") || req.params.options || req.query.options || req.headers.options;
  try{
    reqOptions = lodashLang.isEmpty(reqOptions) ? null : JSON.parse(reqOptions);
  }
  catch(err){
      logger.error(err);
      reqOptions = null;
  }

  return reqOptions;
}

exports.documentExists = function(req, res) {
    var includeDeleted = req.params.includeDeleted;
    var documentId = req.params.id;

    var checkOptions = {};
    if (includeDeleted) {
        checkOptions.excludeDeleted = false;
    }

    DataService.documentExists(documentId, null, checkOptions).then(
        function successFunc(found) {
            res.json(found);
        }, function errorFunc(err) {
            res.status(utilHttpStatus.INTERNAL_SERVER_ERROR).send(err);
        }
    );
};


exports.bulkUploadDocuments = bulkUploadDocuments;

async function bulkUploadDocuments(req, res) {

    let documents= req.body;

    let reqOptions =   getOptionsFromRequest(req);
    let options = initOptions(reqOptions);

    let user = req.user;
    let userId = user ? user.documentId : null;

    try{
        if (typeof documents === "string") {
            documents = JSON.parse(documents);
        }

        let results = {};
        if (lodashLang.isArray(documents)){
            await Promise.all(documents.map(async (document) => {
                const result = await saveDocument(document, userId, options);
                results[result.documentId] = result.status;
            }));
        }
        else{
            const result = await saveDocument(document, userId, options);
            results[result.documentId] = result.status;
        }
        res.json(results);

    }catch(err){
        const errMsg = 'Error in documents bulk upload : ' + err.message;
        logger.error(errMsg);

        const httpStatus = err.httpStatusCode ? err.httpStatusCode : utilHttpStatus.INTERNAL_SERVER_ERROR;
        res.status(httpStatus).send(errMsg);

    }

};

async function saveDocument(document, userId, options) {
    try {
        let result  = {};
        let documentRecord = await DataService.findOne(document.documentId, userId, options);
        if (documentRecord && documentRecord.documentId){
           let doc = await DataService.jsonPatchUpdate(documentRecord.documentId, document, userId, options);
           result = {documentId : doc.documentId, status : "updated"};
        }
        else {
            let doc = await DataService.insert(document, userId, options);
            result = {documentId : doc.documentId, status : "inserted"};
        }

        return Promise.resolve(result);
    }catch (err) {
        if (err.type === sharedConstants.ERROR_TYPE_NOTFOUND) {
            let doc = await DataService.insert(document, userId, options);
            let result = {documentId : doc.documentId, status : "inserted"};
            return Promise.resolve(result);
        }
        else{
            return Promise.reject(err);
        }

    }
};
