var fs = require('fs');
var https = require('https');
var http = require('http');
var url = require('url');
var request = require('request');
var xml2js = require('xml2js').parseString;
var strftime = require('strftime');
var math = require('mathjs');
var winston = require('winston');
//var cekstatus = require('./cekstatus.js');
var mongoClient = require('mongodb').MongoClient;
var LRU = require('lru-cache');
var reverseParser = require('./reverse-parser');

var config;
var httpServer;
var aaa;
var logger;
var callbackReport;
var mongodb;

var tasks = LRU(10000);

http.globalAgent.maxSockets = Infinity;
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

var sleep_before_retry = 30000;
var pendingResultCode = ['0005', '0012', '0068', '0090', '0063', '0018', '0096'];

var logTag = __filename.split('/').reverse()[0];

function initMongoClient() {
    if (!config.mongodbstruk || !config.mongodbstruk.url) {
        return;
    }

    try {
        var url = config.mongodbstruk.url;

        mongoClient.connect(url, function(err, db) {
            if (err) {
                logger.warn('Failed to connect to mongodb', {err: err});
                return;
            }
            mongodb = db;
            logger.info('MongoDB connected');
        });
    }
    catch(err) {
        logger.warn('Exception when connecting to mongodb', {err: err, url: url});
    }

}

function prepareResultData(result) {
    var task;

    var data = {};
    data.gateway = config.globals.gateway_name;

    try {
        data.requestId = result.reffid[0].trim();

        var key = config.globals.gateway_name + '.rid:' + data.requestId;
        task = tasks.get(key);
    }
    catch(err) { data.requestId = null; }

    try {
        data.status = result.ResultCode[0].trim();
    }
    catch(err) { data.status = '68' }

    try {
        data.rcmessage = result.ErrorMsg[0].trim();
    }
    catch(err) { data.rcmessage = ''; }

    try {
        data.resptext = '';
    }
    catch(err) { data.resptext = ''; }

    try {
        var ts = moment(task.timestamp, 'YYYYMMDDHHmmss').format('YYYY-MM-DD HH:mm:ss')
        data.dt = ts;
    }
    catch(err) {
        logger.warn('Exception when getting timestamp data, using current timestamp', {err: err, result: result, task: task});
        data.dt = strftime('%Y-%m-%d %H:%M:%S', new Date());
    }

    try {
        data.namapel = result.nama_pel[0].trim();
    }
    catch(err) { data.namapel = 'UNKNOWN'; }

    try {
        data.msn = result.nsm[0].trim();
    }
    catch(err) { data.msn = 'UNKNOWN'; }

    try {
        data.idpel = result.idpel[0].trim();
    }
    catch(err) { data.idpel = 'UNKNOWN'; }

    try {
        data.tarifdaya = result.tarif[0].trim();
    }
    catch(err) { data.tarifdaya = 'UNKNOWN'; }

    try {
        data.admin = parseInt(result.adminfee[0].trim());
    }
    catch(err) { data.admin = 0; }

    try {
        data.adm = parseInt(result.admin_fee[0].trim());
    }
    catch(err) { data.adm = 0; }

    try {
        data.hargapelanggan = parseInt(result.amount_trx[0].trim()) - data.adm;
    }
    catch(err) { data.hargapelanggan = 0; }

    try {
        data.jumlahkwh = result.jml_daya[0].trim();
    }
    catch(err) { data.jumlahkwh = 0; }

    try {
        data.token = result.token[0].trim();
    }
    catch(err) { data.token = 0; }

    try {
        data.ppn = result.ppn_fee[0].trim();
    }
    catch(err) { data.ppn_fee = 0; }

    try {
        data.ppj = result.ppj_fee[0].trim();
    }
    catch(err) { data.ppj = 0; }

    try {
        data.angsuran = result.angsuran_fee[0].trim();
    }
    catch(err) { data.angsuran = 0; }

    try {
        data.meterai = result.materai_fee[0].trim();
    }
    catch(err) { data.materai_fee = 0; }

    return data;
}

function saveTokenToMongoDb(result) {
    if (!mongodb) {
        return;
    }

    if (!config.mongodbstruk) {
        return;
    }

    if (!config.mongodbstruk.collection) {
        return;
    }

    var data = prepareResultData(result);

    try {
        mongodb.collection(config.mongodbstruk.collection).insertOne(data);
    }
    catch(err) {
        logger.warn('Error when inserting data to mongodb', {err: err, data: data});
    }
}

