import { NextResponse } from "next/server";

import {
  getOidcConfiguration,
  OIDC_COOKIE_NAMES,
} from "@/lib/sso/config";
import {
  exchangeAndValidateOidcCode,
  safeOidcValueEquals,
} from "@/lib/sso/oidc";
import { createSsoSession } from "@/lib/sso/session";

export const dynamic = "force-dynamic";
export const runtime = "nodejs";

function getRequestCookie(request: Request, name: string) {
  const rawValue = request.headers
    .get("cookie")
    ?.split(";")
    .map((item) => item.trim())
    .find((item) => item.startsWith(`${name}=`))
    ?.slice(name.length + 1);

  if (!rawValue) return null;

  try {
    return decodeURIComponent(rawValue);
  } catch {
    return null;
  }
}

function clearOidcCookies(response: NextResponse) {
  for (const name of Object.values(OIDC_COOKIE_NAMES)) {
    response.cookies.set({
      name,
      value: "",
      httpOnly: true,
      sameSite: "lax",
      secure: false,
      path: "/api/auth",
      maxAge: 0,
    });
  }

  return response;
}

function redirectToLogin(reason: string) {
  const { frontendOrigin } = getOidcConfiguration();
  const url = new URL("/login", frontendOrigin);
  url.searchParams.set("sso", reason);

  return clearOidcCookies(NextResponse.redirect(url));
}

export async function GET(request: Request) {
  try {
    const url = new URL(request.url);
    const providerError = url.searchParams.get("error");

    if (providerError) {
      return redirectToLogin(
        providerError === "access_denied" ? "cancelled" : "provider_error",
      );
    }

    const code = url.searchParams.get("code");
    const returnedState = url.searchParams.get("state");
    const expectedState = getRequestCookie(
      request,
      OIDC_COOKIE_NAMES.state,
    );
    const expectedNonce = getRequestCookie(
      request,
      OIDC_COOKIE_NAMES.nonce,
    );
    const codeVerifier = getRequestCookie(
      request,
      OIDC_COOKIE_NAMES.verifier,
    );

    if (
      !code ||
      !expectedNonce ||
      !codeVerifier ||
      !safeOidcValueEquals(returnedState, expectedState)
    ) {
      return redirectToLogin("invalid_state");
    }

    const profile = await exchangeAndValidateOidcCode({
      code,
      codeVerifier,
      expectedNonce,
    });
    const response = await createSsoSession(profile);

    return clearOidcCookies(response);
  } catch (error) {
    console.error(
      "[sso] OIDC callback failed",
      error instanceof Error ? error.message : error,
    );

    return redirectToLogin("failed");
  }
}