'use strict';

var fs                  = require('fs');
var mongo               = require('mongodb');
var mongoConnection     = require('../database/providers/mongodb/MongoConnection');
var esIndexingService   = require('./../search/ElasticIndexingService');
var elasticConnection   = require('./../search/ElasticConnection');
var UtilElasticSearch   = require('./../utils/UtilElasticSearch.js');
var dbConfig = require('../database/providers/DatabaseConfig');

var indexInfoFile = './elastic-index/' + UtilElasticSearch.elasticIndexName() + '.json';
var indexLogsFile = './elastic-index/' + UtilElasticSearch.elasticIndexName() + '.log';

var indexInfo = {};
var idle = false;

var cliProgress = require('cli-progress');
var bar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_grey);

/*****************************/
var limit = 1000; // documents per batch, default 100
var defaultTimeout = 500; // in millis

var pages = 0;
var currentPage = 0;
var lastSyncDate = null;
var collection;
var mongoQuery = { "systemHeader.serverUpdatedDate" : {$exists: true} };
var documentsProcessed = 0;
var mongoRetries = 0;
var esClient;

function start() {
    var syncConfig = {};
    var esConfig = config.get("elasticsearch");
    if(esConfig && esConfig.sync) {
        syncConfig = esConfig.sync;
    }

    limit = syncConfig.limit ? syncConfig.limit : limit;
    defaultTimeout = syncConfig.timeOut ? syncConfig.timeOut : defaultTimeout;

    var dsConfig = dbConfig.getDataServicesConfig(SharedConstants.DEFAULT_DB_CONNECTION);
    var startIndex = startMongo;

    startIndex();
}

function countIndexedDocuments() {
    const elasticQuery = { "query": { "bool": {} } };
    var elasticIndexName = UtilElasticSearch.elasticIndexName();

    var options = {
        "index": elasticIndexName,
        "from": 1,
        "size": 1,
        "body": elasticQuery
    };

    return new Promise((resolve, reject) => {
        getEsClient().search(options, (err, result) => {
            if (err) {
                reject(err);
            }

            resolve(result.body.hits.total);
        });
    });
}

function startMongo() {
    mongoConnection.initialise(null, dbCloseCallback).then(function success() {
        fs.readFile(indexInfoFile, 'utf8', function (err, data) {
            try {
                indexInfo = JSON.parse(data);
                lastSyncDate = indexInfo.lastSyncDate;
            } catch (e) {}

            mongoConnection.getCollection(function (err, col) {
                if (err) {
                    var errorMsg = "Error creating collection to find all: " + err;
                    logger.error(errorMsg);
                    exit(1);
                }

                collection = col;
                countIndexedDocuments().then((indexedCount) => {
                    if (!indexInfo.totalProcessed) {
                        indexInfo.totalProcessed = indexedCount + (indexInfo.errors || 0);
                    }

                    return countMongo();
                }).then(function(retrievedCount) {
                    logger.info("Processed: " + indexInfo.totalProcessed + '/' + retrievedCount);

                    if (retrievedCount !== 0) {
                        indexInfo.totalRecords = retrievedCount;
                        pages = indexInfo.totalRecords / limit;
                        currentPage = indexInfo.totalProcessed ? (indexInfo.totalProcessed / limit) : 1;
                    }

                    indexInfo.totalRecords = retrievedCount;
                    indexInfo.totalProcessed = (indexInfo.totalProcessed || 0);
                    indexInfo.errors = (indexInfo.errors || 0);

                    bar.start(indexInfo.totalRecords, indexInfo.totalProcessed);
                    startMongoPollProcess();
                }).catch(err => {
                    logger.error('Error: ' + err.message);

                    if (mongoRetries >= 3) {
                        exit(1);
                    }

                    mongoRetries++;

                    logger.info('Restarting process in 5 seconds... Retries: (' + mongoRetries + ' of 3).');
                    setTimeout(() => {
                        startMongo();
                    }, 5000);
                });
            });
        });
    }, function error(err) {
        logger.error(err);
        exit(1);
    });
};

function dbCloseCallback() {
    exit(1);
}

async function countMongo () {
    return await collection.find(mongoQuery).count();
}