function putTaskToCache(task) {
    var key = config.globals.gateway_name + '.rid:' + task.requestId;

    try {
        tasks.set(key, task);
    }
    catch(err) {
        logger.warn('Error writing to task to cache', {err: err, key: key, task: task});
    }
}

function topupRequest(task) {
    aaa.insertTaskToMongoDb(task);
    putTaskToCache(task);

    var ts = strftime('%Y%m%d%H%M%S', new Date());

    var data =
        config.h2h_out.userid
        + '|' + config.h2h_out.password
        + '|' + task['remoteProduct']
        + '|' + task['destination'] + '|0';

    var options = {
        url: config.h2h_out.partner,
        qs: {
            ts: ts,
            data: data,
            reffid: task['requestId']
        }
    };
    logger.info('Creating http request', {options: options});

    request(options, function (error, response, body) {
        var responseCode = '40';
        var responseMessage = 'Gateway Error';

        if (error) {

            logger.warn('HTTP REQUEST ERROR', error);
            callbackReport(task['requestId'], '89', 'HTTP REQUEST ERROR (' + error + ')');

        } else if (response.statusCode != 200) {

            var error_message = 'GATEWAY ERROR (HTTP RESPONSE CODE: ' + response.statusCode + ')';
            logger.warn(error_message);
            callbackReport(task['requestId'], '91', error_message);

        } else {

            logger.info('DIRECT RESPONSE', {requestId: task.requestId, body: body});

            xml2js(body, function (err, result) {
                if (err) {
                    logger.warn('Error parsing xml response', {requestId: task.requestId, err: err, body: body});
                    callbackReport(task['requestId'], '40', body);
                } else {
                    var directResponse = result;
                    logger.verbose('DIRECT RESPONSE from partner parsed', {directResponse: directResponse});

                    saveTokenToMongoDb(directResponse.Result);

                    try {
                        var result_price;
                        try {
                            result_price = directResponse.Result.Price[0].trim();
                        }
                        catch(err) {
                            result_price = 0;
                        }

                        var result_error_message;
                        try {
                            result_error_message = directResponse.Result.ErrorMsg[0].trim();
                        }
                        catch(err) {
                            result_error_message = '';
                        }

                        var resultCode = directResponse.Result.ResultCode[0].trim();

                        responseMessage =
                            'ResultCode: ' + resultCode
                            + ' | ErrorMsg: ' + result_error_message
                            + ' | DateTime: ' + directResponse.Result.DateTime[0].trim()
                            + ' | nsm: ' + directResponse.Result.nsm[0].trim()
                            + ' | idpel: ' + directResponse.Result.idpel[0].trim()
                            + ' | reffid: ' + directResponse.Result.reffid[0].trim()
                            + ' | TransID: ' + directResponse.Result.TransID[0].trim()
                            + ' | reff_switching: ' + directResponse.Result.reff_switching[0].trim()
                            + ' | amount_trx: ' + directResponse.Result.amount_trx[0].trim()
                            + ' | token: ' + directResponse.Result.token[0].trim()
                            + ' | PrevBalance: ' + directResponse.Result.PrevBalance[0].trim()
                            + ' | Price: ' + result_price
                            + ' | EndBalance: ' + directResponse.Result.EndBalance[0].trim()
                            ;

                        logger.info('Response message: ' + responseMessage);

                        if ( (resultCode == '0099') && (result_error_message.search(/METER .* YANG ANDA MASUKAN SALAH/) >= 0) ) {
                            callbackReport(task.requestId, '14', responseMessage);
                            return;
                        }
                        else if ( (resultCode == '0099') && (result_error_message.search(/IDPEL .* YANG ANDA MASUKAN SALAH/) >= 0) ) {
                            callbackReport(task.requestId, '14', responseMessage);
                            return;
                        }
                        else if ( (resultCode == '0099') && (result_error_message.search(/KONSUMEN .* DIBLOKIR HUBUNGI PLN/) >= 0) ) {
                            callbackReport(task.requestId, '77', responseMessage);
                            return;
                        }
                        else if ( (resultCode == '0099') && (result_error_message.search(/TOTAL KWH MELEBIHI BATAS MAKSIMUM/) >= 0) ) {
                            callbackReport(task.requestId, '47', responseMessage);
                            return;
                        }
                        else if ( (resultCode == '0099') && (result_error_message.search(/INQUIRY TIMEOUT, SILAHKAN DICOBA KEMBALI/) >= 0) ) {
                            callbackReport(task.requestId, '91', responseMessage);
                            return;
                        }

                        if (aaa) {
                            // update balance
                            aaa.updateBalance(directResponse.Result.EndBalance[0]);
                        }

                        if (resultCode == '0000') {
                            var nama_pelanggan = directResponse.Result.nama_pel[0].trim();
                            nama_pelanggan =  nama_pelanggan.replace(/-\/-/g, '-');
                            var sn = directResponse.Result.token[0].trim() + '/' + nama_pelanggan + '/' + directResponse.Result.tarif[0].trim() +  'VA/' + directResponse.Result.jml_daya[0].trim();
                            sn = sn.replace(/\s/g, '-');

                            responseMessage = 'SN=' + sn + '; ' + responseMessage;
                            logger.info('New response message: ' + responseMessage);
                        }

                        if (pendingResultCode.indexOf(resultCode) != -1) {
                            callbackReport(task['requestId'], '68', responseMessage);

                            /*

                            logger.info('Got pending status, requesting advice from webreport in ' + sleep_before_retry + 'ms');
                            setTimeout(function () {
                                cekstatus.advice({trxid: directResponse.Result.TransID[0].trim()}, callbackFromWebReport);
                            }, sleep_before_retry);

                            */

                            return;
                        }

                        responseCode = resultCode.replace(/^00/, "");

                        if (result_error_message == 'Inq - APLICATION SERVER RESPONSE TIMEOUT') {
                            responseCode = '91';
                        }

                    }
                    catch(err) {
                        responseCode = '40';
                        responseMessage = 'Invalid response from gateway';
                    }
                }

                callbackReport(task['requestId'], responseCode, responseMessage);
            });
        }

        //callbackReport(task['requestId'], responseCode, responseMessage);
    });
}

