import * as oauth from "oauth4webapi";
import { AuthErrorRedirect } from "../error";
import type { OIDCProvider, SessionCallback } from "../types";
import { getCallbackURL } from "../utils/callback";
import { parseCookies } from "../utils/cookie";

export async function OIDCCallback(
  request: Request,
  provider: OIDCProvider,
  sessionCallback: SessionCallback,
): Promise<Response> {
  const parsedCookies = parseCookies(request.headers.get("Cookie")!);

  const code_verifier = parsedCookies["__session-code-verifier"];
  const nonce = parsedCookies["__session-nonce"];

  if (!code_verifier) {
    throw Error("Invalid session");
  }

  const { client_id, client_secret, issuer, profileCallback } = provider;
  const client: oauth.Client = {
    client_id,
    client_secret,
    token_endpoint_auth_method: "client_secret_basic",
  };

  const current_url = new URL(request.url);
  const callback_url = getCallbackURL(request);

  const as = await oauth
    .discoveryRequest(issuer, { algorithm: "oidc" })
    .then((response) => oauth.processDiscoveryResponse(issuer, response));

  const params = oauth.validateAuthResponse(as, client, current_url);

  if (oauth.isOAuth2Error(params)) {
    return AuthErrorRedirect(request, "Invalid params");
  }

  const response = await oauth.authorizationCodeGrantRequest(
    as,
    client,
    params,
    callback_url.toString(),
    code_verifier,
  );
  const challenges = oauth.parseWwwAuthenticateChallenges(response);

  if (challenges) {
    return AuthErrorRedirect(request, "Failed to authenticate");
  }

  const token_result = await oauth.processAuthorizationCodeOpenIDResponse(
    as,
    client,
    response,
    nonce,
  );

  if (oauth.isOAuth2Error(token_result)) {
    return AuthErrorRedirect(request, "Failed to authenticate");
  }

  const claims = oauth.getValidatedIdTokenClaims(token_result);
  const userInfoResponse = await oauth.userInfoRequest(
    as,
    client,
    token_result.access_token,
  );

  if (oauth.parseWwwAuthenticateChallenges(userInfoResponse)) {
    return AuthErrorRedirect(request, "Failed to authenticate");
  }

  const result = await oauth.processUserInfoResponse(
    as,
    client,
    claims.sub,
    userInfoResponse,
  );
  const accountInfo = profileCallback({
    sub: result.sub,
    name: result.name!,
    email: result.email!,
    picture: result.picture!,
  });
  return sessionCallback({
    email: accountInfo.email,
    sub: accountInfo.sub,
    provider: provider.name,
    scope: provider.scope,
  });
}
