//external imports
import * as express from 'express';
import * as https from 'https';
import * as WebSocket from 'ws';
import * as fs from 'fs';
import * as jwt from 'jsonwebtoken';
import * as url from 'url';

//internal imports
var routes = require('./routes');
var app = require('./config/app');
var logger = require('./logger');
import ClientManager from './clientManager';
import ChannelManager from './channelManager';
import ClientBase from './clients/clientBase';
import MHSClient from './clients/sites/mhsClient';

var privateKey  = fs.readFileSync('certs/key.pem', 'utf8');
var certificate = fs.readFileSync('certs/cert.pem', 'utf8');
var options = {key: privateKey, cert: certificate, hostname: app.hostname};

const application = express();
const server = https.createServer(options, application);
const wss = new WebSocket.Server({ noServer: true, maxPayload:250000, host: app.hostname });

application.use(express.json());
application.use('', routes);

let clientManager = new ClientManager();
let channelManager = new ChannelManager();

function startServer() {
  wss.on('connection', (ws: WebSocket, request: object, args: string) => {
    var data = JSON.parse(args).data
    logger.accessLog.info(`Client Connected: ${data.user_id}`);

    if (!channelManager.channelExists(data.channel)) {
      channelManager.createChannel(data);
    }

    if (clientManager.clientExists(data.user_id)) {
      var client: ClientBase|MHSClient|null = clientManager.getClient(data.user_id);

      if (client != null) {
        client.replaceWebSocket(ws);
      }
    } else {
      var client: ClientBase|MHSClient|null = clientManager.addClient(data, ws);
    }

    if (client != null) {
      channelManager.addClientToChannel(client, data.channel);
    }

    ws.send('Hi there, welcome to braid, Measures Web Socket server.\nConnecting all our services!');
  });

  server.on('upgrade', async function upgrade(request, socket, head) {
    let args: {};

    try {
      args = await verifyConnection()
    } catch (e) {
      socket.destroy();
      logger.accessLog.info('Connection Terminated');
      return;
    }

    function verifyConnection() {
      return new Promise((resolve, reject) => {
        var data = url.parse(request.url, true).query;
        var token = data.token || (app.environment == 'development' ? app.devToken : '');
        var accepted = true;
        var result: string;

        jwt.verify(token, app.secret, app.signOptions, function(err, decoded) {
          if (err) {
            console.log(err);
            accepted = false;
          }

          result = JSON.stringify(decoded)
          accepted ? resolve(result) : reject('rejected');
        });
      });
    }

    wss.handleUpgrade(request, socket, head, function done(ws) {
      wss.emit('connection', ws, request, args);
    });
  });

  server.listen(app.port, () => {
    console.log(`Braid v${app.version} is running!\n`);
    logger.accessLog.info(`Braid v${app.version} is running!\n`);
  });
}

startServer();

module.exports = {
  clientManager: clientManager,
  channelManager: channelManager
}
