"use strict";

const q = require("q"),
      cbConnection = require("../database/providers/couchbase/CouchbaseConnection"),
      utilHttpStatus = require("../../shared/utils/UtilHttpStatus"),
      cb = require("couchbase"),
      cbUtil = require('../database/providers/couchbase/CouchbaseUtil'),
      ServerAccessManager = require('../database/utils/ServerAccessManager'),
      sharedConstants = require('../../shared/constants/SharedConstants'),
      utilType = require('../../shared/utils/UtilType'),
      Handlebars = require('handlebars'),
      utilHandelbars = require('../../shared/utils/UtilHandlebars'),
      utilString = require('../../shared/utils/UtilString');

const _ = require("lodash/string");
const lodashLang = require("lodash/lang");

let params = [];
let dsConfig;
let bucketName;

utilHandelbars.registerHelper(Handlebars);

const getKeyIdsQuery = (readAccessKeys, options, prefix) => {
  let statement = "";
  let keyIdsStatement = "";
  if (readAccessKeys && readAccessKeys.length > 0) {
    params.push(readAccessKeys);
    keyIdsStatement = " ANY keyId IN " + prefix + "systemHeader.keyIds SATISFIES keyId IN $" + params.length + " END ";
  }

  if (options && options.excludeMissingKeys && keyIdsStatement) {
    statement += " (" + keyIdsStatement;
  } else {
    //add access query check
    statement += " (" + prefix +
      "systemHeader.keyIds IS NULL OR " + prefix +
      "systemHeader.keyIds = [] ";
    if (keyIdsStatement !== "") {
      statement += " OR " + keyIdsStatement;
    }
  }

  statement += ") ";

  return statement;
};

const processSourceFilter = (sourceFilter) => {
  let selectField = "a.* ";
  if (sourceFilter) {
    sourceFilter = sourceFilter.include && sourceFilter.include.length > 0 ?  sourceFilter.include: sourceFilter;
    if (sourceFilter){
        let sfArray =  sourceFilter.toString().split(",");
        selectField = [];
        for (let i =0; i<sfArray.length; i++){
          if (sfArray[i].indexOf(".") > -1){
             let objFld = sfArray[i].split(".");
             selectField.push("a." + objFld[0]);
          }
          else{
            selectField.push("a." + sfArray[i]);
          }
        }
    }
  }

  return selectField.toString();
};

const processSimpleQuery = (query, conjuncts) => {
  let isProcessed = false;
  for (let key in query) {
    if (!lodashLang.isObject(query[key])){
      conjuncts.push({ "field": key, "query": query[key] });
      isProcessed = true;
    }
    else{
      isProcessed = false;
      break;
    }
  }

  return isProcessed;

};

