'use strict';

var q = require("q");

var ServerAccessManager = require('../utils/ServerAccessManager');
var SharedConstants = require('../../../shared/constants/SharedConstants');
var UtilError = require('../../../shared/utils/UtilError');
var UtilApplication = require('../../../shared/utils/UtilApplication');
var UtilDocument = require('../../../shared/utils/UtilDocument');

function errorMessage(errorType, errorMessage) {
    logger.error(errorMessage);

    return {
        type: errorType,
        message: errorMessage
    };
}

async function findTemplate(id, userId, options) {
    if (!id) {
        throw errorMessage(SharedConstants.ERROR_TYPE_INTERNAL, "No documentId specified in findTemplate!");
    } else if (!userId) {
        throw errorMessage(SharedConstants.ERROR_TYPE_INTERNAL, "A user must be provided in the findTemplate method");
    }

    var connectionName = options.connectionName;
    var ignoreDeleted = options.ignoreDeleted;

    try {
        const user = await DBProvider.accountDataService.getAccountById(userId);

      var documentQuery = DBProvider.dataFindService.getFindTemplateQuery({documentId: id});

      var params = {};
      params.query = documentQuery;

      const template = await DBProvider.dataFindService.findOne(params, connectionName);
      if (!ignoreDeleted && (template && template.systemHeader && template.systemHeader.deleted)) {
        var deletedMsg = "Document has been deleted";
        throw errorMessage(SharedConstants.ERROR_TYPE_GONE, deletedMsg);
      }

      const hasAccess = await ServerAccessManager.checkAccess(template, user, SharedConstants.OPERATION_TYPE_READ);

        if (hasAccess) {
            return template;
        }

        var hasAltTemplates = template
            && template.systemHeader
            && template.altTemplateIds
            && template.altTemplateIds.length !== 0;

        if (hasAltTemplates) {
            var params = {
                query:  DBProvider.dataFindService.getMultipleFindTemplateQuery({documentId: template.altTemplateIds})
            };

            const altTemplates = await DBProvider.dataFindService.find(params, connectionName);
            for (const altTpl of altTemplates) {
              if (!(!ignoreDeleted && (template && template.systemHeader && template.systemHeader.deleted))) {
                const hasAccess = await ServerAccessManager.checkAccess(altTpl, user, SharedConstants.OPERATION_TYPE_READ);
                if (hasAccess) return altTpl;
              }
            }
        }

        throw UtilError.createForbiddenError(template, SharedConstants.OPERATION_TYPE_READ);
    } catch (error) {
        throw errorMessage(SharedConstants.ERROR_TYPE_INTERNAL, error.message);
    }
}

function execFindOne(doc, user, ignoreDeleted, options) {

    if (!ignoreDeleted && (doc && doc.systemHeader && doc.systemHeader.deleted)) {
        var deletedMsg = "Document has been deleted";
        return Promise.reject(errorMessage(SharedConstants.ERROR_TYPE_GONE, deletedMsg));
    }

    UtilApplication.checkDocumentApplication(doc, user);

    var operation;
    if (options && options.targetOperation) {
        operation = options.targetOperation;
    } else {
        operation = SharedConstants.OPERATION_TYPE_READ;
    }

    return ServerAccessManager.checkAccess(doc, user, operation).then(function(hasAccess){

        if (options && options.targetOperation === SharedConstants.OPERATION_TYPE_EXECUTE) {

            if (!hasAccess) {
                return Promise.reject(UtilError.createError("Access denied on loading " +
                    options.targetFieldNames + " to execute on document: " + doc.documentId));
            }

        }

        if (hasAccess) {
            return options ? UtilDocument.filterDocumentFields(doc, options.targetFieldNames) : doc;
        }

        return Promise.reject(UtilError.createForbiddenError(doc, SharedConstants.OPERATION_TYPE_READ));
    });
}