function callbackFromWebReport(status) {
    if (!status) {
        logger.warn('Advice from webreport return empty status');
        return;
    }

    logger.info('Got advice result from webreport', {status: status});

    var responseCode = '68';

    var result_price = 0;
    try {
        result_price = directResponse.Result.Price[0].trim();
    }
    catch(err) {}

    var errorMsg = '';
    try {
        errorMsg = status.response.errormsg[0];
    }
    catch(err) {}

    var responseMessage = '';
    try {
        responseMessage =
            'Hasil advice dari webreport '
            + 'ResultCode: ' + status.response.resultcode[0]
    }
    catch(err) {
        logger.warn('Error parsing ResultCode from webreport advice.', {err: err});
    }

    try {
        responseMessage =
            responseMessage
            + ' | ErrorMsg: ' + errorMsg
    }
    catch(err) {
        logger.warn('Error parsing ErrorMsg from webreport advice.', {err: err});
    }

    try {
        responseMessage =
            responseMessage
            + ' | TrxDate: ' + status.trxDate;
    }
    catch(err) {
        logger.warn('Error parsing TrxDate from webreport advice.', {err: err});
    }

    try {
        responseMessage =
            responseMessage
            + ' | UpdateDate: ' + status.updateDate;

    }
    catch(err) {
        logger.warn('Error parsing UpdateDate from webreport advice.', {err: err});
    }

    try {
        responseMessage =
            responseMessage
            + ' | nsm: ' + status.response.nsm[0];
    }
    catch(err) {
        logger.warn('Error parsing nsm from webreport advice.', {err: err});
    }

    try {
        responseMessage =
            responseMessage
            + ' | idpel: ' + status.response.idpel[0];
    }
    catch(err) {
        logger.warn('Error parsing idpel from webreport advice.', {err: err});
    }

    try {
        responseMessage =
            responseMessage
            + ' | reffid: ' + status.response.reffid[0].trim();
    }
    catch(err) {
        logger.warn('Error parsing reffid from webreport advice.', {err: err});
    }

    try {
        responseMessage =
            responseMessage
            + ' | TransID: ' + status.response.transid[0].trim();
    }
    catch(err) {
        logger.warn('Error parsing TransID from webreport advice.', {err: err});
    }

    try {
        responseMessage =
            responseMessage
            + ' | reff_switching: ' + status.response.reff_switching[0];
    }
    catch(err) {
        logger.warn('Error parsing reff_switching from webreport advice.', {err: err});
    }

    try {
        responseMessage =
            responseMessage
            + ' | amount_trx: ' + status.response.amount_trx[0];
    }
    catch(err) {
        logger.warn('Error parsing amount_trx from webreport advice.', {err: err});
    }

    try {
        responseMessage =
            responseMessage
            + ' | token: ' + status.response.token[0];
    }
    catch(err) {
        logger.warn('Error parsing token from webreport advice.', {err: err});
    }

    try {
        responseMessage =
            responseMessage
            + ' | PrevBalance: ' + status.response.prevbalance[0];
    }
    catch(err) {
        logger.warn('Error parsing PrevBalance from webreport advice.', {err: err});
    }

    try {
        responseMessage =
            responseMessage
            + ' | Price: ' + status.amount;
    }
    catch(err) {
        logger.warn('Error parsing Price from webreport advice.', {err: err});
    }

    try {
        responseMessage =
            responseMessage
            + ' | EndBalance: ' + status.response.endbalance[0];
    }
    catch(err) {
        logger.warn('Error parsing EndBalance from webreport advice.', {err: err});
    }


    if ((status.status == 'S') && (status.response.resultcode[0] == '0000')) {
        responseCode = '00';

        var nama_pelanggan = status.response.nama_pel[0] ;
        nama_pelanggan =  nama_pelanggan.replace(/-\/-/g, '-');

        var sn = status.response.token[0] + '/' + nama_pelanggan + '/' + status.response.tarif[0] + 'VA/' + status.response.jml_daya[0];
        responseMessage = 'SN=' + sn + '; ' + responseMessage;

    } else if ((status.status == 'P') || (status.status == 'W')) {

        return callbackReport(status.response.reffid[0].trim(), '68', responseMessage);

        /*
        logger.info('Got pending status, requesting advice from webreport in ' + sleep_before_retry + 'ms');

        setTimeout(function () {

            cekstatus.advice({trxid: status.trxId,}, callbackFromWebReport);

        }, sleep_before_retry);
        */

    } else {
        responseCode = status.response.resultcode[0].replace(/^00/, "");
        if (['00', '05', '12', '68', '90', '63', '18', '96'].indexOf(responseCode) >= 0) {
            responseCode = '40';
        }
    }


    return callbackReport(status.response.reffid[0].trim(), responseCode, responseMessage);
}

