var winston = require('winston');
var soap = require('soap');
var crypto = require('crypto');
var strftime = require('strftime');
var fs = require("fs");
var http = require("http");
var url = require("url");
var mongoClient = require('mongodb').MongoClient;
var moment = require('moment');

process.chdir(__dirname);

var max_retry = 10;
var sleep_before_retry = 5000;

var config;
var matrix;
var callbackReport;
var aaa;
var logger;
var options;
var mongodb;

function start(_config, _callbackReport, options) {
    config = _config;
    callbackReport = _callbackReport

    if (options && options.aaa) {
            aaa = options.aaa;
    }

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

    if (options && options.matrix) {
        matrix = options.matrix;
    }

    initMongoClient();
}

function callbackReportWrapper(requestId, responseCode, message, dontIncrement) {
    callbackReport(requestId, responseCode, message);

    if (dontIncrement) {
        return;
    }

    try {
        aaa.incrementStrikeStatus(responseCode);
    } catch(err) {
        logger.warn("Gagal aaa.incrementStrikeStatus: " + err);
    }
}

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

    try {
        var url = config.mongodb.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 insertTaskToMongoDb(task) {
    if (!isMongoReady()) { return; }

    task.supplier = config.globals.gateway_name;
    task.rc = '68';

    try {
        mongodb.collection(config.mongodb.collection).insertOne(task);
    }
    catch(err) {
        //logger.warn('Exception when inserting document to mongodb', {err: err, task: task});
    }
}

function pushResponseToMongoDb(task, response, rc) {
    if (!isMongoReady()) { return; }


    try {
        if (!response.ts) {
            response.ts = strftime('%Y-%m-%d %H:%M:%S', new Date());
        }

        mongodb.collection(config.mongodb.collection).updateOne(
            {requestId: task.requestId},
            {
                $set: {
                    lastResponse: response,
                    supplier: config.globals.gateway_name,
                    rc: rc
                },
                $push: {
                    responses: response
                }
            },
            function(err, result) {
                if (err) {
                    logger.warn('Error when pushing response to mongodb', {err: err, task: task, response: response});
                    return;
                }
            }
        );
    }
    catch(err) {
        logger.warn('Exception when pushing response to mongodb', {err: err, task: task, response: response});
    }
}

function isMongoReady() {
    if (!config.mongodb) { return; }
    if (!config.mongodb.collection) { return; }
    if (!mongodb) { return; }

    return true;
}

function topupRequest(task, retry) {

    try {
        if (config && config.globals && config.globals.reject_on_pending_count && matrix && matrix.strikeStatus && matrix.strikeStatus.pending) {

            var pendingCount = matrix.strikeStatus.pending;
            var pendingLimitCount = parseInt(config.globals.reject_on_pending_count);

            if (pendingLimitCount <= matrix.strikeStatus.pending) {

                logger.warn(
                    'Reject trx karena pending terlalu banyak',
                    {pendingCount: pendingCount, pendingLimitCount: pendingLimitCount}
                );

                callbackReport(task.requestId, '13', 'Reject trx karena pending terlalu banyak');
                return;
            }
        }
    }
    catch(err) {
        logger.warn("Exception saat periksa pendingLimit: " + err);
    }


    task.ts = moment(task.timestamp, 'YYYYMMDDHHmmss').format('YYYY-MM-DD HH:mm:ss');
    task.ts_date = moment(task.timestamp, 'YYYYMMDDHHmmss').format('YYYY-MM-DD');

    insertTaskToMongoDb(task);
    saldoCheck(billpayment, task);
}

function saldoCheck(callback, task) {

    var params = {
        userName: config.h2h_out.userid,
        productCode: '00000' ,
        terminal: 'H2HIPN10',
        transactionType: '61',
        reff: Math.ceil( Math.random() * 99999999 ),
        timeStamp: strftime('%Y-%m-%d %H:%M:%S', new Date())
    }

    params.signature = createSignatureForSaldoCheck(params, config.h2h_out.password);

    soap.createClient(config.h2h_out.partner, function(err, soapClient) {

        if (err) {

            var errorMessage = 'Error creating soap client for saldoCheck: ' + err;

            logger.warn(errorMessage, {err: err});
            callbackReportWrapper(task.requestId, '40', errorMessage);
            pushResponseToMongoDb(task, {supplier: config.globals.gateway_name, raw: errorMessage}, '40');

            return;
        }

        logger.info('Requesting to service', {url: config.h2h_out.partner, params: params});
        soapClient.apih2h.apih2hPort.saldoCheck({ inputSaldo: params }, function(err, result) {

            logger.verbose(
                'Got saldoCheck response',
                {
                    lastEndpoint: soapClient.lastEndpoint,
                    lastRequest: soapClient.lastRequest,
                    lastMessage: soapClient.lastMessage,
                    lastResponse: soapClient.lastResponse,
                }
            );

            if (err) {
                var errorMessage = 'Error requesting saldoCheck: ' + err;

                logger.warn(errorMessage, {err: err});
                callbackReportWrapper(task.requestId, '40', errorMessage);
                pushResponseToMongoDb(task, {supplier: config.globals.gateway_name, raw: errorMessage}, '40');
            }

            var balance;
            logger.verbose('saldoCheck result', {result: result});

            try {
                balance = result.outputParameter.bit61.$value;
            }
            catch(e) {
                balance = 'UNKNOWN';
            }


            if (task) {
                callback(task, balance);
            }
        });
    });

}

function billpayment(task, balance) {

    var terminalSuffix = Math.ceil( Math.random() * 20 ) + 10;
    var remoteProduct = task.remoteProduct.split(',');

    var params = {
        userName: config.h2h_out.userid,
        productCode: remoteProduct[0] ,
        terminal: 'H2HIPN' + terminalSuffix,
        transactionType: '50',
        billNumber: createBillNumber(task.destination),
        amount: remoteProduct[1],
        bit61: createBillNumber(task.destination),
        reff: task.requestId,
        timeStamp: strftime('%Y-%m-%d %H:%M:%S', new Date())
    }

    var signature = createSignature(params, config.h2h_out.password);
    params.signature = signature;

    soap.createClient(config.h2h_out.partner, function(err, soapClient) {

        var _params = {
            userName: params.userName,
            signature: params.signature,
            productCode: params.productCode,
            terminal: params.terminal,
            transactionType: params.transactionType,
            billNumber: params.billNumber,
            amount: params.amount,
            bit61: params.bit61,
            reff: params.reff,
            timeStamp: params.timeStamp
        }

        logger.info('Requesting to service', {url: config.h2h_out.partner, params: _params});

        soapClient.apih2h.apih2hPort.billpayment({ inputCheck: _params }, function(err, result) {
            logger.verbose(
                'Got response',
                {
                    lastEndpoint: soapClient.lastEndpoint,
                    lastRequest: soapClient.lastRequest,
                    lastMessage: soapClient.lastMessage,
                    lastResponse: soapClient.lastResponse,
                    lastElapsedTime: soapClient.lastElapsedTime,
                }
            );

            if (err) {
                var errorMessage = 'Error requesting service: ' + err;

                logger.warn(errorMessage, {err: err});
                callbackReportWrapper(task.requestId, '68', errorMessage);
                pushResponseToMongoDb(task, {supplier: config.globals.gateway_name, raw: soapClient.lastResponse}, '68');

                return;
            }

            topupResponseHandler(task, result, balance, soapClient.lastResponse);
        }, , {timeout: 120000, time: true});
    });
}

function topupResponseHandler(task, response, balance, rawResponse) {
    var st24rc = '68';
    var st24message = response.outputParameter.resultDesc.$value;

    var resultCode = parseInt(response.outputParameter.resultCode.$value);
    var bit39 = parseInt(response.outputParameter.bit39.$value);

    var sn = '';

    if ( resultCode == 1 ) {
        // product disabled
        st24rc = '13';
    }
    else if ( resultCode == 2 ) {
        // prodcode disable
        st24rc = '13';
    }
    else if ( resultCode == 3 ) {
        // duplicate reff
        st24rc = '55';
    }
    else if ( resultCode == 4 ) {
        // not enough balance
        st24rc = '40';
    }
    else if ( resultCode == 5 ) {
        // username blocked
        st24rc = '40';
    }
    else if ( resultCode == 6 ) {
        // not exists username
        st24rc = '40';
    }
    else if ( resultCode == 11 ) {
        // invalid request
        st24rc = '40'
    }
    else if ( resultCode == 12 ) {
        // no route to host
        st24rc = '40'
    }
    else if ( resultCode == 13 ) {
        // invalid signature
        st24rc = '40'
    }
    else if ( bit39 == 6 ) {
        st24rc = '40';
        st24message = 'Error Transaksi ditolak karena terjadi error di H2H dengan response code diluar daftar ini. Silahkan hubungi H2H';
    }
    else if ( bit39 == 12 ) {
        st24rc = '40';
        st24message = 'Invalid Transaction Transaksi ditolak karena flow transaksi tidak valid';
    }
    else if ( bit39 == 13 ) {
        st24rc = '13';
        st24message = 'Invalid voucher nominal';
    }
    else if ( bit39 == 14 ) {
        st24rc = '14';
        st24message = 'MSISDN tidak ditemukan';
    }
    else if ( bit39 == 30 ) {
        st24rc = '40';
        st24message = 'Format Error';
    }
    else if ( bit39 == 31 ) {
        st24rc = '40';
        st24message = 'Kode bank tidak terdaftar';
    }
    else if ( bit39 == 63 ) {
        st24rc = '40';
        st24message = 'Reversal denied';
    }
    else if ( bit39 == 68 ) {
        st24rc = '68';
        st24message = 'Transaksi sedang dalam proses';
    }
    else if ( bit39 == 69 ) {
        st24rc = '68';
        st24message = 'Respon Ok lebih dari 24 detik';
    }
    else if ( bit39 == 70 ) {
        st24rc = '13';
        st24message = 'Voucher out of stock';
    }
    else if ( bit39 == 79 ) {
        st24rc = '14';
        st24message = 'Phone number is blocked by Telkomsel';
    }
    else if ( bit39 == 81 ) {
        st24rc = '14';
        st24message = 'Phone number is expired';
    }
    else if ( bit39 == 89 ) {
        st24rc = '40';
        st24message = 'Link to billing provider is down';
    }
    else if ( bit39 == 91 ) {
        st24rc = '40';
        st24message = 'Database problem';
    }
    else if ( bit39 == 92 ) {
        st24rc = '40';
        st24message = 'Unable to route transaction';
    }
    else if ( bit39 == 94 ) {
        st24rc = '40';
        st24message = 'Duplicate reversal request';
    }
    else if ( resultCode == 0 && bit39 == 0) {

        try {
            sn = response.outputParameter.bit61.$value.substring(43);
        }
        catch(e) {
            sn = '';
        }

        st24message = 'SN=' + sn + '; ' + st24message;
        st24rc = '00';
    }

    var message =
        response.outputParameter.resultCode.$value
        + " " + response.outputParameter.resultDesc.$value
        + "; BIT39: " + response.outputParameter.bit39.$value
        ;

    if (response.outputParameter.resultDesc.$value != st24message) {
        var message = message + " " + st24message;
    }

    message = message + ' -- Prev Balance: ' + balance;

    var parsedResponse = {
        productCode: response.outputParameter.productCode.$value,
        terminal: response.outputParameter.terminal.$value,
        transactionType: response.outputParameter.transactionType.$value,
        billNumber: response.outputParameter.billNumber.$value,
        amount: response.outputParameter.amount.$value,
        bit61: response.outputParameter.bit61.$value,
        reff: response.outputParameter.reff.$value,
        timeStamp: response.outputParameter.timeStamp.$value,
        resultCode: response.outputParameter.resultCode.$value,
        resultDesc: response.outputParameter.resultDesc.$value,
        bit39: response.outputParameter.bit39.$value,
        prevBalance: balance,
        sn: sn,
        st24message: st24message,
    }

    var combinedMessage = '';

    Object.keys(parsedResponse).forEach(function(key,index) {
        combinedMessage += key + ': ' + parsedResponse[key] + '; '
    });
    combinedMessage = combinedMessage.trim();

    parsedResponse.MESSAGE = combinedMessage;

    logger.info('Got result: ' + message, {response: response});
    callbackReportWrapper(task.requestId, st24rc, st24message + ' -- Prev Balance: ' + balance);
    pushResponseToMongoDb(task, {supplier: config.globals.gateway_name, raw: rawResponse, parsed: parsedResponse}, st24rc);
}

function createSignature(params, password) {
    var passwordHash = crypto.createHash('sha256').update(password).digest().toString('hex');
    var plain =
        params.userName
        + passwordHash
        + params.productCode
        + params.terminal
        + params.transactionType
        + params.billNumber
        + params.amount
        + params.reff
        + params.timeStamp;

    return crypto.createHash('sha1').update(plain).digest().toString('hex');
}

function createSignatureForSaldoCheck(params, password) {
    var passwordHash = crypto.createHash('sha256').update(password).digest().toString('hex');
    var plain =
        params.userName
        + passwordHash
        + params.productCode
        + params.terminal
        + params.transactionType
        + params.reff
        + params.timeStamp;

    return crypto.createHash('sha1').update(plain).digest().toString('hex');
}

function createBillNumber(destination) {
    return ("0000000000000" + destination).slice(-13);
}

exports.start = start;
exports.topupRequest = topupRequest;
exports.createSignature = createSignature;
exports.createBillNumber = createBillNumber;