import { getOrganisationAuthenticationPortalOptions } from '@documenso/auth/server/lib/utils/organisation-portal';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import {
  ORGANISATION_ACCOUNT_LINK_VERIFICATION_TOKEN_IDENTIFIER,
  ORGANISATION_USER_ACCOUNT_TYPE,
} from '@documenso/lib/constants/organisations';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { addUserToOrganisation } from '@documenso/lib/server-only/organisation/accept-organisation-invitation';
import { ZOrganisationAccountLinkMetadataSchema } from '@documenso/lib/types/organisation';
import type { RequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { prisma } from '@documenso/prisma';
import { UserSecurityAuditLogType } from '@prisma/client';

export interface LinkOrganisationAccountOptions {
  token: string;
  requestMeta: RequestMetadata;
}

export const linkOrganisationAccount = async ({ token, requestMeta }: LinkOrganisationAccountOptions) => {
  if (!IS_BILLING_ENABLED()) {
    throw new AppError(AppErrorCode.INVALID_REQUEST, {
      message: 'Billing is not enabled',
    });
  }

  // Delete the token since it contains unnecessary sensitive data.
  const verificationToken = await prisma.verificationToken.delete({
    where: {
      token,
      identifier: ORGANISATION_ACCOUNT_LINK_VERIFICATION_TOKEN_IDENTIFIER,
    },
    include: {
      user: {
        select: {
          id: true,
          emailVerified: true,
          accounts: {
            select: {
              provider: true,
              providerAccountId: true,
            },
          },
        },
      },
    },
  });

  if (!verificationToken) {
    throw new AppError(AppErrorCode.INVALID_REQUEST, {
      message: 'Verification token not found, used or expired',
    });
  }

  if (verificationToken.completed) {
    throw new AppError('ALREADY_USED');
  }

  if (verificationToken.expires < new Date()) {
    throw new AppError(AppErrorCode.INVALID_REQUEST, {
      message: 'Verification token not found, used or expired',
    });
  }

  const tokenMetadata = ZOrganisationAccountLinkMetadataSchema.safeParse(verificationToken.metadata);

  if (!tokenMetadata.success) {
    console.error('Invalid token metadata', tokenMetadata.error);

    throw new AppError(AppErrorCode.INVALID_REQUEST, {
      message: 'Verification token not found, used or expired',
    });
  }

  const user = verificationToken.user;

  const { clientOptions, organisation } = await getOrganisationAuthenticationPortalOptions({
    type: 'id',
    organisationId: tokenMetadata.data.organisationId,
  });

  const organisationMember = await prisma.organisationMember.findFirst({
    where: {
      userId: user.id,
      organisationId: tokenMetadata.data.organisationId,
    },
  });

  const oauthConfig = tokenMetadata.data.oauthConfig;

  const userAlreadyLinked = user.accounts.find(
    (account) => account.provider === clientOptions.id && account.providerAccountId === oauthConfig.providerAccountId,
  );

  if (organisationMember && userAlreadyLinked) {
    return;
  }

  // Link the user if not linked yet.
  if (!userAlreadyLinked) {
    await prisma.$transaction(async (tx) => {
      await tx.account.create({
        data: {
          type: ORGANISATION_USER_ACCOUNT_TYPE,
          provider: clientOptions.id,
          providerAccountId: oauthConfig.providerAccountId,
          access_token: oauthConfig.accessToken,
          expires_at: oauthConfig.expiresAt,
          token_type: 'Bearer',
          id_token: oauthConfig.idToken,
          userId: user.id,
        },
      });

      // Log link event.
      await tx.userSecurityAuditLog.create({
        data: {
          userId: user.id,
          ipAddress: requestMeta.ipAddress,
          userAgent: requestMeta.userAgent,
          type: UserSecurityAuditLogType.ORGANISATION_SSO_LINK,
        },
      });

      // If account already exists in an unverified state, remove the password to ensure
      // they cannot sign in using that method since we cannot confirm the password
      // was set by the user.
      if (!user.emailVerified) {
        await tx.user.update({
          where: {
            id: user.id,
          },
          data: {
            emailVerified: new Date(),
            password: null,
            // Todo: (RR7) Will need to update the "password" account after the migration.
          },
        });
      }
    });
  }

  // Only add the user to the organisation if they are not already a member.
  // Done outside the above transaction to avoid nested transactions and
  // holding connections during the job trigger network I/O.
  if (!organisationMember) {
    await addUserToOrganisation({
      userId: user.id,
      organisationId: tokenMetadata.data.organisationId,
      organisationGroups: organisation.groups,
      organisationMemberRole: organisation.organisationAuthenticationPortal.defaultOrganisationRole,
    });
  }
};
