var xmlrpc = require('xmlrpc');
var url = require('url');
var math = require('mathjs');
var winston = require('winston');
var redis = require('redis');
var resendDelay = require('sate24/resend-delay.js');
var LRU = require('lru-cache');

var aaa;
var logger;
var config;
var _callbackReport;
var redisClient;

var taskHistory = LRU({max: 500, maxAge: 1000 * 3600 * 2});

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

function callbackReport(requestId, responseCode, message, dontResendDelay, raw) {
    var responseToSave = {
        parsed: {
            MESSAGE: message,
        }
    };

    if (raw) {
        responseToSave.raw = raw;
    }

    if (responseCode != '68' || dontResendDelay) {
        resendDelay.cancel(requestId);
    } else {
        getTaskFromHistory(requestId, function(err, archivedTask) {
            if (archivedTask) {
                logger.verbose('DEBUG', {archivedTask: archivedTask});
                resendDelay.register(archivedTask);
            }
        });
    }

    _callbackReport(requestId, responseCode, message, null, responseToSave);
}

function createRedisClient(host, port) {
    if (!host && !port) {
        logger.info('Not creating redis client because unspecified host or port');
        return;
    }

    try {
        redisClient = redis.createClient(port, host);
    } catch(err) {
        logger.warn("Error creating redis client to " + host + ':' + port);
    }
}

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

    getTaskFromHistory(task, function(err, archivedTask) {
        putTaskToHistory(task);

        if (archivedTask) {
            logger.info('Task has been executed before, going to checkStatus', {task: task, archivedTask: archivedTask});
            checkStatus(task);
        } else {
            _topupRequest(task);
        }
    });
}

function _topupRequest(task, pendingOnConnectError) {

    if (!aaa.isTodayTrx(task)) {
        callbackReport(task.requestId, '68', 'Terdeteksi transaksi beda hari, batal kirim ke supplier. Silahkan cek webreport', true);
        return;
    }

    var partnerUrl = url.parse(config.h2h_out.partner);
    var clientOptions = {
        host: partnerUrl.hostname,
        port: partnerUrl.port,
        path: partnerUrl.pathname
    };

    var client;
    if (partnerUrl.protocol == 'https:') {
        client = xmlrpc.createSecureClient(clientOptions);
    } else {
        client = xmlrpc.createClient(clientOptions);
    }

    var params = {
        MSISDN: config.h2h_out.userid,
        REQUESTID: task['requestId'],
        PIN: config.h2h_out.password,
        NOHP: task['destination'],
        NOM: task['remoteProduct']
    };

    var methodName = 'topUpRequest';
    logger.info('Preparing XMLRPC request', {methodname: methodName, params: params, partnerUrl: partnerUrl.href});

    client.methodCall(methodName, [ params ], function (error, value) {

        // Results of the method response
        if (error && !pendingOnConnectError && (error.code == 'ECONNREFUSED' || error.code == 'EHOSTUNREACH')) {

            logger.warn('XMLRPC Client Error on connecting', {requestId: task['requestId'], err: error});
            callbackReport(task['requestId'], '91', 'Gangguan koneksi ke suplier: ' + error);
            return;

        } else if (error) {

            logger.warn('XMLRPC Client Error', {requestId: task['requestId'], err: error});
            callbackReport(task['requestId'], '68', 'XMLRPC Client Error: ' + error);
            return;

        }

        logger.info('Got XMLRPC response from partner for', {response_method: methodName, response_message: value});

        if (value['RESPONSECODE'] == '94') {
            logger.info('Change RC 94 to 68');
            value['RESPONSECODE'] = '68';
        }

        if (value['RESPONSECODE'] == '00' && value['SN'] && value['SN'].trim()) {
            value['MESSAGE'] = 'SN=' + value['SN'].trim() + '; ' + value['MESSAGE'];
        }
        else if (value['RESPONSECODE'] == '00' && config.h2h_out.parse_sn == 'YES') {
            value['MESSAGE'] = 'SN=' + parseSN(value['MESSAGE']) + '; ' + value['MESSAGE'];
        }

        callbackReport(value['REQUESTID'], value['RESPONSECODE'], value['MESSAGE']);
    });
}