function createHttpReportServer() {
    var httpServer = http.createServer(function(request, response) {
        var qs = url.parse(request.url, true).query;
        var path = url.parse(request.url).pathname;

        logger.info('Got reverse report from partner', {path: path, qs: qs});
        response.end('OK');

        var requestId = qs.reffid;
        var resultCode = qs.rescode;

        if (requestId && resultCode && resultCode != '0') {

        }

        if (requestId && resultCode) {
            if (resultCode == '0') {
                var responseData = reverseParser.parseMessage(qs.msg);
                var sn = [
                    qs.token,
                    responseData.namapel,
                    responseData.tarifdaya,
                    responseData.jumlahkwh
                ].join('/');

                return callbackReport(requestId, '00', 'SN=' + sn + ';' + 'Got reverse report: ' + qs.msg);
            }
            else if (pendingResultCode.indexOf(resultCode) >= 0) {
                return callbackReport(requestId, '68', 'Got reverse report: ' + qs.msg);
            }
            else {
                return callbackReport(requestId, '40', 'Got reverse report: ' + qs.msg);
            }
        }


        /*

        var  trxid;
        try {
            trxid = qs.transid;
        }
        catch(err) {
        }

        if (trxid) {
            logger.info('Requesting advice from webreport', {trxid: trxid})
            cekstatus.advice({trxid: trxid}, callbackFromWebReport);
        }
        */

    });

    httpServer.listen(config.h2h_out.listen_port, function() {
        logger.info('HTTP Reverse/Report server listen on port ' + config.h2h_out.listen_port);
    });
}

function start(options) {
    if (!options) {
        console.log('Undefined options, terminating....');
        process.exit(1);
    }

    if (options.config) {
        config = options.config;
    } else {
        console.log('Undefined options.config, terminating....')
        process.exit(1);
    }

    if (options.aaa) {
        aaa = options.aaa;
        callbackReport = options.aaa.callbackReportWithPushToMongoDb;
    } else {
        console.log('Undefined options.aaa, terminating....')
        process.exit(1);
    }

    if (options && options.logger) {
        logger = options.logger;
    } else {
        logger = new winston.Logger({
            transports: [
              new (winston.transports.Console)()
            ]
        });
    }

    cekstatus.setLogger(logger);

    createHttpReportServer();
    initMongoClient();
}

exports.start = start;
exports.topupRequest = topupRequest;