import * as fs from "node:fs/promises";
import axios from "axios";
import provideConfig from "./provideConfig";
import { initLogger, logError } from "./logger";

// @ts-ignore
import reportErrorToRollbar from "./rollbar.mjs";
import { CREDENTIALS_DIR, CREDENTIALS_FILE } from "./credentials";
const oraP = import("ora");
const openP = import("open");

export default async () => {
  await initLogger("login");
  const breadcrumbs: string[] = [];
  const ora = (await oraP).default;
  const authenticationSpinner = ora("Waiting for code verification...").start();

  try {
    const open = (await openP).default;
    const config = await provideConfig();
    breadcrumbs.push("provideConfig");

    await resolveFiles();
    breadcrumbs.push("resolveFiles");

    const deviceCodePayload = {
      client_id: config.authClientId,
      audience: config.audienceUrl,
    };

    const deviceCodeResponse = await axios.post(
      `https://${config.authDomain}/oauth/device/code`,
      deviceCodePayload,
    );

    ora(
      "Confirm this code on your browser: " +
        deviceCodeResponse.data["user_code"],
    ).info();

    const tokenPayload = {
      grant_type: "urn:ietf:params:oauth:grant-type:device_code",
      device_code: deviceCodeResponse.data["device_code"],
      client_id: config.authClientId,
    };

    await open(deviceCodeResponse.data["verification_uri_complete"]);

    let isTerminated = false;
    const signalHandler = () => {
      isTerminated = true;
      authenticationSpinner.fail("Authentication process interrupted");
      process.exit(0);
    };
    process.on("SIGTERM", signalHandler);
    process.on("SIGINT", signalHandler);

    /**
     * This is a recommended way to poll, since it take some time for a user to enter a `user_code` in a browser.
     * deviceCodeResponse.data['interval'] is a recommended/calculated polling interval specified in seconds.
     */
    while (!isTerminated) {
      try {
        const tokenResponse = await axios.post(
          `https://${config.authDomain}/oauth/token`,
          tokenPayload,
        );
        await fs.writeFile(
          CREDENTIALS_FILE,
          JSON.stringify(tokenResponse.data),
        );
        authenticationSpinner.succeed(
          "You are successfully authenticated now!",
        );
        break;
      } catch (e: any) {
        if (e.response.data?.error !== "authorization_pending") {
          authenticationSpinner.fail(
            "Authentication failed. Please try again.",
          );
          process.exit(1);
        }

        await sleep(deviceCodeResponse.data["interval"] * 1000);
      }
    }

    // Clean up signal handlers
    process.off("SIGTERM", signalHandler);
    process.off("SIGINT", signalHandler);
  } catch (error: unknown) {
    authenticationSpinner.fail("Authentication failed. Please try again.");
    await logError({ command: "login", breadcrumbs, error });
    await reportErrorToRollbar(error);
    console.log(error);
    process.exit(1);
  }
};

export async function getToken() {
  try {
    const rawCredentials = await fs.readFile(CREDENTIALS_FILE, "utf-8");
    const credentials = JSON.parse(rawCredentials.toString());

    return credentials?.access_token ?? "";
  } catch (_e) {
    return "";
  }
}

function sleep(ms: number) {
  return new Promise((res) => setTimeout(res, ms));
}

export async function resolveFiles() {
  try {
    await fs.access(CREDENTIALS_DIR);
  } catch (_e) {
    await fs.mkdir(CREDENTIALS_DIR);
  }

  try {
    await fs.access(CREDENTIALS_FILE);
  } catch (e) {
    await fs.writeFile(CREDENTIALS_FILE, "");
  }
}
