import jwt from "jsonwebtoken";
import { JWT_AUD, JWT_PURPOSE } from "./constants.js";

const DEFAULT_ISSUER = "ehs-platform";

export class TokenClaimError extends Error {
  constructor(message, status = 401) {
    super(message);
    this.name = "TokenClaimError";
    this.status = status;
  }
}

function issuer() {
  return process.env.JWT_ISSUER || DEFAULT_ISSUER;
}

function loginExpiresIn() {
  return process.env.JWT_EXPIRES_IN || "7d";
}

function sessionExpiresIn() {
  return process.env.JWT_SESSION_EXPIRES_IN || loginExpiresIn();
}

function launchExpiresIn() {
  return process.env.JWT_LAUNCH_EXPIRES_IN || "60s";
}

export function userClaimsFromPayload(payload) {
  const userId =
    payload?.userId ?? payload?.user_id ?? payload?.id ?? payload?.sub;
  return {
    userId,
    email: payload?.email ?? payload?.email_id ?? "",
    employeeName:
      payload?.employeeName ?? payload?.employee_name ?? payload?.name ?? "",
    roles: Array.isArray(payload?.roles) ? payload.roles : [],
    roleIds: Array.isArray(payload?.roleIds)
      ? payload.roleIds
      : payload?.role_id != null
        ? [payload.role_id]
        : [],
  };
}

export function buildTokenBody(userClaims) {
  return {
    userId: userClaims.userId,
    email: userClaims.email,
    employeeName: userClaims.employeeName,
    roles: userClaims.roles,
    roleIds: userClaims.roleIds,
  };
}

export function signLoginToken(userClaims, secret) {
  return jwt.sign(
    {
      ...buildTokenBody(userClaims),
      aud: JWT_AUD.MASTER,
      purpose: JWT_PURPOSE.LOGIN,
    },
    secret,
    { expiresIn: loginExpiresIn(), issuer: issuer() }
  );
}

export function signLaunchToken(userClaims, aud, secret) {
  return jwt.sign(
    {
      ...buildTokenBody(userClaims),
      aud,
      purpose: JWT_PURPOSE.APP_LAUNCH,
    },
    secret,
    { expiresIn: launchExpiresIn(), issuer: issuer() }
  );
}

export function signSessionToken(userClaims, aud, secret) {
  return jwt.sign(
    {
      ...buildTokenBody(userClaims),
      aud,
      purpose: JWT_PURPOSE.APP_SESSION,
    },
    secret,
    { expiresIn: sessionExpiresIn(), issuer: issuer() }
  );
}

export function verifyToken(token, secret) {
  return jwt.verify(token, secret);
}

export function assertLoginToken(decoded) {
  if (
    decoded?.purpose === JWT_PURPOSE.LOGIN &&
    decoded?.aud === JWT_AUD.MASTER
  ) {
    return;
  }

  if (
    decoded?.purpose === JWT_PURPOSE.APP_LAUNCH ||
    decoded?.purpose === JWT_PURPOSE.APP_SESSION
  ) {
    throw new TokenClaimError("Login token required");
  }

  // Legacy HSE login JWT (issued before aud/purpose claims).
  const claims = userClaimsFromPayload(decoded);
  if (!decoded?.purpose && !decoded?.aud && claims.userId != null) {
    return;
  }

  if (decoded?.purpose !== JWT_PURPOSE.LOGIN) {
    throw new TokenClaimError("Login token required");
  }
  if (decoded?.aud !== JWT_AUD.MASTER) {
    throw new TokenClaimError("Invalid token audience for master API");
  }
}

export function assertLaunchToken(decoded, expectedAud) {
  if (decoded?.purpose !== JWT_PURPOSE.APP_LAUNCH) {
    throw new TokenClaimError("Launch token required");
  }
  if (decoded?.aud !== expectedAud) {
    throw new TokenClaimError("Invalid launch token audience");
  }
}

export function assertSessionToken(decoded, expectedAud) {
  if (decoded?.purpose !== JWT_PURPOSE.APP_SESSION) {
    throw new TokenClaimError("App session token required");
  }
  if (decoded?.aud !== expectedAud) {
    throw new TokenClaimError("Invalid token audience for this API");
  }
}

export function exchangeLaunchForSession(launchToken, expectedAud, secret) {
  const decoded = verifyToken(launchToken, secret);
  assertLaunchToken(decoded, expectedAud);
  const sessionToken = signSessionToken(userClaimsFromPayload(decoded), expectedAud, secret);
  return { decoded, sessionToken };
}
