'use strict';

var q = require("q");
var logger = require('../utils/Logger').loggers.get('ftSearch');
var elasticConnection = require('./ElasticConnection');
var UtilElasticSearch = require('../../server/utils/UtilElasticSearch.js');
var _ = require('lodash/lang');

const lodashCloneDeep = require('lodash.clonedeep');

var esClient;

function BulkIndexOperation() {
    var self = this;

    self.bulk = [];
    self.indexed = false;
    self.err = null;
}

/**
 * indexingId - the id to index with. This will be the _id in Mongo but may be different in Couchbase and other databases
 * document, this is passed in as a separate parameter so that it can be read from a different field with different databases. In
 * Couchbase the doc_id is outside the document
 */
BulkIndexOperation.prototype.addToBulk = function(indexingId, document) {
    var deferred = q.defer();
    var self = this;

    var elasticIndexName = UtilElasticSearch.elasticIndexName();

    if (!document) {
        deferred.reject(new Error("document not specified in ElasticSearch bulk indexing"));
    } else if (!document.documentId) {
        deferred.reject(new Error("documentId not specified in ElasticSearch bulk indexing. _id: " + indexingId));
    } else if (!indexingId) {
        deferred.reject(new Error("indexingId not specified in ElasticSearch bulk indexing. This is the _id in MongoDB"));
    } else if (!indexingId) {
        deferred.reject(new Error("indexingId not specified in ElasticSearch bulk indexing. This is the _id in MongoDB"));
    } else {
        let index = {
            "_index": elasticIndexName,
            "_id": indexingId
        };

        self.bulk.push({
            "index": index
        });

        var indexingDocument = documentToIndex(document);
        self.bulk.push(indexingDocument);
        deferred.resolve();
    }

    return deferred.promise;
};

BulkIndexOperation.prototype.bulkIndex = function() {
    var deferred = q.defer();
    var self = this;

    if (UtilElasticSearch.disableIndexing()) {
        deferred.resolve('Indexing disabled');
    } else if (self.err) {
        deferred.reject(self.err);
    } else if (!self.bulk) {
        deferred.reject(new Error("Bulk index operation has not been initialised"));
    } else if (self.bulk.length === 0) {
        deferred.reject(new Error("No documents to index in bulk operation"));
    } else {
        isIndexExisting(function(err, exist) {
            err = err ? err : self.err;

            if (err) {
                deferred.reject(err);
            } if (!exist) {
                deferred.reject("Index " + UtilElasticSearch.elasticIndexName() + " does not exist!");
            } else {
                if (!self.indexed) {
                    getEsClient().bulk({
                        refresh: true,
                        body: self.bulk
                    }, function (err, resp) {
                        if (err) {
                            deferred.reject(err);
                        } else {
                            self.indexed = true;
                            deferred.resolve(resp);
                        }
                    });
                } else {
                    deferred.reject("Bulk instance already indexed!");
                }
            }
        });
    }

    return deferred.promise;
};

module.exports.initializeBulkIndex = function() {
    return new BulkIndexOperation();
};

function documentToIndex(document) {
    // delete the _id from the document because ElasticSearch doesn't allow _id to be in the document.
    // You will get the following error if _id is in the document:
    // MapperParsingException[failed to parse]; nested:
    // IllegalStateException[Mixing up field types:
    // class org.elasticsearch.index.mapper.core.StringFieldMapper$StringFieldType !=
    // class org.elasticsearch.index.mapper.internal.IdFieldMapper$IdFieldType on field _id];

    // use a clone so that _id will still be in the document saved to the database
    var result;

    if (document) {
        result = lodashCloneDeep(document);
        delete result._id;
    } else {
        logger.warn("No document passed to documentToIndex function");
    }

    return result;
}

module.exports.index = async function(indexingId, document, dsConfig) {
  if (UtilElasticSearch.disableIndexing() || ( dsConfig && dsConfig.disableElasticSearchIndexing)) {
    return 'Indexing disabled';
  } else if (!document) {
    throw new Error('Document not specified in ElasticSearch indexing');
  } else if (!document.documentId) {
    throw new Error("documentId not specified in ElasticSearch indexing");
  } else if (!indexingId) {
    throw new Error('indexingId not specified in ElasticSearch indexing. This is the _id in MongoDB');
  } else {
    const elasticIndexName = UtilElasticSearch.elasticIndexName();

    const indexOptions = {
      "index": elasticIndexName,
      "id": indexingId,
      "body": documentToIndex(document),
      "refresh": true
    };

    try {
      await getEsClient().index(indexOptions);
      return true;
    } catch (error) {
      if (error.message && error.message.indexOf("mapper_parsing_exception") !== -1) {
        logger.error(error);
        throw new Error("Mapping Error, please contact administrator.");
      } else {
        throw error;
      }
    }
  }
};

function isIndexExisting(callback) {
    var elasticIndexName = UtilElasticSearch.elasticIndexName();

    getEsClient().indices.exists(
        {'index': elasticIndexName},
        function(err, response, status) {
            if (!response || err) {
                err = err ? err : "ElasticSearch index " + elasticIndexName +
                " has not been created. Please create the index using the curl " +
                "command with the correct template json file.";
                callback(err);
            } else {
                callback(err, true);
            }
        }
    );
}

function getEsClient() {
    if (!esClient) {
        esClient = elasticConnection.esConnection(UtilElasticSearch.searchConfig());
    }

    return esClient;
}

module.exports.bulkIndex = function(params, dsConfig) {
    var bulkOp = new BulkIndexOperation();

    return new Promise(function(resolve, reject){
        if (dsConfig && dsConfig.disableElasticSearchIndexing) {
            return resolve('Indexing disabled');
        }
        else{
            var updateDocument = params.updateDocument;
            var finalDocumentIndexingId = params.finalDocumentIndexingId;
            var versionDocuments = params.versionDocuments;

            bulkOp.addToBulk(finalDocumentIndexingId, updateDocument);

            for(var k = 0 ; k < versionDocuments.length ; k++){
                var version = _.clone(versionDocuments[k]);
                var verIndexingId = DBProvider.dataUpdateService.getVersionIndexId(version);
                bulkOp.addToBulk(verIndexingId, version);
            }

            bulkOp.bulkIndex().then(function successBulk(response) {
                if (response.errors || (response.body && response.body.errors) ) {
                    var error;
                    var items = response.items ? response.items : response.body ? response.body.items : null;
                    for (var x = 0; x < items.length; x++) {
                        var item = items[x].index;
                        if (item.error && item.error.reason) {
                            error = item.error.reason;
                        }
                    }

                    logger.error("Error indexing to ElasticSearch when updating document: " + error);
                    reject(new Error(error));
                } else {
                    resolve(true);
                }

            }, function indexingError(error) {
                reject(new Error(error));
            });
        }
    });


};
