import { prisma } from "@linkwarden/prisma";
import sendInvitationRequest from "@/lib/api/sendInvitationRequest";
import sendVerificationRequest from "@/lib/api/sendVerificationRequest";
import updateSeats from "@/lib/api/stripe/updateSeats";
import verifySubscription from "@/lib/api/stripe/verifySubscription";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { User } from "@linkwarden/prisma/client";
import bcrypt from "bcrypt";
import type { NextApiRequest, NextApiResponse } from "next";
import { Adapter } from "next-auth/adapters";
import NextAuth from "next-auth/next";
import { Provider } from "next-auth/providers";
import FortyTwoProvider from "next-auth/providers/42-school";
import AppleProvider from "next-auth/providers/apple";
import AtlassianProvider from "next-auth/providers/atlassian";
import Auth0Provider from "next-auth/providers/auth0";
import AuthentikProvider from "next-auth/providers/authentik";
import AzureAdProvider from "next-auth/providers/azure-ad";
import AzureAdB2CProvider from "next-auth/providers/azure-ad-b2c";
import BattleNetProvider, {
  BattleNetIssuer,
} from "next-auth/providers/battlenet";
import BoxProvider from "next-auth/providers/box";
import CognitoProvider from "next-auth/providers/cognito";
import CoinbaseProvider from "next-auth/providers/coinbase";
import CredentialsProvider from "next-auth/providers/credentials";
import DiscordProvider from "next-auth/providers/discord";
import DropboxProvider from "next-auth/providers/dropbox";
import DuendeIDS6Provider from "next-auth/providers/duende-identity-server6";
import EmailProvider from "next-auth/providers/email";
import EVEOnlineProvider from "next-auth/providers/eveonline";
import FacebookProvider from "next-auth/providers/facebook";
import FaceItProvider from "next-auth/providers/faceit";
import FourSquareProvider from "next-auth/providers/foursquare";
import FreshbooksProvider from "next-auth/providers/freshbooks";
import FusionAuthProvider from "next-auth/providers/fusionauth";
import GitHubProvider from "next-auth/providers/github";
import GitlabProvider from "next-auth/providers/gitlab";
import GoogleProvider from "next-auth/providers/google";
import HubspotProvider from "next-auth/providers/hubspot";
import IdentityServer4Provider from "next-auth/providers/identity-server4";
import KakaoProvider from "next-auth/providers/kakao";
import KeycloakProvider from "next-auth/providers/keycloak";
import LineProvider from "next-auth/providers/line";
import LinkedInProvider from "next-auth/providers/linkedin";
import MailchimpProvider from "next-auth/providers/mailchimp";
import MailRuProvider from "next-auth/providers/mailru";
import NaverProvider from "next-auth/providers/naver";
import NetlifyProvider from "next-auth/providers/netlify";
import OktaProvider from "next-auth/providers/okta";
import OneLoginProvider from "next-auth/providers/onelogin";
import OssoProvider from "next-auth/providers/osso";
import OsuProvider from "next-auth/providers/osu";
import PatreonProvider from "next-auth/providers/patreon";
import PinterestProvider from "next-auth/providers/pinterest";
import PipedriveProvider from "next-auth/providers/pipedrive";
import RedditProvider from "next-auth/providers/reddit";
import SalesforceProvider from "next-auth/providers/salesforce";
import SlackProvider from "next-auth/providers/slack";
import SpotifyProvider from "next-auth/providers/spotify";
import StravaProvider from "next-auth/providers/strava";
import TodoistProvider from "next-auth/providers/todoist";
import TwitchProvider from "next-auth/providers/twitch";
import UnitedEffectsProvider from "next-auth/providers/united-effects";
import VkProvider from "next-auth/providers/vk";
import WikimediaProvider from "next-auth/providers/wikimedia";
import WordpressProvider from "next-auth/providers/wordpress";
import YandexProvider from "next-auth/providers/yandex";
import ZitadelProvider from "next-auth/providers/zitadel";
import ZohoProvider from "next-auth/providers/zoho";
import ZoomProvider from "next-auth/providers/zoom";
import * as process from "process";

