import config from 'config';
import { Server } from 'socket.io';
import path from 'path';
import fastify from 'fastify';
import jsonwebtoken from 'jsonwebtoken';
import fastifyStatic from '@fastify/static';
import fastifyCors from '@fastify/cors';
import fastifySensible from '@fastify/sensible';
import fastifyHelmet from '@fastify/helmet';
import fastifyAutoload from '@fastify/autoload';
import { getLogger } from './utils';

declare module 'fastify' {
  // eslint-disable-next-line no-unused-vars
  interface FastifyRequest {
    websocketToken: string;
    multipart: Buffer;
  }
}

const httpPort = config.get('webserverPort') as number;
const wsPort = config.get('websocketPort') as number;
const defaultToken = config.get('websocketToken') as string;
const io = new Server(wsPort);

const server = fastify({ logger: false, bodyLimit: 20971520 });

server.register(fastifyStatic, {
  root: path.join(__dirname, '../public'),
});

server.register(fastifyCors, {});
server.register(fastifySensible);
server.register(fastifyHelmet, { contentSecurityPolicy: false });

server.register(fastifyAutoload, {
  dir: path.join(__dirname, 'routes'),
});

server.register(fastifyAutoload, {
  dir: path.join(__dirname, 'routes'),
  options: { prefix: '/viewer' },
});

server.decorateRequest('multipart', '');
server.addContentTypeParser('multipart/related', { parseAs: 'buffer' }, async (request, payload) => {
  request.multipart = payload;
});

const logger = getLogger();

global.connectedClients = {};

// log exceptions
process.on('uncaughtException', async (err) => {
  await logger.error('uncaught exception received:');
  await logger.error(err.stack);
});

//------------------------------------------------------------------

process.on('SIGINT', async () => {
  await logger.info('shutting down web server...');
  io.close();
  server.close().then(
    async () => {
      await logger.info('webserver shutdown successfully');
    },
    (err) => {
      logger.error('webserver shutdown failed', err);
    }
  );
  process.exit(1);
});

//------------------------------------------------------------------

// incoming websocket connections are registered here
io.on('connection', (socket) => {
  const origin = socket.conn.remoteAddress;
  logger.info(`websocket client connected from origin: ${origin}`);
  const { token } = socket.handshake.auth;
  logger.info('Added socket to clients', token);
  global.connectedClients[token] = socket;

  socket.on('disconnect', (reason) => {
    logger.info(`websocket client disconnected, origin: ${origin}, reason: ${reason}`);
    delete global.connectedClients[token];
  });
});

server.decorateRequest('websocketToken', '');

server.addHook('onRequest', async (request) => {
  const { headers } = request;
  const token = headers.authorization?.replace(/bearer /gi, '');
  if (token) {
    try {
      const secret = config.get('jwtPacsSecret') as string;
      const issuer = config.get('jwtPacsIssuer') as string;
      const { websocketToken } = jsonwebtoken.verify(token, secret, { issuer });
      logger.info(websocketToken, ' ', request.url, ' ', request.method);
      request.websocketToken = websocketToken || defaultToken;
    } catch (e) {
      request.websocketToken = defaultToken;
    }
  } else {
    request.websocketToken = defaultToken;
  }
});

//------------------------------------------------------------------

logger.info('starting...');

server.listen({ port: httpPort, host: '0.0.0.0' }, async (err, address) => {
  if (err) {
    await logger.error(err, address);
    process.exit(1);
  }
  logger.info(`web-server listening on port: ${httpPort}`);
  logger.info(`websocket-server listening on port: ${wsPort}`);
});

//------------------------------------------------------------------