function createServer() {

    logger.info('Creating XML-RPC server on port ' + config.h2h_out.listen_port);
    var serverOptions = {
        port: config.h2h_out.listen_port
    };

    var server = xmlrpc.createServer(serverOptions);

    server.on('NotFound', function (method, params) {
        logger.warn('Unknown method recevied on XMLRPC server', {xmlrpc_method: method, xmlrpc_params: params});
    });

    server.on('topUpReport', function (err, params, callback) {

        logger.info('Got XMLRPC topUpReport request from partner', {xmlrpc_method: 'topUpReport', xmlrpc_params: params});

        var paramscount = params.length;
        for (var i = 0; i < paramscount; i++) {
            var value = params[i];

            if (value['RESPONSECODE'] == '94') {
                logger.info('Change RC 94 to 68');
                value['RESPONSECODE'] = '68';
            }

            if (value['RESPONSECODE'] == '00' && config.h2h_out.parse_sn == 'YES') {
                value['MESSAGE'] = 'SN=' + parseSN(value['MESSAGE']) + '; ' + value['MESSAGE'];
            }

            callbackReport(value['REQUESTID'], value['RESPONSECODE'], value['MESSAGE']);
        }

        callback(null, 'ACK REPORT OK');
    })

}

function getBalanceFromMessage(message, balance_regex) {
    if (!balance_regex) {
        if (config && config.globals && config.globals.balance_regex) {
            balance_regex = config.globals.balance_regex;
        }
    }

    if (!balance_regex) {
        return;
    }

    try {
        var re = new RegExp(balance_regex);
        var matches = message.match(re);

        var result = matches[1];
        result = result.replace(/\./g, '');
        result = result.replace(/,/g, '');


        return Number(result);
    }
    catch(err) {
        return;
    }
}

function updateBalance(message) {
    var balance = getBalanceFromMessage(message);
    if (balance) {
        logger.info('Balance: ' + balance);
        aaa.updateBalance(balance);
    }
}

function checkStatus(task) {

    if (Number(config.globals.topup_request_on_check_status)) {
        _topupRequest(task, true);
        return;
    }

    var partnerUrl = url.parse(config.h2h_out.partner);
    var clientOptions = {
        host: partnerUrl.hostname
        , port: partnerUrl.port
        , path: partnerUrl.pathname
    };
    logger.info('XMLRPC client options:');
    logger.info(clientOptions);

    var client;
    if (partnerUrl.protocol == 'https:') {
        client = xmlrpc.createSecureClient(clientOptions);
    } else {
        client = xmlrpc.createClient(clientOptions);
    }

    var methodName = 'topUpInquiry';

    var params = {
        REQUESTID: task['requestId'],
        MSISDN: config.h2h_out.userid,
        PIN: config.h2h_out.password,
        NOHP: task['destination']
    };

    logger.info('Requesting topUpInquiry', {params: params});

    client.methodCall(methodName, [ params ], function (error, value) {
        // Results of the method response
        if (error) {
            logger.warn('Error requesting topUpInquiry: ', {err: error, params: params});
            callbackReport(task.requestId, '68', 'Error requesting topUpInquiry: ' + error);
            return;
        }
        logger.info('Method response for \'' + methodName, {response: value});

        callbackReport(task.requestId, value['RESPONSECODE'], value['MESSAGE']);
    });
}

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)()
            ]
        });
    }

    createRedisClient(config.globals.redis_host, config.globals.redis_port);
    createServer();

    var resendDelayOptions = {
        config: config,
        topupRequest: checkStatus,
        logger: logger
    };

    if (Number(config.globals.topup_request_on_resend_delay)) {
        resendDelayOptions.topupRequest = topupRequest;
    }

    resendDelay.init(resendDelayOptions);
}