async function countCheckerPoll() {
    const count = await countMongo();
    if (indexInfo.totalRecords < count) {
        idle = false;
        logger.info((count - indexInfo.totalRecords) + " updated documents found.");
        startMongo();
    } else {
        idle = true;
        pollProcess(countCheckerPoll, true);
    }
}

function startMongoPollProcess() {

    var mongoCursor = collection.find(mongoQuery)
        .sort({ "systemHeader.serverUpdatedDate": 1 })
        .skip(indexInfo.totalProcessed).limit(limit);

    mongoCursor.toArray(
        function (err, data) {
            if (err) {
                logger.error(err);
            } else {
                var haveDocumentsToIndex = false;
                if (indexInfo.totalProcessed < indexInfo.totalRecords) {
                    haveDocumentsToIndex = true;
                }

                if (haveDocumentsToIndex) {
                    currentPage++;
                    indexInBatch(data).then(() => {
                        pollProcess(startMongoPollProcess);
                    });
                } else {
                    if (!idle) {
                        lastSyncDate = indexInfo.lastSyncDate;
                        logger.info("Total Processed: " + indexInfo.totalProcessed);
                        logger.info("Waiting for new documents to index.");
                        idle = true;
                    }

                    pollProcess(countCheckerPoll, true);
                }
            }
        });
}

async function indexInBatch(documents) {
    var bulkOp = esIndexingService.initializeBulkIndex();

    for (const doc of documents) {
        var id = doc._id;

        try {
            await bulkOp.addToBulk(id, doc);
        } catch(err) {
            indexInfo.errors++;
            appendErrorDocToFile(id, doc, err.message);
        }

        indexInfo.lastSyncDate = doc.systemHeader.serverUpdatedDate;
        indexInfo.lastSyncVersion = doc.systemHeader.versionId;
    }

    try {
        const indexResponse = await bulkOp.bulkIndex();
        if (indexResponse.body.errors) {
            // var batch = collection.initializeOrderedBulkOp();
            for (var i = 0; i < indexResponse.body.items.length; i++) {
                var item = indexResponse.body.items[i];
                var id = item.index._id;

                if (item.index.error) {
                    indexInfo.errors++;
                    const errDoc = documents.find(d => d._id.toHexString() === id);
                    appendErrorDocToFile(id, errDoc, item);

                    // var objectId = new mongo.ObjectID(id);
                    // batch.find({ $or: [ {'_id': objectId}, {'_id': id}]})
                    //     .update({$set: {"systemHeader.indexFail": true}});
                }
            }
            // batch.execute();
        }
    } catch(err) {
        appendErrorToFile({
            error: err.message,
            query: mongoQuery,
            skip,
            limit
        });
    } finally {
        indexInfo.totalProcessed += documents.length;
        writeIndexInfo();
        bar.update(indexInfo.totalProcessed);
    }
}

function pollProcess(pollProcess, withTimeout) {
    if (withTimeout) {
        setTimeout(pollProcess, defaultTimeout);
    } else {
        pollProcess();
    }
};

function exit(exitCode, exitWait) {
    if (!exitWait) {
        exitWait = 60000;
    }
    logger.warn("Exiting process in " + exitWait + "ms.");

    setTimeout(function() {
        logger.warn("Exiting...");
        process.exit(exitCode);
    }, exitWait);
}

function writeIndexInfo() {
    fs.writeFile(indexInfoFile, JSON.stringify(indexInfo), function(err) {
        if(err) logger.error(err);
    });
}

function appendErrorDocToFile(id, document, error) {
    const errorObj = {
        _id: id,
        error: error
    };

    if (document) {
        errorObj.documentId = document.documentId;
        if (document.systemHeader) {
            errorObj['systemHeader.currentVersion'] = document.systemHeader.currentVersion;
            errorObj['systemHeader.versionId'] = document.systemHeader.versionId;
        }
    }

    appendErrorToFile(errorObj);
}

function appendErrorToFile(esIndexingError) {
    var error = "\n Indexing Error #" + indexInfo.errors + "\n" + JSON.stringify(esIndexingError, null, 4);
    fs.appendFileSync(indexLogsFile, error);
}

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

    return esClient;
}

////////////////////////////////////
// public api
var ElasticSyncProcessor = {

    start: start

};

module.exports = ElasticSyncProcessor;
