/**
 * Auth callback. The link emailed by Supabase / GoTrue can arrive in one of
 * two shapes depending on flow:
 *
 *   1. OAuth code exchange:   /auth/callback?code=<>&next=/...
 *   2. PKCE email confirm:    /auth/callback?token=<>&type=signup|magiclink|recovery|invite&next=/...
 *      (also: token_hash variant on newer Supabase JS)
 *
 * We handle both, exchange for a session, and bounce to `next` (or /).
 */
import { NextResponse } from "next/server";
import { createClient } from "@/lib/supabase/server";

type OtpType = "signup" | "magiclink" | "recovery" | "invite" | "email_change";

const VALID_OTP_TYPES: Set<OtpType> = new Set([
  "signup", "magiclink", "recovery", "invite", "email_change",
]);

export async function GET(request: Request) {
  const url = new URL(request.url);
  const { searchParams, origin } = url;

  // Always bounce to the public site origin, not whatever Host the
  // request happened to come in on (e.g. api.disclosure.top via the Kong
  // proxy fallback). NEXT_PUBLIC_SITE_URL is baked at build time.
  const siteOrigin = process.env.NEXT_PUBLIC_SITE_URL || origin;

  // CRITICAL: cookies are scoped to the request host. If the link in the
  // email landed on the API host (api.disclosure.top, proxied via Kong),
  // any session cookie we set here would be on api.* and the user would
  // appear logged out when they bounce to disclosure.top. Redirect first
  // so verifyOtp runs on the site domain.
  //
  // `url.host` returns the internal listen host inside the Next.js server
  // (typically `localhost:3000`), NOT the public host the browser used.
  // We must read the proxy-forwarded host header to know where the user
  // actually is. Traefik sets X-Forwarded-Host; Kong preserves Host.
  const forwardedHost = (request.headers.get("x-forwarded-host") ||
                          request.headers.get("host") || "").toLowerCase();
  try {
    const siteUrl = new URL(siteOrigin);
    if (forwardedHost && forwardedHost !== siteUrl.host.toLowerCase()) {
      const redirected = new URL("/auth/callback", siteOrigin);
      searchParams.forEach((v, k) => redirected.searchParams.set(k, v));
      return NextResponse.redirect(redirected.toString());
    }
  } catch {
    /* siteOrigin malformed — fall through and try local verify */
  }

  const code = searchParams.get("code");
  const tokenHash = searchParams.get("token_hash") || searchParams.get("token");
  const typeRaw = searchParams.get("type");
  const next = searchParams.get("next") ?? "/";

  const supabase = await createClient();

  // Path 1 — PKCE / OTP token (token + type)
  if (tokenHash && typeRaw && VALID_OTP_TYPES.has(typeRaw as OtpType)) {
    const { error } = await supabase.auth.verifyOtp({
      type: typeRaw as OtpType,
      token_hash: tokenHash,
    });
    if (error) {
      return NextResponse.redirect(
        `${siteOrigin}/auth/signin?error=${encodeURIComponent(error.message)}`,
      );
    }
    return NextResponse.redirect(`${siteOrigin}${next}`);
  }

  // Path 2 — OAuth code exchange
  if (code) {
    const { error } = await supabase.auth.exchangeCodeForSession(code);
    if (error) {
      return NextResponse.redirect(
        `${siteOrigin}/auth/signin?error=${encodeURIComponent(error.message)}`,
      );
    }
    return NextResponse.redirect(`${siteOrigin}${next}`);
  }

  // Neither shape provided
  return NextResponse.redirect(`${siteOrigin}/auth/signin?error=missing_token`);
}
