import { NextAuthOptions } from "next-auth";
import { JWT } from "next-auth/jwt";
import KeycloakProvider from "next-auth/providers/keycloak";
import Avatar from "@assets/avatar/avatar-small.jpeg";

type KeycloakTokenResponse = {
  access_token: string;
  expires_in: number;
  refresh_token?: string;
};

const getKeycloakTokenEndpoint = () => {
  const issuer = process.env.KEYCLOAK_ISSUER;
  return issuer
    ? `${issuer.replace(/\/$/, "")}/protocol/openid-connect/token`
    : undefined;
};

const refreshAccessToken = async (token: JWT): Promise<JWT> => {
  if (!token.refreshToken) {
    return { ...token, error: "RefreshAccessTokenError" };
  }

  const keycloakClientId = process.env.KEYCLOAK_CLIENT_ID;
  const keycloakClientSecret = process.env.KEYCLOAK_CLIENT_SECRET;
  const keycloakTokenEndpoint = getKeycloakTokenEndpoint();

  if (!keycloakClientId || !keycloakClientSecret || !keycloakTokenEndpoint) {
    return { ...token, error: "RefreshAccessTokenError" };
  }

  const body = new URLSearchParams({
    grant_type: "refresh_token",
    client_id: keycloakClientId,
    client_secret: keycloakClientSecret,
    refresh_token: token.refreshToken,
  });

  const response = await fetch(keycloakTokenEndpoint, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body,
  });
  const refreshed = (await response.json()) as KeycloakTokenResponse;

  if (!response.ok || !refreshed.access_token || typeof refreshed.expires_in !== "number") {
    return { ...token, error: "RefreshAccessTokenError" };
  }

  return {
    ...token,
    accessToken: refreshed.access_token,
    accessTokenExpires: Date.now() + refreshed.expires_in * 1000,
    refreshToken: refreshed.refresh_token ?? token.refreshToken,
    error: undefined,
  };
};

const authOptions: NextAuthOptions = {
  // Configure one or more authentication providers
  providers: [
    KeycloakProvider({
      clientId: process.env.KEYCLOAK_CLIENT_ID ?? "",
      clientSecret: process.env.KEYCLOAK_CLIENT_SECRET ?? "",
      issuer: process.env.KEYCLOAK_ISSUER ?? "",
      profile(profile) {
        return {
          id: profile.sub,
          username: profile.preferred_username,
          name: profile.name ?? profile.preferred_username,
          email: profile.email,
          image: Avatar.src,
        };
      },
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET,
  callbacks: {
    jwt: async ({ token, profile, account }) => {
      if (profile?.sub) {
        token.sub = profile.sub;
      }
      const preferredUsername = (
        profile as { preferred_username?: unknown } | undefined
      )?.preferred_username;
      if (typeof preferredUsername === "string") {
        token.username = preferredUsername;
      }

      if (account) {
        if (account.access_token) {
          token.accessToken = account.access_token;
        }
        if (account.refresh_token) {
          token.refreshToken = account.refresh_token;
        }
        if (typeof account.expires_at === "number") {
          token.accessTokenExpires = account.expires_at * 1000;
        }
        token.error = undefined;
        return token;
      }

      if (typeof token.accessTokenExpires === "number" && Date.now() < token.accessTokenExpires - 30_000) {
        return token;
      }

      return refreshAccessToken(token);
    },
    session: async ({ session, token }) => {
      if (session.user && token.sub) {
        session.user.id = token.sub;
      }
      if (session.user && token.username) {
        session.user.username = token.username;
      }
      if (token.accessToken) {
        session.accessToken = token.accessToken;
      }
      if (token.error) {
        session.error = token.error;
      }
      return session;
    },
  },
};

export default authOptions;
