import { prepareCscRecipientSigning } from '@documenso/ee/server-only/signing/csc/prepare-recipient-signing';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token';
import { rejectDocumentWithToken } from '@documenso/lib/server-only/document/reject-document-with-token';
import { createEnvelopeRecipients } from '@documenso/lib/server-only/recipient/create-envelope-recipients';
import { deleteEnvelopeRecipient } from '@documenso/lib/server-only/recipient/delete-envelope-recipient';
import { getRecipientById } from '@documenso/lib/server-only/recipient/get-recipient-by-id';
import { setDocumentRecipients } from '@documenso/lib/server-only/recipient/set-document-recipients';
import { setTemplateRecipients } from '@documenso/lib/server-only/recipient/set-template-recipients';
import { updateEnvelopeRecipients } from '@documenso/lib/server-only/recipient/update-envelope-recipients';
import { isTspEnvelope } from '@documenso/lib/types/signature-level';
import { unsafeBuildEnvelopeIdQuery } from '@documenso/lib/utils/envelope';
import { prisma } from '@documenso/prisma';
import { EnvelopeType } from '@prisma/client';
import { ZGenericSuccessResponse, ZSuccessResponseSchema } from '../schema';
import { authenticatedProcedure, procedure, router } from '../trpc';
import { findRecipientSuggestionsRoute } from './find-recipient-suggestions';
import {
  ZCompleteDocumentWithTokenMutationSchema,
  ZCompleteDocumentWithTokenResponseSchema,
  ZCreateDocumentRecipientRequestSchema,
  ZCreateDocumentRecipientResponseSchema,
  ZCreateDocumentRecipientsRequestSchema,
  ZCreateDocumentRecipientsResponseSchema,
  ZCreateTemplateRecipientRequestSchema,
  ZCreateTemplateRecipientResponseSchema,
  ZCreateTemplateRecipientsRequestSchema,
  ZCreateTemplateRecipientsResponseSchema,
  ZDeleteDocumentRecipientRequestSchema,
  ZDeleteTemplateRecipientRequestSchema,
  ZGetRecipientRequestSchema,
  ZGetRecipientResponseSchema,
  ZRejectDocumentWithTokenMutationSchema,
  ZSetDocumentRecipientsRequestSchema,
  ZSetDocumentRecipientsResponseSchema,
  ZSetTemplateRecipientsRequestSchema,
  ZSetTemplateRecipientsResponseSchema,
  ZUpdateDocumentRecipientRequestSchema,
  ZUpdateDocumentRecipientResponseSchema,
  ZUpdateDocumentRecipientsRequestSchema,
  ZUpdateDocumentRecipientsResponseSchema,
  ZUpdateTemplateRecipientRequestSchema,
  ZUpdateTemplateRecipientResponseSchema,
  ZUpdateTemplateRecipientsRequestSchema,
  ZUpdateTemplateRecipientsResponseSchema,
} from './schema';

