const emisApiCert = require('./emisApiCert');
const defaultLogger = require('./defaultLogger');
const connectionManager = require('amqp-connection-manager');
const fs = require('fs');
const NamedTimers = require('./namedTimers');

module.exports = function(serviceEnvName, settings) {

    const throttleTimer = 'throttle';
    const executeTimer = 'execute';
    let elapsedTimeWarning = settings.getOrDefault('elapsedTimeWarningSeconds');
    if (elapsedTimeWarning)
        elapsedTimeWarning = elapsedTimeWarning * 1000;

    const mockClinicalLibrary = settings.getOrDefault('clinicalSystemQueue.mockClinicalLibrary', false);
    const clinicalLibrary = (mockClinicalLibrary)
        ? require('@blackpear/pyrusmocks').pyrusConnectClinical
        : require('./clinicalLibrary').clinicalLibrary;

    const loggingOptions = {};
    loggingOptions.level = settings.getOrDefault('logging.level', 'info');
    loggingOptions.name = 'clinicalLibraryResponder';
    loggingOptions.enableConsoleLogging = !(process.env[serviceEnvName] === 'AUTO');
    const log = defaultLogger(loggingOptions);

    const apiCertOptions = settings.getOrDefault('clinicalSystemApi');
    let serverAddress = settings.getOrDefault('clinicalSystemQueue.address', 'amqp://localhost');
    if ((serverAddress) && (!Array.isArray(serverAddress)))
        serverAddress = [serverAddress];

    const requestQueue = settings.getOrDefault('clinicalSystemQueue.requestQueue', 'clinical_request_queue');
    const secondsPerMessage = settings.getOrDefault('clinicalSystemQueue.minSecondsPerMessage', 2);
    const sslOpts = settings.getOrDefault('clinicalSystemQueue.ssl');
    let ssl;
    let lastMessageTime = new Date(0);
    let channelWrapper;

    function notifyConnection(info) {
        if (info)
            info = info.url;

        log.info('Connected to RabbitMQ broker:' + info);
    }

    function notifyDisconnection(info) {
        if ((info) && (info.err))
            info = info.err.message;

        log.info('Disconnected from RabbitMQ broker: ' + info);
    }

    function connectionError(err) {
        log.error('ConnectionManager error (%s): %s', serverAddress, err);
        throw (err);
    }

    function channelError(err) {
        log.error('Error creating ChannelWrapper: %', err);
        throw (err);
    }

    function notifyStart() {
        const s = (mockClinicalLibrary) ? ' (using mock)' : '';

        log.info('Responder is listening for clinical data requests%s...', s);
    }

    function setAdapterConfig(){
        const configArr = [];
        configArr.push('adapterCache=' + (!settings.getOrDefault('disableAdapterCache', false)).toString());
        const adapterExpiry = settings.getOrDefault('clinicalSystemAdapterExpiryMinutes');
        if (adapterExpiry)
            configArr.push('adapterExpiry=' + adapterExpiry.toString());

        return clinicalLibrary.config(configArr.join('&'));
    }

    function callClinicalSystem(request) {
        try {
            if (!mockClinicalLibrary)
                emisApiCert.createApiCertFile(request.connectionString, apiCertOptions);

            switch (request.method.toLowerCase()) {
                case 'get':
                    return clinicalLibrary.get(request.url, request.connectionString, request.headers);
                case 'post':
                    return clinicalLibrary.post(request.url, request.data, request.connectionString, request.headers);
                default:
                    return Promise.reject(new Error('Unknown request method when calling clinical system'));
            }
        }
        catch (error) {
            return Promise.reject(error);
        }
    }

    function handleMessage(msg) {
        const timers = new NamedTimers(log, elapsedTimeWarning);

        function sleep(ms) {
            return new Promise((resolve) => {
                setTimeout(resolve, ms);
            });
        }

        function throttleRequests(intervalSeconds) {
            const thisMessageTime = new Date();
            let msDelay = (intervalSeconds * 1000 - (thisMessageTime - lastMessageTime));
            if (msDelay < 0) {
                msDelay = 0;
            }

            lastMessageTime = thisMessageTime;
            timers.startNamedTimer(throttleTimer);

            return sleep(msDelay)
                .then(function () {
                    timers.stopNamedTimer(throttleTimer);
                    timers.startNamedTimer(executeTimer);
                    return msg.content.toString();
                });
        }

        function parseRequest(requestString) {
            const request = JSON.parse(requestString);
            timers.setScope('[' + request.method + ']' + request.url);
            return request;
        }

        function sendGoodResponse(data) {
            sendThisResponse(
                data,
                {correlationId: msg.properties.correlationId});
        }

        function sendErrorResponse(error) {
            log.error('Responded with %s', error);
            if (error['StackTrace'])
                log.debug({debugStack: error['StackTrace']}, error.name);
            if (error['InnerException'])
                log.debug({debugInnerException: error['InnerException']}, error.name);
            sendThisResponse(
                error.message,
                {correlationId: msg.properties.correlationId, type: 'error'});
        }

        function sendThisResponse(responseStr, options) {
            timers.stopNamedTimer(executeTimer);
            channelWrapper.ack(msg);
            if (responseStr == null)
                responseStr = '';
            channelWrapper.sendToQueue(msg.properties.replyTo, responseStr, options);
            timers.logElapsed();
        }

        try {
            throttleRequests(secondsPerMessage)
                .then(parseRequest)
                .then(callClinicalSystem)
                .then(sendGoodResponse)
                .catch(sendErrorResponse);
        }
        catch (error) {
            sendErrorResponse(error);
        }
    }

    function createAutoDeleteQueue(ch) {
        return ch.assertQueue(requestQueue, {durable: false, autoDelete: true})
            .then(function () {
                return ch;
            });
    }

    function consumeQueue(ch) {
        ch.prefetch(1);
        return ch.consume(requestQueue, handleMessage);
    }

    function channelSetup(ch) {
        return createAutoDeleteQueue(ch)
            .then(consumeQueue)
            .then(setAdapterConfig)
    }

    function getCertificate(filename){

        if (filename)
            return fs.readFileSync(filename);
    }

    if (sslOpts){
        ssl = {
            cert: getCertificate(sslOpts['certPath']),
            key: getCertificate(sslOpts['keyPath']),
            ca: [getCertificate(sslOpts['caPath'])],
            passphrase: sslOpts['secret']
        };
    }

    try {
        const conn = connectionManager.connect(serverAddress, {connectionOptions: ssl});
        process.once('SIGINT', function () {
            log.debug('SIGINT received');
            return conn.close().then(() => log.debug('Connection closed'));
        });
        process.once('SIGTERM', function () {
            log.debug('SIGTERM received');
            return conn.close().then(() => log.debug('Connection closed'));
        });
        process.once('close', function () {
            log.debug('CLOSE received');
            return conn.close().then(() => log.debug('Connection closed'));
        });
        process.once('exit', function () {
            log.debug('EXIT received');
            return conn.close().then(() => log.debug('Connection closed'));
        });
        conn.on('connect', notifyConnection);
        conn.on('disconnect', notifyDisconnection);
        conn.on('connectFailed', connectionError);

        try {
            channelWrapper = conn.createChannel({json: true, setup: channelSetup});
            channelWrapper.on('error', log.error);
            notifyStart();
        }
        catch (err) {
            channelError(err);
        }

    } catch (err) {
        connectionError(err);
    }
};
