import * as express from "express";
import { createDecoder, createVerifier } from "fast-jwt";

import HttpError from "../interfaces/http-error";
import HttpStatusCode from "../interfaces/http-status";
import { JwtPayload } from "jsonwebtoken";

if (!process.env.PUBLIC_KEY && !process.env.REALM_URL) {
  throw new Error("Require public key or realm url.");
}
if (process.env.PUBLIC_KEY && process.env.REALM_URL && !process.env.PREFERRED_AUTH) {
  throw new Error("Preferred auth type must be specified if public key and realm url is provided.");
}
if (!process.env.MANAGEMENT_ROLE) {
  throw new Error("Management role env is required.");
}

const jwtVerify = createVerifier({
  key: async () => {
    return `-----BEGIN PUBLIC KEY-----\n${process.env.PUBLIC_KEY}\n-----END PUBLIC KEY-----`;
  },
});

const jwtDecode = createDecoder();

export async function expressAuthentication(
  request: express.Request,
  securityName: string,
  scopes?: string[],
) {
  if (process.env.NODE_ENV !== "production" && process.env.AUTH_BYPASS) {
    return { preferred_username: "bypassed" };
  }

  if (securityName !== "bearerAuth") throw new Error("Unknown authentication method.");

  const token = request.headers["authorization"]?.includes("Bearer ")
    ? request.headers["authorization"].split(" ")[1]
    : null;

  if (!token) throw new HttpError(HttpStatusCode.UNAUTHORIZED, "ไม่พบข้อมูลสำหรับยินยันตัวตน");

  let payload: JwtPayload = {};

  switch (process.env.PREFERRED_AUTH) {
    case "online":
      payload = await verifyOnline(token);
      break;
    case "offline":
      payload = await verifyOffline(token);
      break;
    default:
      if (process.env.REALM_URL) payload = await verifyOnline(token);
      if (process.env.PUBLIC_KEY) payload = await verifyOffline(token);
      break;
  }

  if (
    scopes &&
    scopes.length > 0 &&
    scopes
      .map((v) => (v === "management-role" ? process.env.MANAGEMENT_ROLE : v))
      .every((v) => !payload.role.includes(v))
  ) {
    throw new HttpError(HttpStatusCode.FORBIDDEN, "คุณไม่มีสิทธิในเข้าถึงข้อมูลนี้");
  }

  return payload;
}

async function verifyOffline(token: string) {
  const payload = await jwtVerify(token).catch((_) => null);
  if (!payload) throw new HttpError(HttpStatusCode.UNAUTHORIZED, "ไม่สามารถยืนยันตัวตนได้");
  return payload;
}

async function verifyOnline(token: string) {
  const res = await fetch(`${process.env.REALM_URL}/protocol/openid-connect/userinfo`, {
    headers: { authorization: `Bearer ${token}` },
  }).catch((e) => console.error(e));

  if (!res) throw new Error("ไม่สามารถเข้าถึงระบบยืนยันตัวตน");
  if (!res.ok) throw new HttpError(HttpStatusCode.UNAUTHORIZED, "ไม่สามารถยืนยันตัวตนได้");

  return await jwtDecode(token);
}
