/* jslint node: true */
'use strict';

//  ENiGMA½
const { MenuModule }    = require('./menu_module.js');
const { resetScreen }   = require('./ansi_term.js');
const Config            = require('./config.js').get;
const { Errors }        = require('./enig_error.js');
const Log               = require('./logger.js').log;
const {
    getEnigmaUserAgent
}                       = require('./misc_util.js');
const {
    trackDoorRunBegin,
    trackDoorRunEnd
}                       = require('./door_util.js');

//  deps
const async         = require('async');
const _             = require('lodash');
const joinPath      = require('path').join;
const crypto        = require('crypto');
const moment        = require('moment');
const https         = require('https');
const querystring   = require('querystring');
const fs            = require('fs-extra');
const SSHClient     = require('ssh2').Client;

/*
    Configuration block:


    someDoor: {
        module: exodus
        config: {
            //  defaults
            ticketHost: oddnetwork.org
            ticketPort: 1984
            ticketPath: /exodus
            rejectUnauthorized: false // set to true to allow untrusted CA's (dangerous!)
            sshHost: oddnetwork.org
            sshPort: 22
            sshUser: exodus
            sshKeyPem: /path/to/enigma-bbs/misc/exodus.id_rsa

            //  optional
            caPem: /path/to/cacerts.pem // see https://curl.haxx.se/docs/caextract.html

            //  required
            board: XXXX
            key: XXXX
            door: some_door
        }
    }
*/

exports.moduleInfo = {
    name    : 'Exodus',
    desc    : 'Exodus Door Server Access Module - https://oddnetwork.org/exodus/',
    author  : 'NuSkooler',
};

exports.getModule = class ExodusModule extends MenuModule {
    constructor(options) {
        super(options);

        this.config                     = options.menuConfig.config || {};
        this.config.ticketHost          = this.config.ticketHost || 'oddnetwork.org';
        this.config.ticketPort          = this.config.ticketPort || 1984,
        this.config.ticketPath          = this.config.ticketPath || '/exodus';
        this.config.rejectUnauthorized  = _.get(this.config, 'rejectUnauthorized', true);
        this.config.sshHost             = this.config.sshHost || this.config.ticketHost;
        this.config.sshPort             = this.config.sshPort || 22;
        this.config.sshUser             = this.config.sshUser || 'exodus_server';
        this.config.sshKeyPem           = this.config.sshKeyPem || joinPath(Config().paths.misc, 'exodus.id_rsa');
    }

    initSequence() {

        const self              = this;
        let clientTerminated    = false;

        async.waterfall(
            [
                function validateConfig(callback) {
                    //  very basic validation on optionals
                    async.each( [ 'board', 'key', 'door' ], (key, next) => {
                        return _.isString(self.config[key]) ? next(null) : next(Errors.MissingConfig(`Config requires "${key}"!`));
                    }, callback);
                },
                function loadCertAuthorities(callback) {
                    if(!_.isString(self.config.caPem)) {
                        return callback(null, null);
                    }

                    fs.readFile(self.config.caPem, (err, certAuthorities) => {
                        return callback(err, certAuthorities);
                    });
                },
                function getTicket(certAuthorities, callback) {
                    const now       = moment.utc().unix();
                    const sha256    = crypto.createHash('sha256').update(`${self.config.key}${now}`).digest('hex');
                    const token     = `${sha256}|${now}`;

                    const postData  = querystring.stringify({
                        token       : token,
                        board       : self.config.board,
                        user        : self.client.user.username,
                        door        : self.config.door,
                    });

                    const reqOptions = {
                        hostname            : self.config.ticketHost,
                        port                : self.config.ticketPort,
                        path                : self.config.ticketPath,
                        rejectUnauthorized  : self.config.rejectUnauthorized,
                        method              : 'POST',
                        headers             : {
                            'Content-Type'      : 'application/x-www-form-urlencoded',
                            'Content-Length'    : postData.length,
                            'User-Agent'        : getEnigmaUserAgent(),
                        }
                    };

                    if(certAuthorities) {
                        reqOptions.ca = certAuthorities;
                    }

                    let ticket = '';
                    const req = https.request(reqOptions, res => {
                        res.on('data', data => {
                            ticket += data;
                        });

                        res.on('end', () => {
                            if(ticket.length !== 36) {
                                return callback(Errors.Invalid(`Invalid Exodus ticket: ${ticket}`));
                            }

                            return callback(null, ticket);
                        });
                    });

                    req.on('error', err => {
                        return callback(Errors.General(`Exodus error: ${err.message}`));
                    });

                    req.write(postData);
                    req.end();
                },
                function loadPrivateKey(ticket, callback) {
                    fs.readFile(self.config.sshKeyPem, (err, privateKey) => {
                        return callback(err, ticket, privateKey);
                    });
                },
                function establishSecureConnection(ticket, privateKey, callback) {

                    let pipeRestored = false;
                    let pipedStream;
                    let doorTracking;

                    function restorePipe() {
                        if(pipedStream && !pipeRestored && !clientTerminated) {
                            self.client.term.output.unpipe(pipedStream);
                            self.client.term.output.resume();

                            if(doorTracking) {
                                trackDoorRunEnd(doorTracking);
                            }
                        }
                    }

                    self.client.term.write(resetScreen());
                    self.client.term.write('Connecting to Exodus server, please wait...\n');

                    const sshClient = new SSHClient();

                    const window = {
                        rows        : self.client.term.termHeight,
                        cols        : self.client.term.termWidth,
                        width       : 0,
                        height      : 0,
                        term        : 'vt100',  //  Want to pass |self.client.term.termClient| here, but we end up getting hung up on :(
                    };

                    const options = {
                        env : {
                            exodus : ticket,
                        },
                    };

                    sshClient.on('ready', () => {
                        self.client.once('end', () => {
                            self.client.log.info('Connection ended. Terminating Exodus connection');
                            clientTerminated = true;
                            return sshClient.end();
                        });

                        sshClient.shell(window, options, (err, stream) => {
                            doorTracking = trackDoorRunBegin(self.client, `exodus_${self.config.door}`);

                            pipedStream = stream;   //  :TODO: ewwwwwwwww hack
                            self.client.term.output.pipe(stream);

                            stream.on('data', d => {
                                return self.client.term.rawWrite(d);
                            });

                            stream.on('close', () => {
                                restorePipe();
                                return sshClient.end();
                            });

                            stream.on('error', err => {
                                Log.warn( { error : err.message }, 'Exodus SSH client stream error');
                            });
                        });
                    });

                    sshClient.on('close', () => {
                        restorePipe();
                        return callback(null);
                    });

                    sshClient.connect({
                        host        : self.config.sshHost,
                        port        : self.config.sshPort,
                        username    : self.config.sshUser,
                        privateKey  : privateKey,
                    });
                }
            ],
            err => {
                if(err) {
                    self.client.log.warn( { error : err.message }, 'Exodus error');
                }

                if(!clientTerminated) {
                    self.prevMenu();
                }
            }
        );
    }
};