"use strict";

const q = require("q"),
    sharedConstants = require("../../shared/constants/SharedConstants"),
    mongoConnection = require("../database/providers/mongodb/MongoConnection"),
    ServerAccessManager = require("../database/utils/ServerAccessManager"),
    utilHttpStatus = require("../../shared/utils/UtilHttpStatus"),
    dbConfig = require("../database/providers/DatabaseConfig"),
    utilType = require('../../shared/utils/UtilType'),
    Handlebars = require('handlebars'),
    _ = require('lodash/lang');

let params = [];
let dsConfig;

function getAccessKeysQuery(accessKeys, options) {
    var result;

    // if the excludeMissingKeys option is specified, documents with no keys won't be included in the results. This allows querying
    // for documents that must have the key. It must be combined with the alternativeKey option, otherwise the query won't include
    // anything
    if (options && options.excludeMissingKeys) {
        result = [];
    } else {
        result = [
            {"systemHeader.keyIds": {$exists: false}},
            {"systemHeader.keyIds": {$size: 0}}
        ];
    }


    result.push({"systemHeader.keyIds": {$in: accessKeys}});

    return result;
}

const getCollection = (callback, connectionName ) => {
    let mongodb;
    try{
        mongodb = mongoConnection.getDatabase(connectionName);
    }catch(err){

    }

    if (!mongodb){
        mongoConnection.initialise(connectionName).then(function () {
            mongoConnection.getCollection(function(error, collection) {
                if (error) {
                    callback(error);
                }else{
                    callback(null, collection);
                }
            },connectionName);
        },function errorFunc(err) {
            callback(err);
        });
    }
    else{
        mongoConnection.getCollection(function(error, collection) {
            if (error) {
                callback(error);
            }else{
                callback(null, collection);
            }
        },connectionName);
    }
};