function parseSN(message, _config) {

    if (!_config) {
        _config = config;
    }

    var sn_regex = new RegExp(_config.h2h_out.sn_pattern);
    var sn_match = message.match(sn_regex);

    if (sn_match <= 0) {
        logger.info('SN Not found: ' + message);
        return '';
    }

    var match_index = 0;
    if (_config.h2h_out.sn_match_index) {
        match_index = Number(_config.h2h_out.sn_match_index);
    }

    var sn = sn_match[match_index];

    if (_config.h2h_out.sn_remove_whitespace) {
        sn = sn.replace(/\s/g, '');
    }

    var sn_remove_patterns = [];
    if (_config.h2h_out.sn_remove_patterns && _config.h2h_out.sn_remove_patterns_separator) {
        sn_remove_patterns = _config.h2h_out.sn_remove_patterns.split(_config.h2h_out.sn_remove_patterns_separator);
    }
    var count = sn_remove_patterns.length;

    for(var i = 0; i < count; i++) {

        //sn = sn.replace(sn_remove_patterns[i], '');

        var re = new RegExp(sn_remove_patterns[i], 'g');
        sn = sn.replace(re, '');
    }

    //sn = paddingSN(sn, _config);

    return sn.trim();
}

function getTaskKey(task, chipInfo) {
    var requestId;

    if (typeof task === 'string') {
        requestId = task;
    } else {
        try {
            requestId = task.requestId;
        }
        catch(e) {
            logger.warn('Something wrong', {task: task});
            console.trace('Cekidot');
            process.exit(1);
        }

    }

    if (!chipInfo && config && config.globals && config.globals.gateway_name) {
        chipInfo = config.globals.gateway_name;
    }

    return chipInfo + '.hitachi.rid:' + requestId;
}


function putTaskToHistory(task, cb) {
    if (Number(config.globals.no_dupe_check)) {
        if (cb) { cb(); }
        return;
    }
    var key = getTaskKey(task, config.globals.gateway_name);
    logger.verbose('Saving task to history LRU', {key: key, task: task});

    try {
        taskHistory.set(key, JSON.parse(JSON.stringify(task)));
    } catch (e) { }

    putTaskToRedis(task, cb);
}

function putTaskToRedis(task, cb) {
    if (!redisClient) {
        logger.verbose('Not saving to redis because of undefined redisClient')
        if (cb) { cb(); }
        return;
    }

    var key = getTaskKey(task, config.globals.gateway_name);
    logger.verbose('Saving task to redis', {key: key, task: task});

    redisClient.set(key, JSON.stringify(task), function() {
        redisClient.expire(key, 3600*24*30);
        if (cb) {
            cb();
        }
    });
}

function getTaskFromHistory(task, cb) {
    logger.verbose('Getting task from history', {task: task});
    var key = getTaskKey(task, config.globals.gateway_name);
    var archive = taskHistory.get(key);

    if (archive) {
        if (cb) { cb(null, archive); }
    }
    else {
        getTaskFromRedis(task, cb);
    }
}

function getTaskFromRedis(task, cb) {
    if (!redisClient) {
        if (cb) { cb(null, null); }
        return;
    }

    var key = getTaskKey(task, config.globals.gateway_name);
    redisClient.get(key, function(err, result) {
        if (err) {
            logger.warn('Error retrieving task from redis', {err: err});
            cb(err, null);
            return;
        }

        var task;
        try {
            task = JSON.parse(result);
        }
        catch(e) {
            logger.warn('Exception on parsing redis result as a json', {err: e});
        }

        cb(null, task);
    })
}

exports.start = start;
exports.topupRequest = topupRequest;
exports.getBalanceFromMessage = getBalanceFromMessage;
exports.checkStatus = checkStatus;