import {
  convertPlaceholdersToFieldInputs,
  extractPdfPlaceholders,
} from '@documenso/lib/server-only/pdf/auto-place-fields';
import { findRecipientByPlaceholder } from '@documenso/lib/server-only/pdf/helpers';
import { insertFormValuesInPdf } from '@documenso/lib/server-only/pdf/insert-form-values-in-pdf';
import { normalizePdf } from '@documenso/lib/server-only/pdf/normalize-pdf';
import { DOCUMENT_AUDIT_LOG_TYPE } from '@documenso/lib/types/document-audit-logs';
import type { ApiRequestMetadata } from '@documenso/lib/universal/extract-request-metadata';
import { prefixedId } from '@documenso/lib/universal/id';
import { putPdfFileServerSide } from '@documenso/lib/universal/upload/put-file.server';
import { createDocumentAuditLogData } from '@documenso/lib/utils/document-audit-logs';
import { prisma } from '@documenso/prisma';
import type { Envelope, EnvelopeItem, Recipient } from '@prisma/client';

type UnsafeCreateEnvelopeItemsOptions = {
  files: {
    clientId?: string;
    file: File;
    orderOverride?: number;
  }[];
  envelope: Envelope & {
    envelopeItems: EnvelopeItem[];
    recipients: Recipient[];
  };
  user: {
    id: number;
    name: string | null;
    email: string;
  };
  apiRequestMetadata: ApiRequestMetadata;
};

/**
 * Create envelope items.
 *
 * It is assumed all prior validation has been completed.
 */
export const UNSAFE_createEnvelopeItems = async ({
  files,
  envelope,
  user,
  apiRequestMetadata,
}: UnsafeCreateEnvelopeItemsOptions) => {
  const currentHighestOrderValue = envelope.envelopeItems[envelope.envelopeItems.length - 1]?.order ?? 1;

  // For each file: normalize, extract & clean placeholders, then upload.
  const envelopeItemsToCreate = await Promise.all(
    files.map(async ({ file, orderOverride, clientId }, index) => {
      let buffer = Buffer.from(await file.arrayBuffer());

      if (envelope.formValues) {
        buffer = await insertFormValuesInPdf({ pdf: buffer, formValues: envelope.formValues });
      }

      const normalized = await normalizePdf(buffer, {
        flattenForm: envelope.type !== 'TEMPLATE',
      });

      const { cleanedPdf, placeholders } = await extractPdfPlaceholders(normalized);

      const { documentData } = await putPdfFileServerSide({
        name: file.name,
        type: 'application/pdf',
        arrayBuffer: async () => Promise.resolve(cleanedPdf),
      });

      return {
        id: prefixedId('envelope_item'),
        title: file.name,
        clientId,
        documentDataId: documentData.id,
        placeholders,
        order: orderOverride ?? currentHighestOrderValue + index + 1,
      };
    }),
  );

  return await prisma.$transaction(async (tx) => {
    const createdItems = await tx.envelopeItem.createManyAndReturn({
      data: envelopeItemsToCreate.map((item) => ({
        id: item.id,
        envelopeId: envelope.id,
        title: item.title,
        documentDataId: item.documentDataId,
        order: item.order,
      })),
      include: {
        documentData: true,
      },
    });

    await tx.documentAuditLog.createMany({
      data: createdItems.map((item) =>
        createDocumentAuditLogData({
          type: DOCUMENT_AUDIT_LOG_TYPE.ENVELOPE_ITEM_CREATED,
          envelopeId: envelope.id,
          data: {
            envelopeItemId: item.id,
            envelopeItemTitle: item.title,
          },
          user: {
            name: user.name,
            email: user.email,
          },
          requestMetadata: apiRequestMetadata.requestMetadata,
        }),
      ),
    });

    // Create fields from placeholders if the envelope already has recipients.
    if (envelope.recipients.length > 0) {
      const orderedRecipients = [...envelope.recipients].sort((a, b) => {
        const aOrder = a.signingOrder ?? Number.MAX_SAFE_INTEGER;
        const bOrder = b.signingOrder ?? Number.MAX_SAFE_INTEGER;

        if (aOrder !== bOrder) {
          return aOrder - bOrder;
        }

        return a.id - b.id;
      });

      for (const uploadedItem of envelopeItemsToCreate) {
        if (!uploadedItem.placeholders || uploadedItem.placeholders.length === 0) {
          continue;
        }

        const createdItem = createdItems.find((ci) => ci.documentDataId === uploadedItem.documentDataId);

        if (!createdItem) {
          continue;
        }

        const fieldsToCreate = convertPlaceholdersToFieldInputs(
          uploadedItem.placeholders,
          (recipientPlaceholder, placeholder) =>
            findRecipientByPlaceholder(recipientPlaceholder, placeholder, orderedRecipients, orderedRecipients),
          createdItem.id,
        );

        if (fieldsToCreate.length > 0) {
          await tx.field.createMany({
            data: fieldsToCreate.map((field) => ({
              envelopeId: envelope.id,
              envelopeItemId: createdItem.id,
              recipientId: field.recipientId,
              type: field.type,
              page: field.page,
              positionX: field.positionX,
              positionY: field.positionY,
              width: field.width,
              height: field.height,
              customText: '',
              inserted: false,
              fieldMeta: field.fieldMeta || undefined,
            })),
          });
        }
      }
    }

    return createdItems.map((item) => {
      const clientId = envelopeItemsToCreate.find((file) => file.id === item.id)?.clientId;

      return {
        ...item,
        clientId,
      };
    });
  });
};