const parseQuery = (query, user) => {
    let queryData;
    let txt = query.text;
    if (txt) {
        queryData = { $text: { $search: txt } };
    }else if (query.filter) {
        if(utilType.isString(query.filter)) {
            let queryFilter = query.filter.replace(/'/g, '"');

            if (query.document) {

                let document = utilType.isString(query.document) ? JSON.parse(query.document) : query.document;

                let doc = {
                    document: document,
                    account: user
                };

                let hbTemplate = Handlebars.compile(queryFilter);
                queryFilter = hbTemplate(doc);
            }

            queryData = utilType.isString(queryFilter) ? JSON.parse(queryFilter) : queryFilter;
        } else {
            //in case of filter is a json object not a string
            queryData = query.filter;
        }
    }

    return queryData;
};

const search = (query, userId, allVersions, options) => {
    params = [];
    const deferred = q.defer();
    const accountDataService = require("../database/dataServices/AccountService");

    let includeAllVersions = false;
    let includeDeleted = false;


    // add support for elasticsearch sourceFilter
    let sourceFilter = query.sourceFilter;
    let projection = query.projection || {};
    if (sourceFilter && sourceFilter.include) {
        for (const include of sourceFilter.include) {
            projection[include] = 1;
        }
    }

    if (query.includeAllVersions) {
        allVersions = query.includeAllVersions;
    }

    //#11535 - includeAllVersions should accept boolean, string, and int
    if (allVersions && (
        allVersions === true
        || allVersions === "true"
        || allVersions === 1
        || allVersions === "1")) {
        includeAllVersions = true;
    }

    if (query.includeDeleted) {
        includeDeleted = query.includeDeleted;
    }

    let size = query.size || query.pageSize || sharedConstants.DEFAULT_PAGE_SIZE;
    if (query.size === 0) {
        size = 0;
    }

    let page = query.page || options.page || sharedConstants.DEFAULT_PAGE;
    let from = query.from || options.from;

    if (!from) from = (parseInt(page)) * size;

    let results = {
        statusCode: utilHttpStatus.OK,
        data: [],
        filter: false
    };

    let user;
    accountDataService.getAccountById(userId).then(function successFunc(dbUser) {
        user = dbUser;

        if (options && options.targetOperation === SharedConstants.OPERATION_TYPE_EXECUTE) {
          // No filter for keyIds because the search service needs to return 403 error for consistent with DataService.
          // The caller need to check Execute on the result.
          return Promise.resolve();
        }

        return ServerAccessManager.getUserAccessKeysForOperation(user.documentId, sharedConstants.OPERATION_TYPE_READ);
    }).then(function getUserAccessKeysSuccess(readAccessKeys) {
        try {
            const connectionName = options.connectionName;
            dsConfig = dbConfig.getDataServicesConfig(connectionName);
            getCollection(function(err, collection) {
                if (err) {
                    const msg = "Error: " + err;
                    logger.error(msg);
                    deferred.reject(msg);
                } else {

                    let queryData = parseQuery(query,user);
                    let filterQuery = queryData.query ? queryData.query :queryData;

                    const pageNumber = from;
                    let numberPerPage = size;

                    let finalQuery = {
                        $and: [
                            filterQuery
                        ]
                    };
                    if (readAccessKeys) {
                        const accessKeysQuery = getAccessKeysQuery(readAccessKeys, options);

                        finalQuery.$and.push({ $or: accessKeysQuery });
                    }

                    if (!includeAllVersions) {
                        finalQuery.$and.push({ "systemHeader.currentVersion": true });
                    }

                    //Filter out deleted documents
                    if (!includeDeleted) {
                        const unDeletedQuery = {
                            "$or": [
                                { "systemHeader.deleted": false },
                                { "systemHeader.deleted": { $exists: false } }
                            ]
                        };
                        finalQuery["$and"].push(unDeletedQuery);
                    }

                    //account Security document should be removed from result
                    const removeAccountSecurity = {
                        "$or": [
                            { "systemHeader.systemType": { "$ne": SharedConstants.SYSTEM_TYPE_ACCOUNT_SECURITY } }
                        ]
                    };
                    finalQuery["$and"].push(removeAccountSecurity);

                    logger.info("Final query to search documents: " + JSON.stringify(finalQuery));

                    var objSort = {};
                    if (queryData.sort && _.isPlainObject(queryData.sort)) {
                        objSort = queryData.sort;
                        logger.info("results sorted by: " + JSON.stringify(objSort));
                    }

                    let mongoCursor = collection.find(finalQuery).project(projection).sort(objSort);

                    if ((pageNumber || pageNumber > -1) && numberPerPage) {
                        mongoCursor.skip(pageNumber).limit(numberPerPage);
                    }

                    mongoCursor.toArray(
                        function(err, result) {
                            if (err) {
                                let message = err.message;

                                if (err.codeName === 'IndexNotFound') {
                                    message = 'Please create at least one full-text index for general search to work.';
                                }

                                let error = {
                                    errorCode: utilHttpStatus.BAD_REQUEST,
                                    message
                                };

                                logger.error("Error on mongo search : " + JSON.stringify(error));
                                logger.error(err);
                                deferred.reject(error);
                            } else {
                                mongoCursor.count().then(function(integerDocCount) {
                                    results.total = integerDocCount;
                                    results.data = result;
                                    results.statusCode = utilHttpStatus.OK;
                                    results.searchProvier = dsConfig.providerName;
                                    deferred.resolve(results);
                                });
                            }
                        }
                    );

                }

            }, connectionName);

        } catch (e) {
            const msg = "Error: " + e;
            logger.error(msg);
            deferred.reject(msg);
        }
    }).catch(function(e) {
        logger.error(e);
        deferred.reject(e);
    });

    return deferred.promise;

};



const getLoadRuleQuery = (rulesetTemplate, document, eventName) =>{

    let statement = {   "systemHeader.systemType" : SharedConstants.SYSTEM_TYPE_DOCUMENT,
                        "systemHeader.templateId" : rulesetTemplate,
                        "event" : eventName,
                        "template.documentId" : document.systemHeader.templateId
                    };

    return {
        "from": 0,
        "size": 1,
        'query': statement
    };

};



const getFindDocumentsQuery = (query) =>{
    let qry = utilType.isString(query) ? JSON.parse(query) : query;
    return {"filter" : qry};
};



const getDoFindAllQuery = (query) =>{
    let qry = utilType.isString(query) ? JSON.parse(query) : query;

    return {"filter" : qry};
};

module.exports = {
    search: search,

    getFindDocumentsQuery: getFindDocumentsQuery,

    getDoFindAllQuery : getDoFindAllQuery

};
