#!/usr/bin/env node

// config should be imported at the top of this file.
import "./config";

import {Server, Socket} from "socket.io";
import http, {IncomingHttpHeaders} from "http";

import app from "../app";
import {register} from "../socket.io";
import {IO} from "../shared/io";
import sessionMiddleware from "../middlewares/session-middleware";
import {getLoggedInUserIdFromSocket} from "../socket.io/util";
import {startCronJobs} from "../cron_jobs";
import FileConstants from "../shared/file-constants";
import {initRedis} from "../redis/client";
import DbTaskStatusChangeListener from "../pg_notify_listeners/db-task-status-changed";

function normalizePort(val?: string) {
  const p = parseInt(val || "0", 10);
  if (isNaN(p)) return val; // named pipe
  if (p >= 0) return p; // port number
  return false;
}

const port = normalizePort(process.env.PORT);
app.set("port", port);

const server = http.createServer(app);

const io = new Server(server, {
  transports: ["websocket"],
  path: "/socket",
  cors: {
    origin: (process.env.SOCKET_IO_CORS || "*").split(",")
  },
  cookie: true
});

const wrap = (middleware: any) => (socket: any, next: any) => middleware(socket.request, {}, next);

io.use(wrap(sessionMiddleware));

io.use((socket, next) => {
  const userId = getLoggedInUserIdFromSocket(socket);
  if (userId)
    return next();
  return next(new Error("401 unauthorized"));
});

io.engine.on("initial_headers", (headers: IncomingHttpHeaders) => {
  headers["Strict-Transport-Security"] = "max-age=63072000; includeSubDomains";
  headers["X-Content-Type-Options"] = "nosniff";
  headers["X-Frame-Options"] = "Deny";
  headers["X-XSS-Protection"] = "1; mode=block";
});

io.on("connection", (socket: Socket) => {
  register(io, socket);
});

IO.setInstance(io);

function onError(error: any) {
  DbTaskStatusChangeListener.disconnect();

  if (error.syscall !== "listen") {
    throw error;
  }

  const bind = typeof port === "string"
    ? `Pipe ${port}`
    : `Port ${port}`;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case "EACCES":
      console.error(`${bind} requires elevated privileges`);
      process.exit(1);
      break;
    case "EADDRINUSE":
      console.error(`${bind} is already in use`);
      process.exit(1);
      break;
    default:
      throw error;
  }
}

function onListening() {
  const addr = server.address();
  if (!addr) return;

  const bind = typeof addr === "string"
    ? `pipe ${addr}`
    : `port ${addr.port}`;

  process.env.ENABLE_EMAIL_CRONJOBS === "true" && startCronJobs();
  // void initRedis();
  FileConstants.init();
  void DbTaskStatusChangeListener.connect();

  console.info(`Listening on ${bind}`);
}

function onClose() {
  DbTaskStatusChangeListener.disconnect();
}

server.on("error", onError);
server.on("close", onClose);
server.on("listening", onListening);

process.on("SIGINT", () => {
  server.close();
});

server.listen(port);