export const recipientRouter = router({
  suggestions: {
    find: findRecipientSuggestionsRoute,
  },

  /**
   * @public
   */
  getDocumentRecipient: authenticatedProcedure
    .meta({
      openapi: {
        method: 'GET',
        path: '/document/recipient/{recipientId}',
        summary: 'Get document recipient',
        description:
          'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Returns a single recipient. If you want to retrieve all the recipients for a document, use the "Get Document" endpoint.',
        tags: ['Document Recipients'],
        deprecated: true,
      },
    })
    .input(ZGetRecipientRequestSchema)
    .output(ZGetRecipientResponseSchema)
    .query(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { recipientId } = input;

      ctx.logger.info({
        input: {
          recipientId,
        },
      });

      return await getRecipientById({
        userId: ctx.user.id,
        teamId,
        recipientId,
        type: EnvelopeType.DOCUMENT,
      });
    }),

  /**
   * @public
   */
  createDocumentRecipient: authenticatedProcedure
    .meta({
      openapi: {
        method: 'POST',
        path: '/document/recipient/create',
        summary: 'Create document recipient',
        description:
          'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create a single recipient for a document.',
        tags: ['Document Recipients'],
        deprecated: true,
      },
    })
    .input(ZCreateDocumentRecipientRequestSchema)
    .output(ZCreateDocumentRecipientResponseSchema)
    .mutation(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { documentId, recipient } = input;

      ctx.logger.info({
        input: {
          documentId,
        },
      });

      const createdRecipients = await createEnvelopeRecipients({
        userId: ctx.user.id,
        teamId,
        id: {
          type: 'documentId',
          id: documentId,
        },
        recipients: [recipient],
        requestMetadata: ctx.metadata,
      });

      return createdRecipients.recipients[0];
    }),

  /**
   * @public
   */
  createDocumentRecipients: authenticatedProcedure
    .meta({
      openapi: {
        method: 'POST',
        path: '/document/recipient/create-many',
        summary: 'Create document recipients',
        description:
          'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create multiple recipients for a document.',
        tags: ['Document Recipients'],
        deprecated: true,
      },
    })
    .input(ZCreateDocumentRecipientsRequestSchema)
    .output(ZCreateDocumentRecipientsResponseSchema)
    .mutation(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { documentId, recipients } = input;

      ctx.logger.info({
        input: {
          documentId,
        },
      });

      return await createEnvelopeRecipients({
        userId: ctx.user.id,
        teamId,
        id: {
          type: 'documentId',
          id: documentId,
        },
        recipients,
        requestMetadata: ctx.metadata,
      });
    }),

  /**
   * @public
   */
  updateDocumentRecipient: authenticatedProcedure
    .meta({
      openapi: {
        method: 'POST',
        path: '/document/recipient/update',
        summary: 'Update document recipient',
        description:
          'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Update a single recipient for a document.',
        tags: ['Document Recipients'],
        deprecated: true,
      },
    })
    .input(ZUpdateDocumentRecipientRequestSchema)
    .output(ZUpdateDocumentRecipientResponseSchema)
    .mutation(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { documentId, recipient } = input;

      ctx.logger.info({
        input: {
          documentId,
        },
      });

      const updatedRecipients = await updateEnvelopeRecipients({
        userId: ctx.user.id,
        teamId,
        id: {
          type: 'documentId',
          id: documentId,
        },
        recipients: [recipient],
        requestMetadata: ctx.metadata,
      });

      return updatedRecipients.recipients[0];
    }),

  /**
   * @public
   */
  updateDocumentRecipients: authenticatedProcedure
    .meta({
      openapi: {
        method: 'POST',
        path: '/document/recipient/update-many',
        summary: 'Update document recipients',
        description:
          'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Update multiple recipients for a document.',
        tags: ['Document Recipients'],
        deprecated: true,
      },
    })
    .input(ZUpdateDocumentRecipientsRequestSchema)
    .output(ZUpdateDocumentRecipientsResponseSchema)
    .mutation(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { documentId, recipients } = input;

      ctx.logger.info({
        input: {
          documentId,
        },
      });

      return await updateEnvelopeRecipients({
        userId: ctx.user.id,
        teamId,
        id: {
          type: 'documentId',
          id: documentId,
        },
        recipients,
        requestMetadata: ctx.metadata,
      });
    }),

  /**
   * @public
   */
  deleteDocumentRecipient: authenticatedProcedure
    .meta({
      openapi: {
        method: 'POST',
        path: '/document/recipient/delete',
        summary: 'Delete document recipient',
        description:
          'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
        tags: ['Document Recipients'],
        deprecated: true,
      },
    })
    .input(ZDeleteDocumentRecipientRequestSchema)
    .output(ZSuccessResponseSchema)
    .mutation(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { recipientId } = input;

      ctx.logger.info({
        input: {
          recipientId,
        },
      });

      await deleteEnvelopeRecipient({
        userId: ctx.user.id,
        teamId,
        recipientId,
        requestMetadata: ctx.metadata,
      });

      return ZGenericSuccessResponse;
    }),

  /**
   * @private
   */
  setDocumentRecipients: authenticatedProcedure
    .input(ZSetDocumentRecipientsRequestSchema)
    .output(ZSetDocumentRecipientsResponseSchema)
    .mutation(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { documentId, recipients } = input;

      ctx.logger.info({
        input: {
          documentId,
        },
      });

      return await setDocumentRecipients({
        userId: ctx.user.id,
        teamId,
        id: {
          type: 'documentId',
          id: documentId,
        },
        recipients: recipients.map((recipient) => ({
          id: recipient.id,
          email: recipient.email,
          name: recipient.name,
          role: recipient.role,
          signingOrder: recipient.signingOrder,
          actionAuth: recipient.actionAuth,
        })),
        requestMetadata: ctx.metadata,
      });
    }),

  /**
   * @public
   */
  getTemplateRecipient: authenticatedProcedure
    .meta({
      openapi: {
        method: 'GET',
        path: '/template/recipient/{recipientId}',
        summary: 'Get template recipient',
        description:
          'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Returns a single recipient. If you want to retrieve all the recipients for a template, use the "Get Template" endpoint.',
        tags: ['Template Recipients'],
        deprecated: true,
      },
    })
    .input(ZGetRecipientRequestSchema)
    .output(ZGetRecipientResponseSchema)
    .query(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { recipientId } = input;

      ctx.logger.info({
        input: {
          recipientId,
        },
      });

      return await getRecipientById({
        userId: ctx.user.id,
        teamId,
        recipientId,
        type: EnvelopeType.TEMPLATE,
      });
    }),

  /**
   * @public
   */
  createTemplateRecipient: authenticatedProcedure
    .meta({
      openapi: {
        method: 'POST',
        path: '/template/recipient/create',
        summary: 'Create template recipient',
        description:
          'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create a single recipient for a template.',
        tags: ['Template Recipients'],
        deprecated: true,
      },
    })
    .input(ZCreateTemplateRecipientRequestSchema)
    .output(ZCreateTemplateRecipientResponseSchema)
    .mutation(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { templateId, recipient } = input;

      ctx.logger.info({
        input: {
          templateId,
        },
      });

      const createdRecipients = await createEnvelopeRecipients({
        userId: ctx.user.id,
        teamId,
        id: {
          id: templateId,
          type: 'templateId',
        },
        recipients: [recipient],
        requestMetadata: ctx.metadata,
      });

      return createdRecipients.recipients[0];
    }),

  /**
   * @public
   */
  createTemplateRecipients: authenticatedProcedure
    .meta({
      openapi: {
        method: 'POST',
        path: '/template/recipient/create-many',
        summary: 'Create template recipients',
        description:
          'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Create multiple recipients for a template.',
        tags: ['Template Recipients'],
        deprecated: true,
      },
    })
    .input(ZCreateTemplateRecipientsRequestSchema)
    .output(ZCreateTemplateRecipientsResponseSchema)
    .mutation(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { templateId, recipients } = input;

      ctx.logger.info({
        input: {
          templateId,
        },
      });

      return await createEnvelopeRecipients({
        userId: ctx.user.id,
        teamId,
        id: {
          id: templateId,
          type: 'templateId',
        },
        recipients,
        requestMetadata: ctx.metadata,
      });
    }),

  /**
   * @public
   */
  updateTemplateRecipient: authenticatedProcedure
    .meta({
      openapi: {
        method: 'POST',
        path: '/template/recipient/update',
        summary: 'Update template recipient',
        description:
          'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Update a single recipient for a template.',
        tags: ['Template Recipients'],
        deprecated: true,
      },
    })
    .input(ZUpdateTemplateRecipientRequestSchema)
    .output(ZUpdateTemplateRecipientResponseSchema)
    .mutation(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { templateId, recipient } = input;

      ctx.logger.info({
        input: {
          templateId,
        },
      });

      const updatedRecipients = await updateEnvelopeRecipients({
        userId: ctx.user.id,
        teamId,
        id: {
          type: 'templateId',
          id: templateId,
        },
        recipients: [recipient],
        requestMetadata: ctx.metadata,
      });

      return updatedRecipients.recipients[0];
    }),

  /**
   * @public
   */
  updateTemplateRecipients: authenticatedProcedure
    .meta({
      openapi: {
        method: 'POST',
        path: '/template/recipient/update-many',
        summary: 'Update template recipients',
        description:
          'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide. Update multiple recipients for a template.',
        tags: ['Template Recipients'],
        deprecated: true,
      },
    })
    .input(ZUpdateTemplateRecipientsRequestSchema)
    .output(ZUpdateTemplateRecipientsResponseSchema)
    .mutation(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { templateId, recipients } = input;

      ctx.logger.info({
        input: {
          templateId,
        },
      });

      return await updateEnvelopeRecipients({
        userId: ctx.user.id,
        teamId,
        id: {
          type: 'templateId',
          id: templateId,
        },
        recipients,
        requestMetadata: ctx.metadata,
      });
    }),

  /**
   * @public
   */
  deleteTemplateRecipient: authenticatedProcedure
    .meta({
      openapi: {
        method: 'POST',
        path: '/template/recipient/delete',
        summary: 'Delete template recipient',
        description:
          'Deprecated: this endpoint is being replaced by the Envelope API. See https://docs.documenso.com/docs/developers/api/migrate-to-envelopes for the migration guide.',
        tags: ['Template Recipients'],
        deprecated: true,
      },
    })
    .input(ZDeleteTemplateRecipientRequestSchema)
    .output(ZSuccessResponseSchema)
    .mutation(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { recipientId } = input;

      ctx.logger.info({
        input: {
          recipientId,
        },
      });

      await deleteEnvelopeRecipient({
        recipientId,
        userId: ctx.user.id,
        teamId,
        requestMetadata: ctx.metadata,
      });

      return ZGenericSuccessResponse;
    }),

  /**
   * @private
   */
  setTemplateRecipients: authenticatedProcedure
    .input(ZSetTemplateRecipientsRequestSchema)
    .output(ZSetTemplateRecipientsResponseSchema)
    .mutation(async ({ input, ctx }) => {
      const { teamId } = ctx;
      const { templateId, recipients } = input;

      ctx.logger.info({
        input: {
          templateId,
        },
      });

      return await setTemplateRecipients({
        userId: ctx.user.id,
        teamId,
        id: {
          type: 'templateId',
          id: templateId,
        },
        recipients: recipients.map((recipient) => ({
          id: recipient.id,
          email: recipient.email,
          name: recipient.name,
          role: recipient.role,
          signingOrder: recipient.signingOrder,
          actionAuth: recipient.actionAuth,
        })),
      });
    }),

  /**
   * @private
   */
  completeDocumentWithToken: procedure
    .input(ZCompleteDocumentWithTokenMutationSchema)
    .output(ZCompleteDocumentWithTokenResponseSchema)
    .mutation(async ({ input, ctx }) => {
      try {
        const { token, documentId, accessAuthOptions, nextSigner, recipientOverride } = input;

        ctx.logger.info({
          input: {
            documentId,
          },
        });

        // Branch on TSP envelopes before any SES side effects: TSP recipients
        // can't complete via this route — they go through the CSC sync sign
        // flow (`enterprise.csc.signEnvelope`). This route returns the redirect URL
        // for the credential-scope OAuth round-trip.
        const envelope = await prisma.envelope.findFirst({
          where: {
            ...unsafeBuildEnvelopeIdQuery({ type: 'documentId', id: documentId }, EnvelopeType.DOCUMENT),
            recipients: { some: { token } },
          },
          select: { signatureLevel: true, internalVersion: true },
        });

        // The most common cause is a stale signing page: the document was
        // deleted, or the recipient was removed, after the link was opened.
        // Surface a NOT_FOUND instead of leaking a Prisma P2025 as a 500.
        if (!envelope) {
          throw new AppError(AppErrorCode.NOT_FOUND, {
            message: 'Document not found for the provided signing token',
            statusCode: 404,
          });
        }

        if (isTspEnvelope(envelope)) {
          return await prepareCscRecipientSigning({
            recipientToken: token,
            requestMetadata: ctx.metadata.requestMetadata,
          });
        }

        await completeDocumentWithToken({
          token,
          id: {
            type: 'documentId',
            id: documentId,
          },
          accessAuthOptions,
          nextSigner,
          recipientOverride,
          userId: ctx.user?.id,
          requestMetadata: ctx.metadata.requestMetadata,
        });

        return { status: 'SIGNED' as const };
      } catch (err) {
        // Resolve retried, stale or concurrent duplicate completion requests
        // idempotently so the client routes the user to the completed page
        // instead of surfacing an error for a document that is signed.
        if (err instanceof AppError && err.code === AppErrorCode.RECIPIENT_ALREADY_SIGNED) {
          ctx.logger.info({
            message: 'Recipient attempted to complete a document they have already signed',
          });

          return { status: 'ALREADY_SIGNED' as const };
        }

        // Log the error for debugging purposes.
        ctx.logger.error({
          message: 'Error completing document with token',
          error: err instanceof AppError ? `[${err.code}]: ${err.message}` : String(err),
        });

        // Raw console.log incase we're somehow dealing with a funky error object that doesn't serialize well.
        console.log('Error completing document with token', err);

        // Rethrow the error so that the client receives the appropriate error response.
        throw err;
      }
    }),

  /**
   * @private
   */
  rejectDocumentWithToken: procedure.input(ZRejectDocumentWithTokenMutationSchema).mutation(async ({ input, ctx }) => {
    const { token, documentId, reason } = input;

    ctx.logger.info({
      input: {
        documentId,
      },
    });

    return await rejectDocumentWithToken({
      token,
      id: {
        type: 'documentId',
        id: documentId,
      },
      reason,
      requestMetadata: ctx.metadata.requestMetadata,
    });
  }),
});
