import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { getEnvelopeWhereInput } from '@documenso/lib/server-only/envelope/get-envelope-by-id';
import { UNSAFE_updateEnvelopeItems } from '@documenso/lib/server-only/envelope-item/update-envelope-items';
import { getEnvelopeItemPermissions } from '@documenso/lib/utils/envelope';
import { prisma } from '@documenso/prisma';

import { authenticatedProcedure } from '../trpc';
import {
  updateEnvelopeItemsMeta,
  ZUpdateEnvelopeItemsRequestSchema,
  ZUpdateEnvelopeItemsResponseSchema,
} from './update-envelope-items.types';

export const updateEnvelopeItemsRoute = authenticatedProcedure
  .meta(updateEnvelopeItemsMeta)
  .input(ZUpdateEnvelopeItemsRequestSchema)
  .output(ZUpdateEnvelopeItemsResponseSchema)
  .mutation(async ({ input, ctx }) => {
    const { user, teamId } = ctx;
    const { envelopeId, data } = input;

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

    const { envelopeWhereInput } = await getEnvelopeWhereInput({
      id: {
        type: 'envelopeId',
        id: envelopeId,
      },
      type: null,
      userId: user.id,
      teamId,
    });

    const envelope = await prisma.envelope.findUnique({
      where: envelopeWhereInput,
      include: {
        recipients: true,
        envelopeItems: true,
      },
    });

    if (!envelope) {
      throw new AppError(AppErrorCode.NOT_FOUND, {
        message: 'Envelope not found',
      });
    }

    if (data.length === 0) {
      throw new AppError(AppErrorCode.INVALID_BODY, {
        message: 'Envelope items are required',
      });
    }

    const permissions = getEnvelopeItemPermissions(envelope, envelope.recipients);

    const hasOrderChange = data.some((item) => {
      if (item.order === undefined) {
        return false;
      }

      const existingItem = envelope.envelopeItems.find((e) => e.id === item.envelopeItemId);

      return !existingItem || existingItem.order !== item.order;
    });

    const hasTitleChange = data.some((item) => item.title !== undefined);

    if (!hasTitleChange && !hasOrderChange) {
      return {
        data: envelope.envelopeItems.map((item) => ({
          id: item.id,
          order: item.order,
          title: item.title,
          envelopeId: item.envelopeId,
        })),
      };
    }

    if (hasTitleChange && !permissions.canTitleBeChanged) {
      throw new AppError(AppErrorCode.INVALID_REQUEST, {
        message: 'Envelope item title is not editable',
      });
    }

    if (hasOrderChange && !permissions.canOrderBeChanged) {
      throw new AppError(AppErrorCode.INVALID_REQUEST, {
        message: 'Envelope item order is not editable',
      });
    }

    // Check that the items belong to the envelope.
    const itemsBelongToEnvelope = data.every((item) =>
      envelope.envelopeItems.some(({ id }) => item.envelopeItemId === id),
    );

    if (!itemsBelongToEnvelope) {
      throw new AppError(AppErrorCode.INVALID_REQUEST, {
        message: 'One or more envelope items to update do not exist',
      });
    }

    const updatedEnvelopeItems = await UNSAFE_updateEnvelopeItems({
      envelopeId,
      envelopeType: envelope.type,
      existingEnvelopeItems: envelope.envelopeItems,
      data,
      user: {
        name: user.name,
        email: user.email,
      },
      apiRequestMetadata: ctx.metadata,
    });

    return {
      data: updatedEnvelopeItems,
    };
  });
