// ============ AcademIA8 · Consola — autenticación Cognito Hosted UI + PKCE ============
// App client público (sin secret) → OAuth authorization_code + PKCE.
// Flujo: sin token → redirect a la Hosted UI con code_challenge; el callback
// (?code=) se intercambia por tokens en el token endpoint con el code_verifier;
// se guardan los tokens; el ID token da name/email; logout redirige al endpoint
// de logout de Cognito.
//
// redirect_uri/logout_uri se calculan de `window.location.origin` en vez de
// leer `cfg.redirectUri`/`cfg.logoutUri` (valor fijo de config.json, siempre
// el dominio de CloudFront) -- así el login se queda en el mismo dominio por
// el que entró el usuario (CloudFront o admin.academia8.com), en vez de
// autenticar y rebotar siempre al de CloudFront. Requiere que el dominio esté
// registrado como callback/logout URL en el UserPoolClient (infra/stacks/
// academia8_stack.py, `console_callback_urls`/`console_logout_urls`) -- si no
// lo está, Cognito rechaza el redirect con `redirect_mismatch`, por diseño.
const A8Auth = (() => {
  const CFG = () => window.A8_CONFIG || {};
  const LS_TOKENS = "a8_tokens";
  const SS_VERIFIER = "a8_pkce_verifier";
  const originUri = () => window.location.origin + "/";

  // ---- PKCE helpers ----
  function b64url(bytes) {
    let s = btoa(String.fromCharCode(...new Uint8Array(bytes)));
    return s.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
  }
  function randomVerifier() {
    const a = new Uint8Array(64);
    crypto.getRandomValues(a);
    return b64url(a.buffer);
  }
  async function challenge(verifier) {
    const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
    return b64url(digest);
  }

  // ---- token storage ----
  function saveTokens(t) {
    t.obtained_at = Math.floor(Date.now() / 1000);
    localStorage.setItem(LS_TOKENS, JSON.stringify(t));
  }
  function getTokens() {
    try { return JSON.parse(localStorage.getItem(LS_TOKENS)); } catch (e) { return null; }
  }
  function clearTokens() { localStorage.removeItem(LS_TOKENS); }

  function isExpired(t) {
    if (!t || !t.expires_in) return true;
    const now = Math.floor(Date.now() / 1000);
    return now >= (t.obtained_at + t.expires_in - 30); // margen de 30s
  }

  function decodeJwt(jwt) {
    try {
      const payload = jwt.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
      return JSON.parse(decodeURIComponent(escape(atob(payload))));
    } catch (e) { return {}; }
  }

  // ---- redirect a la Hosted UI ----
  async function login() {
    const cfg = CFG();
    const verifier = randomVerifier();
    sessionStorage.setItem(SS_VERIFIER, verifier);
    const url = new URL(cfg.cognitoDomain + "/oauth2/authorize");
    url.searchParams.set("response_type", "code");
    url.searchParams.set("client_id", cfg.clientId);
    url.searchParams.set("redirect_uri", originUri());
    url.searchParams.set("scope", cfg.scopes || "openid profile");
    url.searchParams.set("code_challenge_method", "S256");
    url.searchParams.set("code_challenge", await challenge(verifier));
    window.location.assign(url.toString());
  }

  // ---- intercambio del code por tokens ----
  async function exchangeCode(code) {
    const cfg = CFG();
    const verifier = sessionStorage.getItem(SS_VERIFIER) || "";
    const body = new URLSearchParams({
      grant_type: "authorization_code",
      client_id: cfg.clientId,
      code,
      redirect_uri: originUri(),
      code_verifier: verifier,
    });
    const resp = await fetch(cfg.cognitoDomain + "/oauth2/token", {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: body.toString(),
    });
    if (!resp.ok) throw new Error("token exchange failed: " + resp.status);
    const tokens = await resp.json();
    saveTokens(tokens);
    sessionStorage.removeItem(SS_VERIFIER);
  }

  // ---- API pública ----
  // Garantiza sesión. Si volvemos del callback con ?code=, lo intercambia y limpia
  // la URL. Si no hay token válido, redirige a login (no resuelve). Devuelve true
  // solo si hay sesión utilizable.
  async function ensureAuthenticated() {
    const params = new URLSearchParams(window.location.search);
    const code = params.get("code");
    if (code) {
      await exchangeCode(code);
      // limpiar el ?code= de la URL
      window.history.replaceState({}, document.title, window.location.pathname);
      return true;
    }
    const t = getTokens();
    if (t && !isExpired(t)) return true;
    clearTokens();
    await login();
    return false; // redirigiendo
  }

  function authHeader() {
    const t = getTokens();
    // El authorizer de API Gateway valida contra el app client (audience) → ID token.
    return t ? { Authorization: "Bearer " + (t.id_token || t.access_token) } : {};
  }

  function user() {
    const t = getTokens();
    if (!t) return { name: "", email: "", initials: "" };
    const claims = decodeJwt(t.id_token || "");
    const name = claims.name || claims.email || "Autor";
    const initials = name.split(/\s+/).map((w) => w[0]).slice(0, 2).join("").toUpperCase();
    return { name, email: claims.email || "", initials: initials || "A8" };
  }

  function logout() {
    const cfg = CFG();
    clearTokens();
    const url = new URL(cfg.cognitoDomain + "/logout");
    url.searchParams.set("client_id", cfg.clientId);
    url.searchParams.set("logout_uri", originUri());
    window.location.assign(url.toString());
  }

  return { ensureAuthenticated, authHeader, user, logout };
})();

window.A8Auth = A8Auth;
