import type { NextAuthOptions } from "next-auth";
import type { JWT } from "next-auth/jwt";
import AzureADProvider from "next-auth/providers/azure-ad";
import GoogleProvider from "next-auth/providers/google";

const providers = [];
const azureScope =
  "openid profile email offline_access https://graph.microsoft.com/User.Read https://graph.microsoft.com/Calendars.ReadWrite";

async function refreshAzureAccessToken(token: JWT): Promise<JWT> {
  if (!token.refreshToken || typeof token.refreshToken !== "string") {
    return { ...token, error: "RefreshTokenMissing" };
  }

  const tenant = "common";
  const tokenEndpoint = `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`;

  const body = new URLSearchParams({
    client_id: process.env.MS_CLIENT_ID || "",
    client_secret: process.env.MS_CLIENT_SECRET || "",
    grant_type: "refresh_token",
    refresh_token: token.refreshToken,
    scope: azureScope
  });

  const response = await fetch(tokenEndpoint, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body
  });

  if (!response.ok) {
    return { ...token, error: "RefreshAccessTokenError" };
  }

  const refreshed = (await response.json()) as {
    access_token: string;
    expires_in?: number;
    refresh_token?: string;
  };

  return {
    ...token,
    accessToken: refreshed.access_token,
    accessTokenExpires:
      Date.now() + (typeof refreshed.expires_in === "number" ? refreshed.expires_in : 3600) * 1000,
    refreshToken: refreshed.refresh_token ?? token.refreshToken,
    error: undefined
  };
}

async function refreshGoogleAccessToken(token: JWT): Promise<JWT> {
  if (!token.refreshToken || typeof token.refreshToken !== "string") {
    return { ...token, error: "RefreshTokenMissing" };
  }

  const body = new URLSearchParams({
    client_id: process.env.GOOGLE_CLIENT_ID || "",
    client_secret: process.env.GOOGLE_CLIENT_SECRET || "",
    grant_type: "refresh_token",
    refresh_token: token.refreshToken
  });

  const response = await fetch("https://oauth2.googleapis.com/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body
  });

  if (!response.ok) {
    return { ...token, error: "RefreshAccessTokenError" };
  }

  const refreshed = (await response.json()) as {
    access_token: string;
    expires_in?: number;
    refresh_token?: string;
  };

  return {
    ...token,
    accessToken: refreshed.access_token,
    accessTokenExpires:
      Date.now() + (typeof refreshed.expires_in === "number" ? refreshed.expires_in : 3600) * 1000,
    refreshToken: refreshed.refresh_token ?? token.refreshToken,
    error: undefined
  };
}

if (process.env.MS_CLIENT_ID && process.env.MS_CLIENT_SECRET) {
  providers.push(
    AzureADProvider({
      clientId: process.env.MS_CLIENT_ID,
      clientSecret: process.env.MS_CLIENT_SECRET,
      tenantId: "common",
      authorization: {
        params: {
          scope: azureScope,
          prompt: "consent",
          response_type: "code"
        }
      }
    })
  );
}

if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {
  providers.push(
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      authorization: {
        params: {
          scope:
            "openid email profile https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/calendar.events",
          prompt: "consent select_account",
          access_type: "offline",
          include_granted_scopes: "false",
          response_type: "code"
        }
      }
    })
  );
}

export const authOptions: NextAuthOptions = {
  session: { strategy: "jwt" },
  secret: process.env.NEXTAUTH_SECRET,
  providers,
  callbacks: {
    async jwt({ token, account }) {
      if (account?.access_token) {
        token.accessToken = account.access_token;
        token.provider = account.provider;
        token.refreshToken = account.refresh_token ?? token.refreshToken;

        const expiresInSeconds =
          typeof account.expires_in === "number" ? account.expires_in : 3600;
        const expiresAtSeconds =
          typeof account.expires_at === "number"
            ? account.expires_at
            : Math.floor(Date.now() / 1000) + expiresInSeconds;
        token.accessTokenExpires = expiresAtSeconds * 1000;
        token.error = undefined;
      }

      if (!token.accessToken || !token.accessTokenExpires) {
        // Backward compatibility for sessions issued before expiry metadata existed.
        if (token.accessToken && token.refreshToken && token.provider === "azure-ad") {
          return refreshAzureAccessToken(token);
        }

        if (token.accessToken && token.refreshToken && token.provider === "google") {
          return refreshGoogleAccessToken(token);
        }

        return token;
      }

      if (Date.now() < token.accessTokenExpires - 60 * 1000) {
        return token;
      }

      if (token.provider === "azure-ad") {
        return refreshAzureAccessToken(token);
      }

      if (token.provider === "google") {
        return refreshGoogleAccessToken(token);
      }

      return token;
    },
    async session({ session, token }) {
      session.accessToken = token.accessToken as string | undefined;
      session.provider = token.provider as string | undefined;
      session.error = token.error as string | undefined;
      return session;
    }
  }
};