var findOne = function (id, userId, options) {
    var deferred = q.defer();
    var ignoreDeleted = options.ignoreDeleted;
    var connectionName = options.connectionName;
    var disableDocumentIdAlias = options.disableDocumentIdAlias;
    if (!id) {
        var errorMsg = "No documentId specified in findOne!";
        logger.error(errorMsg);
        deferred.reject(errorMessage(SharedConstants.ERROR_TYPE_INTERNAL, errorMsg));
    }
    else if (!userId) {
        deferred.reject(errorMessage(SharedConstants.ERROR_TYPE_INTERNAL,
            "A user must be provided in the findOne method"));
    } else {
        DBProvider.accountDataService.getAccountById(userId).then(
            function successFunc(user) {
                logger.debug("Got user in find: " + user.documentId);

                var documentQuery ={
                    "documentId": id,
                    "systemHeader.currentVersion": true
                };

                var params = {};
                params.query = documentQuery;

                new Promise((resolve, reject) => {
                    DBProvider.dataFindService.findOne(params, connectionName,
                        (queryErr, doc) => {
                            if (queryErr) {
                                reject(errorMessage(SharedConstants.ERROR_TYPE_INTERNAL,
                                    "Error getting document: " + queryErr));
                            } else if (doc) {
                                resolve(doc);
                            } else if (!disableDocumentIdAlias){
                                delete params.query.documentId;
                                params.query.documentIdAlias = id;

                                DBProvider.dataFindService.findOne(params, connectionName,
                                    (queryErr, doc) => {
                                        if (queryErr) {
                                            reject(errorMessage(SharedConstants.ERROR_TYPE_INTERNAL,
                                                "Error getting document: " + queryErr));
                                        } else {
                                            resolve(doc);
                                        }
                                    });
                            }
                        });
                }).then((doc) => {
                    if (doc) {
                        execFindOne(doc, user, ignoreDeleted, options).then(
                            function successFunc(result) {
                                deferred.resolve(result);
                            },
                            function errorFunc(err) {
                                deferred.reject(err);
                            }
                        );
                    } else {
                        var versionQuery ={
                            "systemHeader.versionId": id
                        };

                        var params = {};
                        params.query = versionQuery;

                        DBProvider.dataFindService.findOne(params, connectionName,
                            function (queryErr, doc) {
                                if (queryErr) {
                                    deferred.reject(errorMessage(SharedConstants.ERROR_TYPE_INTERNAL,
                                        "Error getting document: " + queryErr));
                                } else if (doc) {
                                    execFindOne(doc, user, ignoreDeleted, options).then(
                                        function successFunc(result) {
                                            deferred.resolve(result);
                                        },
                                        function errorFunc(err) {
                                            deferred.reject(err);
                                        }
                                    );
                                } else {
                                    var errorMsg = "Error getting document: document not found! " + id;
                                    if (options && options.expectedDocumentType){
                                      errorMsg = `Error getting ${options.expectedDocumentType}: 
                                      ${options.expectedDocumentType} not found! ${id}` ;
                                    }
                                    deferred.reject(errorMessage(SharedConstants.ERROR_TYPE_NOTFOUND,
                                        errorMsg));
                                }
                            }
                        );
                    }
                }).catch(err => deferred.reject(err));
            }, (err) => deferred.reject(errorMessage(SharedConstants.ERROR_TYPE_INTERNAL,
                "Couldn't get user in findOne: " + JSON.stringify(userId) + ". Error: " + err))
        );
    }

    return deferred.promise;
};

function getUserKeys(userId, options, connectionName) {

    if(!userId){
        return Promise.resolve([]);
    }

    var operationToCheck = options && options.alternativeKey ? options.alternativeKey : SharedConstants.OPERATION_TYPE_READ;
    return ServerAccessManager.getUserAccessKeysForOperation(userId, operationToCheck);

}

function findAll(query, userId, sortQuery, allVersions, options) {

    return getUserKeys(userId, options, options.connectionName).then(function (keys) {
        return DBProvider.dataFindService.findAll(query, keys, sortQuery, allVersions, options);
    });

}

function findVersions(ids, userId, sortQuery, allVersions, options) {
    return getUserKeys(userId, options, options.connectionName).then(function (keys) {
        return DBProvider.dataFindService.findVersions(ids, keys, sortQuery, allVersions, options);
    });
}


module.exports = {
    findOne: findOne,

    /**
     findAll - find all records, with optional query string
     parameters:
     query - a mongodb query to run. This can be any mongodb query
     userId - the userId for the operation. This needs to be the id of an account
     sortQuery - sort query to apply to the results
     allVersions - whether to retrieve all versions
     options - various options. Any new parameters should be added under options, unless it is felt that they are important enough
     to add a new parameter. Current options are:

     alternativeKey - alternative key to apply to query, eg. "Offline", instead of "Read".
     This can be used for specific operations
     excludeMissingKeys - documents with no keys will be excluded from the search. The default is to treat documents with
     no keys as accessible, but in certain operations like getting the offline documents you don't
     want documents with no keys
     */
    findAll: findAll,

    findVersions: findVersions,

    findTemplate
};
