/**
 * The browser's half of `TERA_AUTH_MODE=sso`.
 *
 * In `password` mode the server issues an HttpOnly cookie and this module has
 * nothing to do — `credentials: "same-origin"` carries the session and no script
 * ever sees it, which is the better arrangement and the reason it is still the
 * default for a self-hoster.
 *
 * `sso` mode cannot work that way. The identity provider is a **different
 * origin** from this one, so the session it hands out is not a cookie this site
 * can read or be sent. What the browser gets instead is a bearer token, which
 * means it has to be stored somewhere a script can reach and attached by hand.
 * That is a real downgrade — an XSS bug on this page can now walk off with a
 * session — and it is accepted here for the same reason the rest of the fleet
 * accepts it: the alternative is a cross-origin cookie with `SameSite=None`,
 * which is worse, and the token is short-lived and revocable at the issuer.
 *
 * `sessionStorage`, not `localStorage`: the token dies with the tab. A shared
 * office kiosk is a plausible way to use this, and "signed in forever on a
 * machine somebody walked away from" is the failure this avoids.
 */

/** Namespaced so a self-hoster running something else on this origin is unaffected. */
const KEY = "tera.session.token";

/**
 * Build-time configuration, read the way `plan.ts` reads `DEV` and for the same
 * stated reason: Vite substitutes `import.meta.env` and Node leaves it
 * undefined, so reaching through it directly throws the moment this module is
 * imported outside a bundler. That mattered more than it looked. `access.ts`
 * imports `authFetch` from here, so one property access at module scope made
 * the file that decides what an anonymous visitor may see unreachable from a
 * plain test runner — which is most of why it had no tests.
 */
const ENV: Record<string, string | undefined> =
  (import.meta as unknown as { env?: Record<string, string | undefined> }).env ?? {};

/** Whether this build was given an identity provider to sign in against. */
export const IDENTITY_URL: string = ENV.VITE_IDENTITY_URL ?? "";
/** The provider's PUBLIC key. Publishable by design — it gates nothing on its own. */
export const IDENTITY_KEY: string = ENV.VITE_IDENTITY_ANON_KEY ?? "";
export const identityConfigured = IDENTITY_URL !== "" && IDENTITY_KEY !== "";

export function readToken(): string | null {
  try {
    const raw = sessionStorage.getItem(KEY);
    return raw === null || raw === "" ? null : raw;
  } catch {
    // Storage can throw outright in a partitioned or cookie-blocked context.
    // No token is a correct answer there; it just means signing in again.
    return null;
  }
}

export function writeToken(token: string): void {
  try {
    sessionStorage.setItem(KEY, token);
  } catch {
    // Nothing to do: the sign-in still succeeded at the issuer, and the caller
    // finds out on the next request that it did not stick.
  }
}

export function clearToken(): void {
  try {
    sessionStorage.removeItem(KEY);
  } catch {
    /* see writeToken */
  }
}

/**
 * `fetch` with the bearer token attached when there is one.
 *
 * `credentials: "same-origin"` is kept alongside it so a `password`-mode
 * deployment — where the cookie is the session and this module holds nothing —
 * goes on working through exactly the same call.
 */
export function authFetch(input: RequestInfo | URL, init: RequestInit = {}): Promise<Response> {
  const token = readToken();
  const headers = new Headers(init.headers);
  if (token !== null) headers.set("authorization", `Bearer ${token}`);
  return fetch(input, { ...init, credentials: "same-origin", headers });
}

/**
 * Exchange an email and password for an access token at the identity provider.
 *
 * A direct `fetch` rather than `@supabase/supabase-js`, which is a large
 * dependency for one documented REST call and would be this repo's only reason
 * to carry it. The same call is what the rest of the fleet's sites make.
 *
 * Returns the token, or `null` for every way it can fail — the caller shows one
 * message either way, because distinguishing "no such account" from "wrong
 * password" is an enumeration oracle and not a kindness.
 */
export async function signIn(email: string, password: string): Promise<string | null> {
  if (!identityConfigured) return null;
  try {
    const res = await fetch(`${IDENTITY_URL.replace(/\/+$/, "")}/auth/v1/token?grant_type=password`, {
      method: "POST",
      headers: { apikey: IDENTITY_KEY, "content-type": "application/json" },
      body: JSON.stringify({ email, password }),
    });
    if (!res.ok) return null;
    const body = (await res.json()) as { access_token?: unknown };
    return typeof body.access_token === "string" && body.access_token !== ""
      ? body.access_token
      : null;
  } catch {
    return null;
  }
}
