/* =====================================================================
   functions/_shared/oauth-handlers.js — gemeinsame Start-/Callback-Logik für alle OAuth-
   Anbieter (Google, PayPal), von den dünnen Routen unter functions/api/auth/<provider>/start.js
   bzw. callback.js aufgerufen. So gibt es die sicherheitskritische Logik (State-Prüfung, PKCE,
   Sitzungserstellung) nur EINMAL, nicht einmal pro Anbieter dupliziert. ===================================================================== */

import {
  erzeugeZufallswert,
  codeChallengeFuer,
  baueAutorisierungsUrl,
  istProviderKonfiguriert,
  tauscheCodeGegenToken,
  holeNutzerprofil,
} from "./oauth.js";
import { signCustomerSession, customerCookieHeader, CUSTOMER_SESSION_TAGE } from "./customer-auth.js";

const STATE_COOKIE = "vandiy_oauth_state";
const VERIFIER_COOKIE = "vandiy_oauth_verifier";
const LANG_COOKIE = "vandiy_oauth_lang";
const OAUTH_TEMP_MAXAGE = 600; // 10 Minuten — reicht locker für den Login-Vorgang, hält aber die
// Angriffsfläche klein, falls die Cookies je "hängen bleiben".
const ERLAUBTE_SPRACHEN = ["", "en", "ch", "fr"]; // "" = Deutsch (kein Präfix)