const emailEnabled =
  process.env.EMAIL_FROM && process.env.EMAIL_SERVER ? true : false;

const newSsoUsersDisabled = process.env.DISABLE_NEW_SSO_USERS === "true";
const adapter = PrismaAdapter(prisma);

const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;

const providers: Provider[] = [];

if (process.env.NEXT_PUBLIC_CREDENTIALS_ENABLED !== "false") {
  // undefined is for backwards compatibility
  providers.push(
    CredentialsProvider({
      type: "credentials",
      credentials: {},
      async authorize(credentials, req) {
        console.log("User log in attempt...");
        if (!credentials) return null;

        const { username, password } = credentials as {
          username: string;
          password: string;
        };

        const user = await prisma.user.findFirst({
          where: emailEnabled
            ? {
                OR: [
                  {
                    username: username.toLowerCase(),
                  },
                  {
                    email: username?.toLowerCase(),
                  },
                ],
              }
            : {
                username: username.toLowerCase(),
              },
        });

        if (!user) throw Error("Invalid credentials.");
        else if (!user?.emailVerified && emailEnabled) {
          throw Error("Email not verified.");
        }

        let passwordMatches: boolean = false;

        if (user?.password) {
          passwordMatches = bcrypt.compareSync(password, user.password);
        }

        if (passwordMatches && user?.password) {
          return { id: user?.id };
        } else throw Error("Invalid credentials.");
      },
    })
  );
}

if (emailEnabled) {
  providers.push(
    EmailProvider({
      id: "email",
      server: process.env.EMAIL_SERVER,
      from: process.env.EMAIL_FROM,
      maxAge: 1200,
      async sendVerificationRequest({ identifier, url, provider, token }: any) {
        const recentVerificationRequestsCount =
          await prisma.verificationToken.count({
            where: {
              identifier,
              createdAt: {
                gt: new Date(new Date().getTime() - 1000 * 60 * 5), // 5 minutes
              },
            },
          });

        if (recentVerificationRequestsCount >= 4)
          throw Error("Too many requests. Please try again later.");

        sendVerificationRequest({
          identifier,
          url,
          from: provider.from as string,
          token,
        });
      },
    } as any),
    EmailProvider({
      id: "invite",
      server: process.env.EMAIL_SERVER,
      from: process.env.EMAIL_FROM,
      maxAge: 1200,
      async sendVerificationRequest({ identifier, url, provider, token }: any) {
        const parentSubscriptionEmail = (
          await prisma.user.findFirst({
            where: {
              email: identifier,
              emailVerified: null,
            },
            include: {
              parentSubscription: {
                include: {
                  user: {
                    select: {
                      email: true,
                    },
                  },
                },
              },
            },
          })
        )?.parentSubscription?.user.email;

        if (!parentSubscriptionEmail) throw Error("Invalid email.");

        const recentVerificationRequestsCount =
          await prisma.verificationToken.count({
            where: {
              identifier,
              createdAt: {
                gt: new Date(new Date().getTime() - 1000 * 60 * 5), // 5 minutes
              },
            },
          });

        if (recentVerificationRequestsCount >= 4)
          throw Error("Too many requests. Please try again later.");

        sendInvitationRequest({
          parentSubscriptionEmail,
          identifier,
          url,
          from: provider.from as string,
          token,
        });
      },
    } as any)
  );
}

