import * as oauth from "oauth4webapi";
import type { UserinfoResponse } from "openid-client";
import { AuthErrorRedirect } from "../error";
import type { OAuth2Provider, SessionCallback } from "../types";
import { getCallbackURL } from "../utils/callback";
import { parseCookies } from "../utils/cookie";

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

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

  if (!code_verifier || !state) {
    return AuthErrorRedirect(request, "Invalid session");
  }

  const {
    client_id,
    client_secret,
    authorization_server: as,
    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 params = oauth.validateAuthResponse(as, client, current_url, state);
  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.processAuthorizationCodeOAuth2Response(
    as,
    client,
    response,
  );

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

  const userInfoResponse = await oauth.userInfoRequest(
    as,
    client,
    token_result.access_token,
  );
  const userInfo = (await userInfoResponse.json()) as UserinfoResponse;

  const profile = profileCallback(userInfo);
  return sessionCallback({
    email: profile.email,
    sub: profile.sub,
    provider: provider.name,
    scope: provider.scope,
  });
}
