import Keycloak from "keycloak-js";
import { reactive } from "vue";

export const auth = reactive({
  ready: false,
  enabled: false,
  authenticated: false,
  username: "",
  email: "",
  emailVerified: null,
  groups: [],
  loginUrl: "",
  resetUrl: "",
  accountUrl: "",
  accountPasswordUrl: "",
  token: "",
});

let keycloak = null;
let initPromise = null;

/**
 * Build a Keycloak client for the current environment.
 *
 * WHY: tests need to inject a predictable client without changing the runtime
 * behavior for the browser.
 */
export function createKeycloak(config) {
  const factory = globalThis.__ATLAS_KEYCLOAK_FACTORY__;
  if (typeof factory === "function") return factory(config);
  const ctor = globalThis.__ATLAS_KEYCLOAK_CONSTRUCTOR__;
  if (typeof ctor === "function") return new ctor(config);
  return new Keycloak(config);
}

/**
 * Normalize Keycloak groups into the format the UI expects.
 *
 * @param {unknown} groups - Raw group list from the access token.
 * @returns {string[]} A cleaned list of group names without leading slashes.
 */
export function normalizeGroups(groups) {
  if (!Array.isArray(groups)) return [];
  return groups
    .filter((g) => typeof g === "string")
    .map((g) => g.replace(/^\//, ""))
    .filter(Boolean);
}

/**
 * Refresh the reactive auth state from the current Keycloak token.
 *
 * WHY: the UI reads from a shared reactive object, so a token refresh needs to
 * update all dependent fields in one place.
 */
function updateFromToken() {
  const parsed = keycloak?.tokenParsed || {};
  auth.authenticated = Boolean(keycloak?.authenticated);
  auth.token = keycloak?.token || "";
  auth.username = parsed.preferred_username || "";
  auth.email = parsed.email || "";
  auth.emailVerified = typeof parsed.email_verified === "boolean" ? parsed.email_verified : null;
  auth.groups = normalizeGroups(parsed.groups);
}

/**
 * Initialize Keycloak session probing and populate the reactive auth state.
 *
 * @returns {Promise<void>} A singleton promise so callers can await startup.
 */
export async function initAuth() {
  if (initPromise) return initPromise;

  initPromise = (async () => {
    try {
      const resp = await fetch("/api/auth/config", { headers: { Accept: "application/json" } });
      if (!resp.ok) throw new Error(`auth config ${resp.status}`);
      const cfg = await resp.json();
      auth.enabled = Boolean(cfg.enabled);
      auth.loginUrl = cfg.login_url || "";
      auth.resetUrl = cfg.reset_url || "";
      auth.accountUrl = cfg.account_url || "";
      auth.accountPasswordUrl = cfg.account_password_url || "";

      if (!auth.enabled) return;

      keycloak = createKeycloak({
        url: cfg.url,
        realm: cfg.realm,
        clientId: cfg.client_id,
      });

      const authenticated = await keycloak.init({
        onLoad: "check-sso",
        pkceMethod: "S256",
        silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
        checkLoginIframe: true,
        scope: "openid profile email groups",
      });

      auth.authenticated = authenticated;
      updateFromToken();

      keycloak.onAuthSuccess = () => updateFromToken();
      keycloak.onAuthLogout = () => updateFromToken();
      keycloak.onAuthRefreshSuccess = () => updateFromToken();
      keycloak.onTokenExpired = () => {
        keycloak
          .updateToken(30)
          .then(() => updateFromToken())
          .catch(() => updateFromToken());
      };

      window.setInterval(() => {
        if (!keycloak?.authenticated) return;
        keycloak.updateToken(60).then(updateFromToken).catch(() => {});
      }, 30_000);
    } catch {
      auth.enabled = false;
    } finally {
      auth.ready = true;
    }
  })();

  return initPromise;
}

/**
 * Open the Keycloak login flow and preserve the current location as the return
 * target.
 */
export async function login(
  redirectPath = window.location.pathname + window.location.search + window.location.hash,
  loginHint = "",
) {
  if (!keycloak) {
    if (auth.loginUrl) window.location.assign(auth.loginUrl);
    return;
  }
  const redirectUri = new URL(redirectPath, window.location.origin).toString();
  const options = { redirectUri };
  if (typeof loginHint === "string" && loginHint.trim()) {
    options.loginHint = loginHint.trim();
  }
  await keycloak.login(options);
}

/**
 * Log the current user out of Keycloak and return them to the portal root.
 */
export async function logout() {
  if (!keycloak) return;
  await keycloak.logout({ redirectUri: window.location.origin });
}

/**
 * Perform a fetch with the current bearer token attached when available.
 *
 * @param {string} url - Target URL.
 * @param {RequestInit} options - Standard fetch options.
 * @returns {Promise<Response>} The browser fetch response.
 */
export async function authFetch(url, options = {}) {
  const headers = new Headers(options.headers || {});
  if (keycloak?.authenticated) {
    try {
      await keycloak.updateToken(30);
      updateFromToken();
    } catch {
      // ignore refresh failures; the API will return 401 and the UI can prompt for login
    }
  }
  if (auth.token) headers.set("Authorization", `Bearer ${auth.token}`);
  return fetch(url, { ...options, headers });
}