// 42 School
if (process.env.NEXT_PUBLIC_FORTYTWO_ENABLED === "true") {
  providers.push(
    FortyTwoProvider({
      id: "42-school",
      name: "42 School",
      clientId: process.env.FORTY_TWO_CLIENT_ID!,
      clientSecret: process.env.FORTY_TWO_CLIENT_SECRET!,
      profile(profile) {
        return {
          id: profile.id.toString(),
          name: profile.usual_full_name,
          email: profile.email,
          image: profile.image_url,
          username: profile.id.toString(),
        };
      },
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Apple
if (process.env.NEXT_PUBLIC_APPLE_ENABLED === "true") {
  providers.push(
    AppleProvider({
      clientId: process.env.APPLE_CLIENT_ID!,
      clientSecret: process.env.APPLE_CLIENT_SECRET!,
      profile(profile) {
        return {
          id: profile.sub,
          name: profile.name,
          email: profile.email,
          image: null,
          username: profile.sub,
        };
      },
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Atlassian
if (process.env.NEXT_PUBLIC_ATLASSIAN_ENABLED === "true") {
  providers.push(
    AtlassianProvider({
      clientId: process.env.ATLASSIAN_CLIENT_ID!,
      clientSecret: process.env.ATLASSIAN_CLIENT_SECRET!,
      authorization: {
        params: {
          scope:
            "write:jira-work read:jira-work read:jira-user offline_access read:me",
        },
      },
      profile(profile) {
        return {
          id: profile.account_id,
          name: profile.name,
          email: profile.email,
          image: profile.picture,
          username: profile.account_id,
        };
      },
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Auth0
if (process.env.NEXT_PUBLIC_AUTH0_ENABLED === "true") {
  providers.push(
    Auth0Provider({
      clientId: process.env.AUTH0_CLIENT_ID!,
      clientSecret: process.env.AUTH0_CLIENT_SECRET!,
      issuer: process.env.AUTH0_ISSUER,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Authelia
if (process.env.NEXT_PUBLIC_AUTHELIA_ENABLED === "true") {
  providers.push({
    id: "authelia",
    name: "Authelia",
    type: "oauth",
    clientId: process.env.AUTHELIA_CLIENT_ID!,
    clientSecret: process.env.AUTHELIA_CLIENT_SECRET!,
    wellKnown: process.env.AUTHELIA_WELLKNOWN_URL!,
    authorization: { params: { scope: "openid email profile" } },
    idToken: true,
    checks: ["pkce", "state"],
    profile(profile) {
      return {
        id: profile.sub,
        name: profile.name,
        email: profile.email,
        username: profile.preferred_username,
      };
    },
  });

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Authentik
if (process.env.NEXT_PUBLIC_AUTHENTIK_ENABLED === "true") {
  providers.push(
    AuthentikProvider({
      clientId: process.env.AUTHENTIK_CLIENT_ID!,
      clientSecret: process.env.AUTHENTIK_CLIENT_SECRET!,
      issuer: process.env.AUTHENTIK_ISSUER,
      profile: (profile) => {
        return {
          id: profile.sub,
          username: profile.preferred_username,
          name: profile.name || "",
          email: profile.email,
          image: profile.picture,
        };
      },
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Azure AD B2C
if (process.env.NEXT_PUBLIC_AZURE_AD_ENABLED === "true") {
  providers.push(
    AzureAdB2CProvider({
      tenantId: process.env.AZURE_AD_B2C_TENANT_NAME,
      clientId: process.env.AZURE_AD_B2C_CLIENT_ID!,
      clientSecret: process.env.AZURE_AD_B2C_CLIENT_SECRET!,
      primaryUserFlow: process.env.AZURE_AD_B2C_PRIMARY_USER_FLOW,
      authorization: { params: { scope: "offline_access openid" } },
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const {
      "not-before-policy": _,
      refresh_expires_in,
      refresh_token_expires_in,
      not_before,
      id_token_expires_in,
      profile_info,
      ...data
    } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Azure AD
if (process.env.NEXT_PUBLIC_AZURE_AD_ENABLED === "true") {
  providers.push(
    AzureAdProvider({
      clientId: process.env.AZURE_AD_CLIENT_ID!,
      clientSecret: process.env.AZURE_AD_CLIENT_SECRET!,
      tenantId: process.env.AZURE_AD_TENANT_ID,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const {
      "not-before-policy": _,
      refresh_expires_in,
      token_type,
      expires_in,
      ext_expires_in,
      access_token,
      ...data
    } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Battle.net
if (process.env.NEXT_PUBLIC_BATTLENET_ENABLED === "true") {
  providers.push(
    BattleNetProvider({
      clientId: process.env.BATTLENET_CLIENT_ID!,
      clientSecret: process.env.BATTLENET_CLIENT_SECRET!,
      issuer: process.env.BATTLENET_ISSUER as BattleNetIssuer,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Box
if (process.env.NEXT_PUBLIC_BOX_ENABLED === "true") {
  providers.push(
    BoxProvider({
      clientId: process.env.BOX_CLIENT_ID,
      clientSecret: process.env.BOX_CLIENT_SECRET,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Cognito
if (process.env.NEXT_PUBLIC_COGNITO_ENABLED === "true") {
  providers.push(
    CognitoProvider({
      clientId: process.env.COGNITO_CLIENT_ID!,
      clientSecret: process.env.COGNITO_CLIENT_SECRET!,
      issuer: process.env.COGNITO_ISSUER,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Coinbase
if (process.env.NEXT_PUBLIC_COINBASE_ENABLED === "true") {
  providers.push(
    CoinbaseProvider({
      clientId: process.env.COINBASE_CLIENT_ID,
      clientSecret: process.env.COINBASE_CLIENT_SECRET,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Discord
if (process.env.NEXT_PUBLIC_DISCORD_ENABLED === "true") {
  providers.push(
    DiscordProvider({
      clientId: process.env.DISCORD_CLIENT_ID!,
      clientSecret: process.env.DISCORD_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Dropbox
if (process.env.NEXT_PUBLIC_DROPBOX_ENABLED === "true") {
  providers.push(
    DropboxProvider({
      clientId: process.env.DROPBOX_CLIENT_ID,
      clientSecret: process.env.DROPBOX_CLIENT_SECRET,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Duende IdentityServer6
if (process.env.NEXT_PUBLIC_DUENDE_IDS6_ENABLED === "true") {
  providers.push(
    DuendeIDS6Provider({
      clientId: process.env.DUENDE_IDS6_ID!,
      clientSecret: process.env.DUENDE_IDS6_SECRET!,
      issuer: process.env.DUENDE_IDS6_ISSUER,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// EVE Online
if (process.env.NEXT_PUBLIC_EVEONLINE_ENABLED === "true") {
  providers.push(
    EVEOnlineProvider({
      clientId: process.env.EVE_CLIENT_ID!,
      clientSecret: process.env.EVE_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Facebook
if (process.env.NEXT_PUBLIC_FACEBOOK_ENABLED === "true") {
  providers.push(
    FacebookProvider({
      clientId: process.env.FACEBOOK_CLIENT_ID!,
      clientSecret: process.env.FACEBOOK_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// FACEIT
if (process.env.NEXT_PUBLIC_FACEIT_ENABLED === "true") {
  providers.push(
    FaceItProvider({
      clientId: process.env.FACEIT_CLIENT_ID,
      clientSecret: process.env.FACEIT_CLIENT_SECRET,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Foursquare
if (process.env.NEXT_PUBLIC_FOURSQUARE_ENABLED === "true") {
  providers.push(
    FourSquareProvider({
      clientId: process.env.FOURSQUARE_CLIENT_ID,
      clientSecret: process.env.FOURSQUARE_CLIENT_SECRET,
      apiVersion: process.env.FOURSQUARE_APIVERSION,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Freshbooks
if (process.env.NEXT_PUBLIC_FRESHBOOKS_ENABLED === "true") {
  providers.push(
    FreshbooksProvider({
      clientId: process.env.FRESHBOOKS_CLIENT_ID,
      clientSecret: process.env.FRESHBOOKS_CLIENT_SECRET,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// FusionAuth
if (process.env.NEXT_PUBLIC_FUSIONAUTH_ENABLED === "true") {
  providers.push(
    FusionAuthProvider({
      id: "fusionauth",
      name: "FusionAuth",
      issuer: process.env.FUSIONAUTH_ISSUER,
      clientId: process.env.FUSIONAUTH_CLIENT_ID!,
      clientSecret: process.env.FUSIONAUTH_SECRET!,
      tenantId: process.env.FUSIONAUTH_TENANT_ID,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// GitHub
if (process.env.NEXT_PUBLIC_GITHUB_ENABLED === "true") {
  providers.push(
    GitHubProvider({
      clientId: process.env.GITHUB_ID!,
      clientSecret: process.env.GITHUB_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// GitLab
if (process.env.NEXT_PUBLIC_GITLAB_ENABLED === "true") {
  providers.push(
    GitlabProvider({
      clientId: process.env.GITLAB_CLIENT_ID!,
      clientSecret: process.env.GITLAB_CLIENT_SECRET!,
      authorization: {
        url: `${
          process.env.GITLAB_AUTH_URL || "https://gitlab.com"
        }/oauth/authorize`,
        params: { scope: "read_user" },
      },
      token: `${
        process.env.GITLAB_AUTH_URL || "https://gitlab.com"
      }/oauth/token`,
      userinfo: `${
        process.env.GITLAB_AUTH_URL || "https://gitlab.com"
      }/api/v4/user`,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Google
if (process.env.NEXT_PUBLIC_GOOGLE_ENABLED === "true") {
  providers.push(
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      httpOptions: {
        timeout: 10000,
      },
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// HubSpot
if (process.env.NEXT_PUBLIC_HUBSPOT_ENABLED === "true") {
  providers.push(
    HubspotProvider({
      clientId: process.env.HUBSPOT_CLIENT_ID!,
      clientSecret: process.env.HUBSPOT_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// IdentityServer4
if (process.env.NEXT_PUBLIC_IDS4_ENABLED === "true") {
  providers.push(
    IdentityServer4Provider({
      id: "identity-server4",
      name: "IdentityServer4",
      issuer: process.env.IdentityServer4_Issuer,
      clientId: process.env.IdentityServer4_CLIENT_ID,
      clientSecret: process.env.IdentityServer4_CLIENT_SECRET,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Kakao
if (process.env.NEXT_PUBLIC_KAKAO_ENABLED === "true") {
  providers.push(
    KakaoProvider({
      clientId: process.env.KAKAO_CLIENT_ID!,
      clientSecret: process.env.KAKAO_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Keycloak
if (process.env.NEXT_PUBLIC_KEYCLOAK_ENABLED === "true") {
  providers.push(
    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 || "",
          email: profile.email,
          image: profile.picture,
        };
      },
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// LINE
if (process.env.NEXT_PUBLIC_LINE_ENABLED === "true") {
  providers.push(
    LineProvider({
      clientId: process.env.LINE_CLIENT_ID!,
      clientSecret: process.env.LINE_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// LinkedIn
if (process.env.NEXT_PUBLIC_LINKEDIN_ENABLED === "true") {
  providers.push(
    LinkedInProvider({
      clientId: process.env.LINKEDIN_CLIENT_ID!,
      clientSecret: process.env.LINKEDIN_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Mailchimp
if (process.env.NEXT_PUBLIC_MAILCHIMP_ENABLED === "true") {
  providers.push(
    MailchimpProvider({
      clientId: process.env.MAILCHIMP_CLIENT_ID,
      clientSecret: process.env.MAILCHIMP_CLIENT_SECRET,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Mail.ru
if (process.env.NEXT_PUBLIC_MAILRU_ENABLED === "true") {
  providers.push(
    MailRuProvider({
      clientId: process.env.MAILRU_CLIENT_ID,
      clientSecret: process.env.MAILRU_CLIENT_SECRET,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Naver
if (process.env.NEXT_PUBLIC_NAVER_ENABLED === "true") {
  providers.push(
    NaverProvider({
      clientId: process.env.NAVER_CLIENT_ID!,
      clientSecret: process.env.NAVER_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Netlify
if (process.env.NEXT_PUBLIC_NETLIFY_ENABLED === "true") {
  providers.push(
    NetlifyProvider({
      clientId: process.env.NETLIFY_CLIENT_ID,
      clientSecret: process.env.NETLIFY_CLIENT_SECRET,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Okta
if (process.env.NEXT_PUBLIC_OKTA_ENABLED === "true") {
  providers.push(
    OktaProvider({
      clientId: process.env.OKTA_CLIENT_ID!,
      clientSecret: process.env.OKTA_CLIENT_SECRET!,
      issuer: process.env.OKTA_ISSUER,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// OneLogin
if (process.env.NEXT_PUBLIC_ONELOGIN_ENABLED === "true") {
  providers.push(
    OneLoginProvider({
      clientId: process.env.ONELOGIN_CLIENT_ID,
      clientSecret: process.env.ONELOGIN_CLIENT_SECRET,
      issuer: process.env.ONELOGIN_ISSUER,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Osso
if (process.env.NEXT_PUBLIC_OSSO_ENABLED === "true") {
  providers.push(
    OssoProvider({
      clientId: process.env.OSSO_CLIENT_ID,
      clientSecret: process.env.OSSO_CLIENT_SECRET,
      issuer: process.env.OSSO_ISSUER,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// osu!
if (process.env.NEXT_PUBLIC_OSU_ENABLED === "true") {
  providers.push(
    OsuProvider({
      clientId: process.env.OSU_CLIENT_ID!,
      clientSecret: process.env.OSU_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Patreon
if (process.env.NEXT_PUBLIC_PATREON_ENABLED === "true") {
  providers.push(
    PatreonProvider({
      clientId: process.env.PATREON_ID!,
      clientSecret: process.env.PATREON_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Pinterest
if (process.env.NEXT_PUBLIC_PINTEREST_ENABLED === "true") {
  providers.push(
    PinterestProvider({
      clientId: process.env.PINTEREST_ID!,
      clientSecret: process.env.PINTEREST_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Pipedrive
if (process.env.NEXT_PUBLIC_PIPEDRIVE_ENABLED === "true") {
  providers.push(
    PipedriveProvider({
      clientId: process.env.PIPEDRIVE_CLIENT_ID!,
      clientSecret: process.env.PIPEDRIVE_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Reddit
if (process.env.NEXT_PUBLIC_REDDIT_ENABLED === "true") {
  providers.push(
    RedditProvider({
      clientId: process.env.REDDIT_CLIENT_ID,
      clientSecret: process.env.REDDIT_CLIENT_SECRET,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Salesforce
if (process.env.NEXT_PUBLIC_SALESFORCE_ENABLED === "true") {
  providers.push(
    SalesforceProvider({
      clientId: process.env.SALESFORCE_CLIENT_ID!,
      clientSecret: process.env.SALESFORCE_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Slack
if (process.env.NEXT_PUBLIC_SLACK_ENABLED === "true") {
  providers.push(
    SlackProvider({
      clientId: process.env.SLACK_CLIENT_ID!,
      clientSecret: process.env.SLACK_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Spotify
if (process.env.NEXT_PUBLIC_SPOTIFY_ENABLED === "true") {
  providers.push(
    SpotifyProvider({
      clientId: process.env.SPOTIFY_CLIENT_ID!,
      clientSecret: process.env.SPOTIFY_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Strava
if (process.env.NEXT_PUBLIC_STRAVA_ENABLED === "true") {
  providers.push(
    StravaProvider({
      clientId: process.env.STRAVA_CLIENT_ID!,
      clientSecret: process.env.STRAVA_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Synology
if (process.env.NEXT_PUBLIC_SYNOLOGY_ENABLED === "true") {
  providers.push({
    id: "synology",
    name: "Synology",
    type: "oauth",
    clientId: process.env.SYNOLOGY_CLIENT_ID!,
    clientSecret: process.env.SYNOLOGY_CLIENT_SECRET!,
    wellKnown: process.env.SYNOLOGY_WELLKNOWN_URL!,
    authorization: { params: { scope: "openid email profile" } },
    idToken: true,
    checks: ["pkce", "state"],
    profile(profile) {
      return {
        id: profile.sub,
        name: profile.name,
        email: profile.email,
        username: profile.preferred_username,
      };
    },
  });

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Todoist
if (process.env.NEXT_PUBLIC_TODOIST_ENABLED === "true") {
  providers.push(
    TodoistProvider({
      clientId: process.env.TODOIST_ID!,
      clientSecret: process.env.TODOIST_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Twitch
if (process.env.NEXT_PUBLIC_TWITCH_ENABLED === "true") {
  providers.push(
    TwitchProvider({
      clientId: process.env.TWITCH_CLIENT_ID!,
      clientSecret: process.env.TWITCH_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// United Effects
if (process.env.NEXT_PUBLIC_UNITED_EFFECTS_ENABLED === "true") {
  providers.push(
    UnitedEffectsProvider({
      clientId: process.env.UNITED_EFFECTS_CLIENT_ID!,
      clientSecret: process.env.UNITED_EFFECTS_CLIENT_SECRET!,
      issuer: process.env.UNITED_EFFECTS_ISSUER!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// VK
if (process.env.NEXT_PUBLIC_VK_ENABLED === "true") {
  providers.push(
    VkProvider({
      clientId: process.env.VK_CLIENT_ID!,
      clientSecret: process.env.VK_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Wikimedia
if (process.env.NEXT_PUBLIC_WIKIMEDIA_ENABLED === "true") {
  providers.push(
    WikimediaProvider({
      clientId: process.env.WIKIMEDIA_CLIENT_ID!,
      clientSecret: process.env.WIKIMEDIA_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Wordpress.com
if (process.env.NEXT_PUBLIC_WORDPRESS_ENABLED === "true") {
  providers.push(
    WordpressProvider({
      clientId: process.env.WORDPRESS_CLIENT_ID,
      clientSecret: process.env.WORDPRESS_CLIENT_SECRET,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Yandex
if (process.env.NEXT_PUBLIC_YANDEX_ENABLED === "true") {
  providers.push(
    YandexProvider({
      clientId: process.env.YANDEX_CLIENT_ID!,
      clientSecret: process.env.YANDEX_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Zitadel
if (process.env.NEXT_PUBLIC_ZITADEL_ENABLED === "true") {
  providers.push(
    ZitadelProvider({
      issuer: process.env.ZITADEL_ISSUER,
      clientId: process.env.ZITADEL_CLIENT_ID!,
      clientSecret: process.env.ZITADEL_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Zoho
if (process.env.NEXT_PUBLIC_ZOHO_ENABLED === "true") {
  providers.push(
    ZohoProvider({
      clientId: process.env.ZOHO_CLIENT_ID,
      clientSecret: process.env.ZOHO_CLIENT_SECRET,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

// Zoom
if (process.env.NEXT_PUBLIC_ZOOM_ENABLED_ENABLED === "true") {
  providers.push(
    ZoomProvider({
      clientId: process.env.ZOOM_CLIENT_ID!,
      clientSecret: process.env.ZOOM_CLIENT_SECRET!,
    })
  );

  const _linkAccount = adapter.linkAccount;
  adapter.linkAccount = (account) => {
    const { "not-before-policy": _, refresh_expires_in, ...data } = account;
    return _linkAccount ? _linkAccount(data) : undefined;
  };
}

export default async function auth(req: NextApiRequest, res: NextApiResponse) {
  return await NextAuth(req, res, {
    adapter: adapter as Adapter,
    session: {
      strategy: "jwt",
      maxAge: 30 * 24 * 60 * 60, // 30 days
    },
    providers,
    pages: {
      signIn: "/login",
      verifyRequest: "/confirmation",
    },
    callbacks: {
      async signIn({ user, account, profile, email, credentials }) {
        if (!(user as User).emailVerified && !email?.verificationRequest) {
          const parentSubscriptionId = (user as User).parentSubscriptionId;

          if (parentSubscriptionId) {
            // Add seat request to Stripe
            const parentSubscription = await prisma.subscription.findFirst({
              where: {
                id: parentSubscriptionId,
              },
            });

            // Count child users with verified email under a specific subscription, excluding the current user
            const verifiedChildUsersCount = await prisma.user.count({
              where: {
                parentSubscriptionId,
                id: {
                  not: user.id as number,
                },
                emailVerified: {
                  not: null,
                },
              },
            });

            if (
              STRIPE_SECRET_KEY &&
              parentSubscription?.quantity &&
              verifiedChildUsersCount + 2 > // add current user and the admin
                parentSubscription.quantity
            ) {
              // Add seat if the user count exceeds the subscription limit
              await updateSeats(
                parentSubscription.stripeSubscriptionId,
                verifiedChildUsersCount + 2
              );
            }
          }
        }

        if (account?.provider !== "credentials") {
          // registration via SSO can be separately disabled
          const existingUser = await prisma.account.findFirst({
            where: {
              providerAccountId: account?.providerAccountId,
            },
          });

          if (!existingUser && newSsoUsersDisabled) {
            return false;
          }

          // If user is already registered, link the provider
          if (user.email && account) {
            const findUser = await prisma.user.findFirst({
              where: {
                email: user.email,
              },
              include: {
                accounts: true,
              },
            });

            if (findUser && findUser.accounts.length === 0) {
              await prisma.account.create({
                data: {
                  userId: findUser.id,
                  type: account.type,
                  provider: account.provider,
                  providerAccountId: account.providerAccountId,
                  id_token: account.id_token,
                  access_token: account.access_token,
                  refresh_token: account.refresh_token,
                  expires_at: account.expires_at,
                  token_type: account.token_type,
                  scope: account.scope,
                  session_state: account.session_state,
                },
              });
            }
          }
        }

        return true;
      },
      async jwt({ token, trigger, user }) {
        token.sub = token.sub ? Number(token.sub) : undefined;
        if (trigger === "signIn" || trigger === "signUp")
          token.id = user?.id as number;

        if (trigger === "signUp") {
          const userExists = await prisma.user.findUnique({
            where: {
              id: token.id,
            },
            include: {
              accounts: true,
            },
          });

          // Verify SSO user email
          if (userExists && userExists.accounts.length > 0) {
            await prisma.user.update({
              where: {
                id: userExists.id,
              },
              data: {
                emailVerified: new Date(),
                dashboardSections: {
                  createMany: {
                    data: [
                      {
                        order: 0,
                        type: "STATS",
                      },
                      {
                        order: 1,
                        type: "RECENT_LINKS",
                      },
                      {
                        order: 2,
                        type: "PINNED_LINKS",
                      },
                    ],
                  },
                },
              },
            });
          }

          if (userExists && !userExists.username) {
            const autoGeneratedUsername =
              "user" + Math.round(Math.random() * 1000000000);

            await prisma.user.update({
              where: {
                id: token.id,
              },
              data: {
                username: autoGeneratedUsername,
              },
            });
          }
        } else if (trigger === "signIn") {
          const user = await prisma.user.findUnique({
            where: {
              id: token.id,
            },
          });

          if (user && !user.username) {
            const autoGeneratedUsername =
              "user" + Math.round(Math.random() * 1000000000);

            await prisma.user.update({
              where: { id: user.id },
              data: { username: autoGeneratedUsername },
            });
          }
        }

        return token;
      },
      async session({ session, token }) {
        session.user.id = token.id;

        if (STRIPE_SECRET_KEY) {
          const user = await prisma.user.findUnique({
            where: {
              id: token.id,
            },
            include: {
              subscriptions: true,
              parentSubscription: true,
            },
          });

          if (user) {
            //
            const subscribedUser = await verifySubscription(user);
          }
        }

        return session;
      },
    },
  });
}