function getCookieRaw(request, name) {
  const header = request.headers.get("Cookie") || "";
  const m = header.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`));
  return m ? decodeURIComponent(m[1]) : null;
}

function langPrefix(lang) {
  return ERLAUBTE_SPRACHEN.includes(lang) ? lang : "";
}

/** GET /api/auth/:provider/start — leitet zum Anbieter weiter, nachdem State + PKCE-Verifier in
 *  kurzlebigen, signierten Cookies zwischengespeichert wurden. */
export async function starteOAuth(context, providerName) {
  const { request, env } = context;
  const url = new URL(request.url);
  const lang = langPrefix(url.searchParams.get("lang") || "");

  if (!istProviderKonfiguriert(env, providerName)) {
    return Response.redirect(`${url.origin}/${lang ? lang + "/" : ""}konto/?login_error=${providerName}_unconfigured`, 302);
  }

  const state = erzeugeZufallswert(24);
  const codeVerifier = erzeugeZufallswert(48);
  const codeChallenge = await codeChallengeFuer(codeVerifier);
  const redirectUri = `${url.origin}/api/auth/${providerName}/callback`;
  const authUrl = baueAutorisierungsUrl(env, providerName, { redirectUri, state, codeChallenge });

  const cookiePath = `/api/auth/${providerName}`;
  const headers = new Headers({ Location: authUrl });
  headers.append("Set-Cookie", `${STATE_COOKIE}=${state}; Path=${cookiePath}; Max-Age=${OAUTH_TEMP_MAXAGE}; HttpOnly; Secure; SameSite=Lax`);
  headers.append("Set-Cookie", `${VERIFIER_COOKIE}=${codeVerifier}; Path=${cookiePath}; Max-Age=${OAUTH_TEMP_MAXAGE}; HttpOnly; Secure; SameSite=Lax`);
  headers.append("Set-Cookie", `${LANG_COOKIE}=${lang}; Path=${cookiePath}; Max-Age=${OAUTH_TEMP_MAXAGE}; HttpOnly; Secure; SameSite=Lax`);
  return new Response(null, { status: 302, headers });
}

/** GET /api/auth/:provider/callback — prüft State (CSRF-Schutz), tauscht den Code gegen ein
 *  Zugangstoken, holt das echte Profil direkt vom Anbieter, legt bei Bedarf einen neuen
 *  Kunden-Datensatz an (oder aktualisiert den bestehenden) und setzt die echte Sitzung. */
export async function verarbeiteOAuthCallback(context, providerName) {
  const { request, env } = context;
  const url = new URL(request.url);
  const code = url.searchParams.get("code");
  const state = url.searchParams.get("state");
  const errorParam = url.searchParams.get("error");
  const lang = langPrefix(getCookieRaw(request, LANG_COOKIE) || "");
  const kontoPfad = `${url.origin}/${lang ? lang + "/" : ""}konto/`;

  const cookiePath = `/api/auth/${providerName}`;
  function loescheTempCookies(headers) {
    headers.append("Set-Cookie", `${STATE_COOKIE}=; Path=${cookiePath}; Max-Age=0`);
    headers.append("Set-Cookie", `${VERIFIER_COOKIE}=; Path=${cookiePath}; Max-Age=0`);
    headers.append("Set-Cookie", `${LANG_COOKIE}=; Path=${cookiePath}; Max-Age=0`);
  }
  function fehlerRedirect(grund) {
    const headers = new Headers({ Location: `${kontoPfad}?login_error=${grund}` });
    loescheTempCookies(headers);
    return new Response(null, { status: 302, headers });
  }

  // Nutzer:in hat bei Google/PayPal abgebrochen/abgelehnt — kein Fehler, nur kein Login.
  if (errorParam) return fehlerRedirect(`${providerName}_denied`);
  if (!code || !state) return fehlerRedirect(`${providerName}_invalid`);

  // ── CSRF-Schutz: der State aus der Weiterleitungs-URL MUSS exakt zum eigenen, kurz zuvor
  //    gesetzten Cookie passen — sonst könnte jemand einen fremden Autorisierungs-Code
  //    "unterschieben" (klassischer OAuth-CSRF-Angriff). ──
  const stateCookie = getCookieRaw(request, STATE_COOKIE);
  const verifierCookie = getCookieRaw(request, VERIFIER_COOKIE);
  if (!stateCookie || stateCookie !== state || !verifierCookie) {
    return fehlerRedirect(`${providerName}_state_mismatch`);
  }

  if (!env.DB || !env.CUSTOMER_SESSION_SECRET) {
    return fehlerRedirect(`${providerName}_backend_unconfigured`);
  }

  try {
    const redirectUri = `${url.origin}/api/auth/${providerName}/callback`;
    const accessToken = await tauscheCodeGegenToken(env, providerName, { code, redirectUri, codeVerifier: verifierCookie });
    // Einzige Quelle für die Identität: die direkte, server-zu-server-Antwort vom Anbieter.
    const profil = await holeNutzerprofil(env, providerName, accessToken);

    const now = new Date().toISOString();
    const bestehend = await env.DB.prepare(
      `SELECT id FROM customers WHERE provider = ? AND provider_user_id = ?`
    ).bind(providerName, profil.providerUserId).first();

    let customerId;
    if (bestehend) {
      customerId = bestehend.id;
      await env.DB.prepare(`UPDATE customers SET email = ?, name = ?, updated_at = ?, last_login_at = ? WHERE id = ?`)
        .bind(profil.email, profil.name, now, now, customerId)
        .run();
    } else {
      const insert = await env.DB.prepare(
        `INSERT INTO customers (created_at, updated_at, last_login_at, provider, provider_user_id, email, name) VALUES (?,?,?,?,?,?,?)`
      )
        .bind(now, now, now, providerName, profil.providerUserId, profil.email, profil.name)
        .run();
      customerId = insert.meta.last_row_id;
    }

    const sessionToken = await signCustomerSession(customerId, env.CUSTOMER_SESSION_SECRET);
    const headers = new Headers({ Location: `${url.origin}/${lang ? lang + "/" : ""}konto/angemeldet/` });
    headers.append("Set-Cookie", customerCookieHeader(sessionToken, CUSTOMER_SESSION_TAGE * 24 * 60 * 60));
    loescheTempCookies(headers);
    return new Response(null, { status: 302, headers });
  } catch (err) {
    return fehlerRedirect(`${providerName}_failed`);
  }
}