const processFullTextSearch = (query, readAccessKeys, includeAllVersions,
                               includeDeleted, from, size, user, options) => {

  const connectionName = options.connectionName;
  let templateFilter = {};
  let txt = query.text;

  const sourceFilter = query.sourceFilter;


  let qryData = {
    "query": {
      "must": {"conjuncts": []},
      "must_not": {"disjuncts": []}
    }
  };
  let conjuncts = qryData.query.must.conjuncts;
  let disjuncts = qryData.query.must_not.disjuncts;

  if (txt){
    const jsonIndx = txt.search(/{/i);
    if (jsonIndx < 0) {
      txt = utilString.escapeSpecialChar(txt);
      conjuncts.push({ "query": txt + " *"});
    }
    else {
      const jsonTxt = JSON.parse(txt);
      if (jsonTxt.filter) {
        conjuncts.push(jsonTxt.filter);
      }
    }
  }
  else if (query.filter) {

      if (utilType.isString(query.filter)) {
        let queryFilter = query.filter.replace(/'/g, '"');

        if (query.document) {

          const 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);
        }

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

      if (templateFilter.query && templateFilter.query.must && templateFilter.query.must.conjuncts ) {
        qryData.query.must.conjuncts = templateFilter.query.must.conjuncts ? templateFilter.query.must.conjuncts : [];
        conjuncts = qryData.query.must.conjuncts;
      }
      if (templateFilter.query && templateFilter.query.must_not && templateFilter.query.must_not.disjuncts){
        qryData.query.must_not.disjuncts = templateFilter.query.must_not.disjuncts ? templateFilter.query.must_not.disjuncts : [];
        disjuncts = qryData.query.must_not.disjuncts;
      }

      if (conjuncts.length < 1){
        if (templateFilter.query) {
          conjuncts.push(templateFilter.query);
        }
        else{
          const isProcessed = processSimpleQuery(templateFilter, conjuncts);
          if (!isProcessed) {
            conjuncts.push(templateFilter);
          }
        }
      }

      if (templateFilter.size)
        size = templateFilter.size;

      if (templateFilter.from)
        from = templateFilter.from;

  }


  qryData.explain = true;
  qryData.fields = ["*"];
  qryData.highlight = {};
  qryData.from = from;
  qryData.size = size;

  if (templateFilter.sort && templateFilter.sort.length > 0){
    qryData.sort = templateFilter.sort;
  }

  if (!includeAllVersions) {
    conjuncts.push ({"field":"systemHeader.currentVersion", "bool":  true});
  }

  disjuncts.push({"field":"systemHeader.systemType", "query": "accountSecurity"});
  if (!includeDeleted) {
    disjuncts.push({"field":"systemHeader.deleted","bool":true});
  }

  dsConfig = cbConnection.getConfig(connectionName);
  bucketName = dsConfig.bucket;


  let reqData = {};
  reqData.request = "POST";
  reqData.header = "Content-Type: application/json";
  reqData.user = dsConfig.userName + ":" + dsConfig.password;
  reqData.data = JSON.stringify(qryData);

  let selectField = "result.total_hits, " + processSourceFilter(sourceFilter);

  let statement = 'SELECT ' + selectField +
    ' FROM CURL("'+ dsConfig.curlHost + "/api/index/" + dsConfig.fullTextSearchIndexName + '/query", ';

  statement += JSON.stringify(reqData);

  statement += ') as result UNNEST result.hits h INNER JOIN `' + bucketName + '` a ON (h.id = meta(a).id) ';
  statement += " WHERE  ";

  if (readAccessKeys) {
      statement += getKeyIdsQuery(readAccessKeys, options, "a.");
  }
  let finalStatement = statement;

  logger.debug("Couchbase FTS query : " + statement.replace(/"user":.*,"data":/, '"user":"","data":'));
  logger.debug("Couchbase FTS query params : " + JSON.stringify(params));

  return cbUtil.getN1QlQuery(finalStatement);
};

const getAliasName = (selectStatement) =>{
  let arr = selectStatement.split("<bucketName>");
  let aliasName = _.trim(arr[1]).replace(/ .*/,'');

  if (aliasName && aliasName.toLowerCase() !== "where"){
    return aliasName + ".";
  }
  return "";
};

const processSelectStatement = (query, readAccessKeys, includeAllVersions,
                                includeDeleted, from, size, user, options) => {

  const connectionName = options.connectionName;
  dsConfig = cbConnection.getConfig(connectionName);
  bucketName = dsConfig.bucket;

  let templateFilter = getSelectStatement(query);
  let filterQuery;
  if (utilType.isString(templateFilter)) {
    filterQuery = templateFilter;
  }
  else {
    filterQuery = templateFilter.query;
    if (templateFilter.size)
      size = templateFilter.size;

    if (templateFilter.from)
      from = templateFilter.from;
  }

  //moving ORDER BY to the end
  const ordIdx = filterQuery.toLowerCase().lastIndexOf("order by");
  let orderBy;
  if (ordIdx > 0){
    orderBy = filterQuery.substr(ordIdx);
    filterQuery = filterQuery.substr(0, ordIdx);
  }

  const prefix = getAliasName(filterQuery);

  _.templateSettings.interpolate = /<([\s\S]+?)>/g;
  let hbTemplate =  _.template(filterQuery) ;
  let filter = hbTemplate({"bucketName" : "`" + bucketName + "`"});

  let isExistsWhere = filter.toLowerCase().lastIndexOf(" where ") > 0;

  if (isExistsWhere){
    filter += " AND ";
  }
  else{
    filter += " WHERE ";
  }

  if (readAccessKeys) {
      filter += getKeyIdsQuery(readAccessKeys, options, prefix);
  }

  if (!includeAllVersions) {
    filter += " AND " + prefix + "systemHeader.currentVersion = true ";
  }

  //account Security document should be removed from result
  filter += " AND " + prefix +"systemHeader.systemType != '" + SharedConstants.SYSTEM_TYPE_ACCOUNT_SECURITY + "' ";
  if (!includeDeleted) {
    filter += " AND (" + prefix +"systemHeader.deleted = false" + " OR " + prefix + "systemHeader.deleted IS MISSING" + ") ";
  }

  //filter += " OFFSET " + from  + " LIMIT " + size;
  if (orderBy){
    filter += " " + orderBy;
  }
  logger.debug("Couchbase select query : " + filter);
  logger.debug("Couchbase select query params : " + params);

  return cbUtil.getN1QlQuery(filter);

};


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

  let includeAllVersions = false;
  let includeDeleted = false;

  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;

      cbConnection.initialise(connectionName).then(function() {
          let cbBucket = cbConnection.getBucket(connectionName);

          let qry;
          try{
              let processSearch = processFullTextSearch;
              if (getSelectStatement(query)){
                    processSearch = processSelectStatement;
              }
              qry = processSearch(query, readAccessKeys, includeAllVersions, includeDeleted, from, size, user, options);
          }
          catch (e) {
              const msg = "Error: " + e;
              logger.error(msg);
              deferred.reject(msg);
          }

          cbBucket.query(qry, params,  function(err, result) {
              if(err) {
                  let error = {
                    errorCode: utilHttpStatus.BAD_REQUEST,
                    message: err.message
                  };
                  logger.error("Error on couchbase search : " + JSON.stringify(error));
                  logger.error(err);
                  deferred.reject(error);
              }
              else{
                  results.total = 0;
                  if (result.length > 0){
                    results.total = result[0].total_hits;
                  }
                  results.data = result;
                  results.statusCode = utilHttpStatus.OK;
                  results.searchProvier = dsConfig.providerName;
                  results.dbName = bucketName;
                  deferred.resolve(results);
              }
          });
      }, function errorFunc(err) {
        deferred.reject(err);
      });

    } 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 getSelectStatement = (query) => {
  let statement;
  let filter = query.filter;
  if (filter && utilType.isString(filter) &&
    _.startsWith(filter.toLowerCase(), "select")){
    statement = filter;
  }
  else if (filter && filter.query && utilType.isString(filter.query)
    && _.startsWith(filter.query.toLowerCase(), "select")) {
    statement = filter;
  }

  return statement;
};




const getFindDocumentsQuery = (query) =>{
  let statement;
  if (utilType.isString(query) && _.startsWith(query.toLowerCase(), "select")){
    statement =  query;
  }
  else{
    let statement= { query: {
        must: {
          conjuncts: []
        }
      }
    };
    for (let key in query) {
      statement.query.must.conjuncts.push({"field": key, "query" : query[key]});
    }
  }

  return  {
    filter: statement
  };
};



const getDoFindAllQuery = (query) =>{

  let esQuery= { query: {
      must: {
        conjuncts: []
      }
    }
  };
  for (let key in query) {
    esQuery.query.must.conjuncts.push({"field": key, "query" : query[key]});
  }

  return  {
    filter: esQuery
  };

};

module.exports = {
  search: search,

  getFindDocumentsQuery: getFindDocumentsQuery,

  getDoFindAllQuery : getDoFindAllQuery

};
