import * as http from "http";
import open from "open";
import { getUser } from "./api";
import {
  getGlobalConfig,
  loginWebpage,
  railwayUrl,
  saveGlobalConfig,
  removeTokenFromGlobalConfig,
} from "./config";
import { User } from "./types";
import { searchStringToObject } from "./utils";

const openLoginWebpage = (port: number, code: string) => {
  open(loginWebpage(port, code));
};

export const saveToken = (token: string) => {
  const config = getGlobalConfig();
  saveGlobalConfig({
    ...config,
    token,
  });
};

export const logout = () => {
  removeTokenFromGlobalConfig();
};

export const getRandomCode = (): string =>
  Math.random().toString().split(".")[1];

export const getAndSaveAuth = (port: number, code: string): Promise<User> =>
  new Promise((resolve, reject) => {
    const wrapAsync = (fn: (...args: any) => Promise<void>) => async (
      ...args: any[]
    ) => {
      try {
        await fn(...args);
      } catch (e) {
        reject(e);
      }
    };

    const server = http.createServer(
      wrapAsync(async (req: http.IncomingMessage, res: http.ServerResponse) => {
        const sendJson = (status: number, body: any) => {
          res.setHeader("Content-Type", "application/json");
          res.statusCode = status;
          res.write(JSON.stringify(body));
          res.end();
        };

        res.setHeader("Access-Control-Allow-Origin", railwayUrl);

        if (req.method === "GET") {
          const query = searchStringToObject(req.url || "");
          const { token, code: returnedCode } = query;

          if (token == null) {
            sendJson(400, { error: "Invalid token" });
            throw new Error("Invalid token");
          }

          // Ensure that these credentials were intended for this login request
          if (returnedCode !== code) {
            sendJson(400, { error: "Invalid code" });
            throw new Error("Invalid code");
          }

          sendJson(200, { status: "Ok" });
          server.close();
          saveToken(token);

          try {
            const user = await getUser();
            return resolve(user);
          } catch (e) {
            return reject("Failed to fetch user");
          }
        } else if (req.method === "OPTIONS") {
          res.setHeader(
            "Access-Control-Allow-Methods",
            "GET,HEAD,PUT,PATCH,POST,DELETE",
          );
          res.setHeader("Access-Control-Allow-Headers", "authorization");
          res.setHeader("Content-Length", 0);
          res.statusCode = 204;
          res.end();
        }
      }),
    );

    server.on("error", e => {
      if ((e as any).code === "EADDRINUSE") {
        console.log(`Port ${port} already in use`);
        reject();
      }
    });

    server.listen(port);

    openLoginWebpage(port, code);
  });
