import type { Workflow, Prisma } from "@prisma/client";
import {
  WorkflowTemplates,
  WorkflowActions,
  WorkflowTriggerEvents,
  BookingStatus,
  WorkflowMethods,
  TimeUnit,
  MembershipRole,
} from "@prisma/client";
import { z } from "zod";

import emailReminderTemplate from "@calcom/ee/workflows/lib/reminders/templates/emailReminderTemplate";
import {
  SMS_REMINDER_NUMBER_FIELD,
  getSmsReminderNumberField,
  getSmsReminderNumberSource,
} from "@calcom/features/bookings/lib/getBookingFields";
import type { WorkflowType } from "@calcom/features/ee/workflows/components/WorkflowListPage";
import { isSMSAction } from "@calcom/features/ee/workflows/lib/actionHelperFunctions";
import {
  WORKFLOW_TEMPLATES,
  WORKFLOW_TRIGGER_EVENTS,
  WORKFLOW_ACTIONS,
  TIME_UNIT,
} from "@calcom/features/ee/workflows/lib/constants";
import { getWorkflowActionOptions } from "@calcom/features/ee/workflows/lib/getOptions";
import {
  deleteScheduledEmailReminder,
  scheduleEmailReminder,
} from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager";
import {
  deleteScheduledSMSReminder,
  scheduleSMSReminder,
} from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager";
import {
  verifyPhoneNumber,
  sendVerificationCode,
} from "@calcom/features/ee/workflows/lib/reminders/verifyPhoneNumber";
import { upsertBookingField, removeBookingField } from "@calcom/features/eventtypes/lib/bookingFieldsManager";
import { IS_SELF_HOSTED, SENDER_ID, CAL_URL } from "@calcom/lib/constants";
import { SENDER_NAME } from "@calcom/lib/constants";
import hasKeyInMetadata from "@calcom/lib/hasKeyInMetadata";
// import { getErrorFromUnknown } from "@calcom/lib/errors";
import { getTranslation } from "@calcom/lib/server/i18n";
import type PrismaType from "@calcom/prisma";
import type { WorkflowStep } from "@calcom/prisma/client";

import { TRPCError } from "@trpc/server";

import { router, authedProcedure } from "../../trpc";
import { viewerTeamsRouter } from "./teams";

function getSender(
  step: Pick<WorkflowStep, "action" | "sender"> & { senderName: string | null | undefined }
) {
  return isSMSAction(step.action) ? step.sender || SENDER_ID : step.senderName || SENDER_NAME;
}

async function isAuthorized(
  workflow: Pick<Workflow, "id" | "teamId" | "userId"> | null,
  prisma: typeof PrismaType,
  currentUserId: number,
  readOnly?: boolean
) {
  if (!workflow) {
    return false;
  }

  if (!readOnly) {
    const userWorkflow = await prisma.workflow.findFirst({
      where: {
        id: workflow.id,
        OR: [
          { userId: currentUserId },
          {
            team: {
              members: {
                some: {
                  userId: currentUserId,
                  accepted: true,
                },
              },
            },
          },
        ],
      },
    });
    if (userWorkflow) return true;
  }

  const userWorkflow = await prisma.workflow.findFirst({
    where: {
      id: workflow.id,
      OR: [
        { userId: currentUserId },
        {
          team: {
            members: {
              some: {
                userId: currentUserId,
                accepted: true,
                NOT: {
                  role: MembershipRole.MEMBER,
                },
              },
            },
          },
        },
      ],
    },
  });

  if (userWorkflow) return true;

  return false;
}

export const workflowsRouter = router({
  list: authedProcedure
    .input(
      z
        .object({
          teamId: z.number().optional(),
          userId: z.number().optional(),
        })
        .optional()
    )
    .query(async ({ ctx, input }) => {
      if (input && input.teamId) {
        const workflows: WorkflowType[] = await ctx.prisma.workflow.findMany({
          where: {
            team: {
              id: input.teamId,
              members: {
                some: {
                  userId: ctx.user.id,
                  accepted: true,
                },
              },
            },
          },
          include: {
            team: {
              select: {
                id: true,
                slug: true,
                name: true,
                members: true,
              },
            },
            activeOn: {
              select: {
                eventType: {
                  select: {
                    id: true,
                    title: true,
                  },
                },
              },
            },
            steps: true,
          },
          orderBy: {
            id: "asc",
          },
        });
        const workflowsWithReadOnly = workflows.map((workflow) => {
          const readOnly = !!workflow.team?.members?.find(
            (member) => member.userId === ctx.user.id && member.role === MembershipRole.MEMBER
          );
          return { ...workflow, readOnly };
        });

        return { workflows: workflowsWithReadOnly };
      }

      if (input && input.userId) {
        const workflows: WorkflowType[] = await ctx.prisma.workflow.findMany({
          where: {
            userId: ctx.user.id,
          },
          include: {
            activeOn: {
              select: {
                eventType: {
                  select: {
                    id: true,
                    title: true,
                  },
                },
              },
            },
            steps: true,
            team: {
              select: {
                id: true,
                slug: true,
                name: true,
                members: true,
              },
            },
          },
          orderBy: {
            id: "asc",
          },
        });

        return { workflows };
      }

      const workflows = await ctx.prisma.workflow.findMany({
        where: {
          OR: [
            { userId: ctx.user.id },
            {
              team: {
                members: {
                  some: {
                    userId: ctx.user.id,
                    accepted: true,
                  },
                },
              },
            },
          ],
        },
        include: {
          activeOn: {
            select: {
              eventType: {
                select: {
                  id: true,
                  title: true,
                },
              },
            },
          },
          steps: true,
          team: {
            select: {
              id: true,
              slug: true,
              name: true,
              members: true,
            },
          },
        },
        orderBy: {
          id: "asc",
        },
      });

      const workflowsWithReadOnly: WorkflowType[] = workflows.map((workflow) => {
        const readOnly = !!workflow.team?.members?.find(
          (member) => member.userId === ctx.user.id && member.role === MembershipRole.MEMBER
        );

        return { readOnly, ...workflow };
      });

      return { workflows: workflowsWithReadOnly };
    }),
  get: authedProcedure
    .input(
      z.object({
        id: z.number(),
      })
    )
    .query(async ({ ctx, input }) => {
      const workflow = await ctx.prisma.workflow.findFirst({
        where: {
          id: input.id,
        },
        select: {
          id: true,
          name: true,
          userId: true,
          teamId: true,
          team: {
            select: {
              id: true,
              slug: true,
              members: true,
            },
          },
          time: true,
          timeUnit: true,
          activeOn: {
            select: {
              eventType: true,
            },
          },
          trigger: true,
          steps: {
            orderBy: {
              stepNumber: "asc",
            },
          },
        },
      });

      const isUserAuthorized = await isAuthorized(workflow, ctx.prisma, ctx.user.id);

      if (!isUserAuthorized) {
        throw new TRPCError({
          code: "UNAUTHORIZED",
        });
      }

      return workflow;
    }),
  create: authedProcedure
    .input(
      z.object({
        teamId: z.number().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { teamId } = input;

      const userId = ctx.user.id;

      if (teamId) {
        const team = await ctx.prisma.team.findFirst({
          where: {
            id: teamId,
            members: {
              some: {
                userId: ctx.user.id,
                accepted: true,
                NOT: {
                  role: MembershipRole.MEMBER,
                },
              },
            },
          },
        });

        if (!team) {
          throw new TRPCError({
            code: "UNAUTHORIZED",
          });
        }
      }

      try {
        const workflow: Workflow = await ctx.prisma.workflow.create({
          data: {
            name: "",
            trigger: WorkflowTriggerEvents.BEFORE_EVENT,
            time: 24,
            timeUnit: TimeUnit.HOUR,
            userId,
            teamId,
          },
        });

        await ctx.prisma.workflowStep.create({
          data: {
            stepNumber: 1,
            action: WorkflowActions.EMAIL_ATTENDEE,
            template: WorkflowTemplates.REMINDER,
            reminderBody: emailReminderTemplate(true, WorkflowActions.EMAIL_ATTENDEE).emailBody,
            emailSubject: emailReminderTemplate(true, WorkflowActions.EMAIL_ATTENDEE).emailSubject,
            workflowId: workflow.id,
            sender: SENDER_NAME,
            numberVerificationPending: false,
          },
        });
        return { workflow };
      } catch (e) {
        throw e;
      }
    }),
  delete: authedProcedure
    .input(
      z.object({
        id: z.number(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { id } = input;

      const workflowToDelete = await ctx.prisma.workflow.findFirst({
        where: {
          id,
        },
        include: {
          activeOn: true,
        },
      });

      const isUserAuthorized = await isAuthorized(workflowToDelete, ctx.prisma, ctx.user.id, true);

      if (!isUserAuthorized || !workflowToDelete) {
        throw new TRPCError({ code: "UNAUTHORIZED" });
      }

      const scheduledReminders = await ctx.prisma.workflowReminder.findMany({
        where: {
          workflowStep: {
            workflowId: id,
          },
          scheduled: true,
          NOT: {
            referenceId: null,
          },
        },
      });

      //cancel workflow reminders of deleted workflow
      scheduledReminders.forEach((reminder) => {
        if (reminder.method === WorkflowMethods.EMAIL) {
          deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
        } else if (reminder.method === WorkflowMethods.SMS) {
          deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
        }
      });

      for (const activeOn of workflowToDelete.activeOn) {
        await removeSmsReminderFieldForBooking({ workflowId: id, eventTypeId: activeOn.eventTypeId });
      }

      await ctx.prisma.workflow.deleteMany({
        where: {
          id,
        },
      });

      return {
        id,
      };
    }),
  update: authedProcedure
    .input(
      z.object({
        id: z.number(),
        name: z.string(),
        activeOn: z.number().array(),
        steps: z
          .object({
            id: z.number(),
            stepNumber: z.number(),
            action: z.enum(WORKFLOW_ACTIONS),
            workflowId: z.number(),
            sendTo: z.string().optional().nullable(),
            reminderBody: z.string().optional().nullable(),
            emailSubject: z.string().optional().nullable(),
            template: z.enum(WORKFLOW_TEMPLATES),
            numberRequired: z.boolean().nullable(),
            sender: z.string().optional().nullable(),
            senderName: z.string().optional().nullable(),
          })
          .array(),
        trigger: z.enum(WORKFLOW_TRIGGER_EVENTS),
        time: z.number().nullable(),
        timeUnit: z.enum(TIME_UNIT).nullable(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { user } = ctx;
      const { id, name, activeOn, steps, trigger, time, timeUnit } = input;

      const userWorkflow = await ctx.prisma.workflow.findUnique({
        where: {
          id,
        },
        select: {
          id: true,
          userId: true,
          teamId: true,
          user: {
            select: {
              teams: true,
            },
          },
          steps: true,
          activeOn: true,
        },
      });

      const isUserAuthorized = await isAuthorized(userWorkflow, ctx.prisma, ctx.user.id, true);

      if (!isUserAuthorized || !userWorkflow) {
        throw new TRPCError({ code: "UNAUTHORIZED" });
      }

      if (steps.find((step) => step.workflowId != id)) {
        throw new TRPCError({ code: "UNAUTHORIZED" });
      }

      const oldActiveOnEventTypes = await ctx.prisma.workflowsOnEventTypes.findMany({
        where: {
          workflowId: id,
        },
        select: {
          eventTypeId: true,
        },
      });

      const newActiveEventTypes = activeOn.filter((eventType) => {
        if (
          !oldActiveOnEventTypes ||
          !oldActiveOnEventTypes
            .map((oldEventType) => {
              return oldEventType.eventTypeId;
            })
            .includes(eventType)
        ) {
          return eventType;
        }
      });

      //check if new event types belong to user or team
      for (const newEventTypeId of newActiveEventTypes) {
        const newEventType = await ctx.prisma.eventType.findFirst({
          where: {
            id: newEventTypeId,
          },
          include: {
            users: true,
            team: {
              include: {
                members: true,
              },
            },
          },
        });

        if (newEventType) {
          if (userWorkflow.teamId && userWorkflow.teamId !== newEventType.teamId) {
            throw new TRPCError({ code: "UNAUTHORIZED" });
          }

          if (
            !userWorkflow.teamId &&
            userWorkflow.userId &&
            newEventType.userId !== userWorkflow.userId &&
            !newEventType?.users.find((eventTypeUser) => eventTypeUser.id === userWorkflow.userId)
          ) {
            throw new TRPCError({ code: "UNAUTHORIZED" });
          }
        }
      }

      //remove all scheduled Email and SMS reminders for eventTypes that are not active any more
      const removedEventTypes = oldActiveOnEventTypes
        .map((eventType) => {
          return eventType.eventTypeId;
        })
        .filter((eventType) => {
          if (!activeOn.includes(eventType)) {
            return eventType;
          }
        });

      const remindersToDeletePromise: Prisma.PrismaPromise<
        {
          id: number;
          referenceId: string | null;
          method: string;
          scheduled: boolean;
        }[]
      >[] = [];

      removedEventTypes.forEach((eventTypeId) => {
        const reminderToDelete = ctx.prisma.workflowReminder.findMany({
          where: {
            booking: {
              eventTypeId: eventTypeId,
              userId: ctx.user.id,
            },
            workflowStepId: {
              in: userWorkflow.steps.map((step) => {
                return step.id;
              }),
            },
          },
          select: {
            id: true,
            referenceId: true,
            method: true,
            scheduled: true,
          },
        });

        remindersToDeletePromise.push(reminderToDelete);
      });

      const remindersToDelete = await Promise.all(remindersToDeletePromise);

      //cancel workflow reminders for all bookings from event types that got disabled
      remindersToDelete.flat().forEach((reminder) => {
        if (reminder.method === WorkflowMethods.EMAIL) {
          deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
        } else if (reminder.method === WorkflowMethods.SMS) {
          deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
        }
      });

      //update active on & reminders for new eventTypes
      await ctx.prisma.workflowsOnEventTypes.deleteMany({
        where: {
          workflowId: id,
        },
      });

      let newEventTypes: number[] = [];
      if (activeOn.length) {
        if (trigger === WorkflowTriggerEvents.BEFORE_EVENT || trigger === WorkflowTriggerEvents.AFTER_EVENT) {
          newEventTypes = newActiveEventTypes;
        }
        if (newEventTypes.length > 0) {
          //create reminders for all bookings with newEventTypes
          const bookingsForReminders = await ctx.prisma.booking.findMany({
            where: {
              eventTypeId: { in: newEventTypes },
              status: BookingStatus.ACCEPTED,
              startTime: {
                gte: new Date(),
              },
            },
            include: {
              attendees: true,
              eventType: true,
              user: true,
            },
          });

          steps.forEach(async (step) => {
            if (step.action !== WorkflowActions.SMS_ATTENDEE) {
              //as we do not have attendees phone number (user is notified about that when setting this action)
              bookingsForReminders.forEach(async (booking) => {
                const bookingInfo = {
                  uid: booking.uid,
                  attendees: booking.attendees.map((attendee) => {
                    return {
                      name: attendee.name,
                      email: attendee.email,
                      timeZone: attendee.timeZone,
                      language: { locale: attendee.locale || "" },
                    };
                  }),
                  organizer: booking.user
                    ? {
                        language: { locale: booking.user.locale || "" },
                        name: booking.user.name || "",
                        email: booking.user.email,
                        timeZone: booking.user.timeZone,
                      }
                    : { name: "", email: "", timeZone: "", language: { locale: "" } },
                  startTime: booking.startTime.toISOString(),
                  endTime: booking.endTime.toISOString(),
                  title: booking.title,
                  language: { locale: booking?.user?.locale || "" },
                  eventType: {
                    slug: booking.eventType?.slug,
                  },
                };
                if (
                  step.action === WorkflowActions.EMAIL_HOST ||
                  step.action === WorkflowActions.EMAIL_ATTENDEE /*||
                  step.action === WorkflowActions.EMAIL_ADDRESS*/
                ) {
                  let sendTo = "";

                  switch (step.action) {
                    case WorkflowActions.EMAIL_HOST:
                      sendTo = bookingInfo.organizer?.email;
                      break;
                    case WorkflowActions.EMAIL_ATTENDEE:
                      sendTo = bookingInfo.attendees[0].email;
                      break;
                    /*case WorkflowActions.EMAIL_ADDRESS:
                      sendTo = step.sendTo || "";*/
                  }

                  await scheduleEmailReminder(
                    bookingInfo,
                    trigger,
                    step.action,
                    {
                      time,
                      timeUnit,
                    },
                    sendTo,
                    step.emailSubject || "",
                    step.reminderBody || "",
                    step.id,
                    step.template,
                    step.senderName || SENDER_NAME
                  );
                } else if (step.action === WorkflowActions.SMS_NUMBER) {
                  await scheduleSMSReminder(
                    bookingInfo,
                    step.sendTo || "",
                    trigger,
                    step.action,
                    {
                      time,
                      timeUnit,
                    },
                    step.reminderBody || "",
                    step.id,
                    step.template,
                    step.sender || SENDER_ID,
                    user.id,
                    userWorkflow.teamId
                  );
                }
              });
            }
          });
        }
        //create all workflow - eventtypes relationships
        activeOn.forEach(async (eventTypeId) => {
          await ctx.prisma.workflowsOnEventTypes.createMany({
            data: {
              workflowId: id,
              eventTypeId,
            },
          });
        });
      }

      userWorkflow.steps.map(async (oldStep) => {
        const newStep = steps.filter((s) => s.id === oldStep.id)[0];
        const remindersFromStep = await ctx.prisma.workflowReminder.findMany({
          where: {
            workflowStepId: oldStep.id,
          },
          include: {
            booking: true,
          },
        });

        //step was deleted
        if (!newStep) {
          // cancel all workflow reminders from deleted steps
          if (remindersFromStep.length > 0) {
            remindersFromStep.forEach((reminder) => {
              if (reminder.method === WorkflowMethods.EMAIL) {
                deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
              } else if (reminder.method === WorkflowMethods.SMS) {
                deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
              }
            });
          }
          await ctx.prisma.workflowStep.delete({
            where: {
              id: oldStep.id,
            },
          });

          //step was edited
        } else if (JSON.stringify(oldStep) !== JSON.stringify(newStep)) {
          if (
            !userWorkflow.teamId &&
            !userWorkflow.user?.teams.length &&
            !isSMSAction(oldStep.action) &&
            isSMSAction(newStep.action)
          ) {
            throw new TRPCError({ code: "UNAUTHORIZED" });
          }
          await ctx.prisma.workflowStep.update({
            where: {
              id: oldStep.id,
            },
            data: {
              action: newStep.action,
              sendTo:
                newStep.action === WorkflowActions.SMS_NUMBER /*||
                newStep.action === WorkflowActions.EMAIL_ADDRESS*/
                  ? newStep.sendTo
                  : null,
              stepNumber: newStep.stepNumber,
              workflowId: newStep.workflowId,
              reminderBody: newStep.reminderBody,
              emailSubject: newStep.emailSubject,
              template: newStep.template,
              numberRequired: newStep.numberRequired,
              sender: getSender({
                action: newStep.action,
                sender: newStep.sender || null,
                senderName: newStep.senderName,
              }),
              numberVerificationPending: false,
            },
          });
          //cancel all reminders of step and create new ones (not for newEventTypes)
          const remindersToUpdate = remindersFromStep.filter((reminder) => {
            if (reminder.booking?.eventTypeId && !newEventTypes.includes(reminder.booking?.eventTypeId)) {
              return reminder;
            }
          });

          //cancel all workflow reminders from steps that were edited
          remindersToUpdate.forEach(async (reminder) => {
            if (reminder.method === WorkflowMethods.EMAIL) {
              deleteScheduledEmailReminder(reminder.id, reminder.referenceId);
            } else if (reminder.method === WorkflowMethods.SMS) {
              deleteScheduledSMSReminder(reminder.id, reminder.referenceId);
            }
          });
          const eventTypesToUpdateReminders = activeOn.filter((eventTypeId) => {
            if (!newEventTypes.includes(eventTypeId)) {
              return eventTypeId;
            }
          });
          if (
            eventTypesToUpdateReminders &&
            (trigger === WorkflowTriggerEvents.BEFORE_EVENT || trigger === WorkflowTriggerEvents.AFTER_EVENT)
          ) {
            const bookingsOfEventTypes = await ctx.prisma.booking.findMany({
              where: {
                eventTypeId: {
                  in: eventTypesToUpdateReminders,
                },
                status: BookingStatus.ACCEPTED,
                startTime: {
                  gte: new Date(),
                },
              },
              include: {
                attendees: true,
                eventType: true,
                user: true,
              },
            });
            bookingsOfEventTypes.forEach(async (booking) => {
              const bookingInfo = {
                uid: booking.uid,
                attendees: booking.attendees.map((attendee) => {
                  return {
                    name: attendee.name,
                    email: attendee.email,
                    timeZone: attendee.timeZone,
                    language: { locale: attendee.locale || "" },
                  };
                }),
                organizer: booking.user
                  ? {
                      language: { locale: booking.user.locale || "" },
                      name: booking.user.name || "",
                      email: booking.user.email,
                      timeZone: booking.user.timeZone,
                    }
                  : { name: "", email: "", timeZone: "", language: { locale: "" } },
                startTime: booking.startTime.toISOString(),
                endTime: booking.endTime.toISOString(),
                title: booking.title,
                language: { locale: booking?.user?.locale || "" },
                eventType: {
                  slug: booking.eventType?.slug,
                },
              };
              if (
                newStep.action === WorkflowActions.EMAIL_HOST ||
                newStep.action === WorkflowActions.EMAIL_ATTENDEE /*||
                newStep.action === WorkflowActions.EMAIL_ADDRESS*/
              ) {
                let sendTo = "";

                switch (newStep.action) {
                  case WorkflowActions.EMAIL_HOST:
                    sendTo = bookingInfo.organizer?.email;
                    break;
                  case WorkflowActions.EMAIL_ATTENDEE:
                    sendTo = bookingInfo.attendees[0].email;
                    break;
                  /*case WorkflowActions.EMAIL_ADDRESS:
                    sendTo = newStep.sendTo || "";*/
                }

                await scheduleEmailReminder(
                  bookingInfo,
                  trigger,
                  newStep.action,
                  {
                    time,
                    timeUnit,
                  },
                  sendTo,
                  newStep.emailSubject || "",
                  newStep.reminderBody || "",
                  newStep.id,
                  newStep.template,
                  newStep.senderName || SENDER_NAME
                );
              } else if (newStep.action === WorkflowActions.SMS_NUMBER) {
                await scheduleSMSReminder(
                  bookingInfo,
                  newStep.sendTo || "",
                  trigger,
                  newStep.action,
                  {
                    time,
                    timeUnit,
                  },
                  newStep.reminderBody || "",
                  newStep.id || 0,
                  newStep.template,
                  newStep.sender || SENDER_ID,
                  user.id,
                  userWorkflow.teamId
                );
              }
            });
          }
        }
      });
      //added steps
      const addedSteps = steps.map((s) => {
        if (s.id <= 0) {
          if (!userWorkflow.user?.teams.length && isSMSAction(s.action)) {
            throw new TRPCError({ code: "UNAUTHORIZED" });
          }
          const { id: _stepId, ...stepToAdd } = s;
          return stepToAdd;
        }
      });

      if (addedSteps) {
        const eventTypesToCreateReminders = activeOn.map((activeEventType) => {
          if (activeEventType && !newEventTypes.includes(activeEventType)) {
            return activeEventType;
          }
        });
        addedSteps.forEach(async (step) => {
          if (step) {
            const { senderName, ...newStep } = step;
            newStep.sender = getSender({
              action: newStep.action,
              sender: newStep.sender || null,
              senderName: senderName,
            });
            const createdStep = await ctx.prisma.workflowStep.create({
              data: { ...newStep, numberVerificationPending: false },
            });
            if (
              (trigger === WorkflowTriggerEvents.BEFORE_EVENT ||
                trigger === WorkflowTriggerEvents.AFTER_EVENT) &&
              eventTypesToCreateReminders &&
              step.action !== WorkflowActions.SMS_ATTENDEE
            ) {
              const bookingsForReminders = await ctx.prisma.booking.findMany({
                where: {
                  eventTypeId: { in: eventTypesToCreateReminders as number[] },
                  status: BookingStatus.ACCEPTED,
                  startTime: {
                    gte: new Date(),
                  },
                },
                include: {
                  attendees: true,
                  eventType: true,
                  user: true,
                },
              });
              bookingsForReminders.forEach(async (booking) => {
                const bookingInfo = {
                  uid: booking.uid,
                  attendees: booking.attendees.map((attendee) => {
                    return {
                      name: attendee.name,
                      email: attendee.email,
                      timeZone: attendee.timeZone,
                      language: { locale: attendee.locale || "" },
                    };
                  }),
                  organizer: booking.user
                    ? {
                        name: booking.user.name || "",
                        email: booking.user.email,
                        timeZone: booking.user.timeZone,
                        language: { locale: booking.user.locale || "" },
                      }
                    : { name: "", email: "", timeZone: "", language: { locale: "" } },
                  startTime: booking.startTime.toISOString(),
                  endTime: booking.endTime.toISOString(),
                  title: booking.title,
                  language: { locale: booking?.user?.locale || "" },
                  eventType: {
                    slug: booking.eventType?.slug,
                  },
                };

                if (
                  step.action === WorkflowActions.EMAIL_ATTENDEE ||
                  step.action === WorkflowActions.EMAIL_HOST /*||
                  step.action === WorkflowActions.EMAIL_ADDRESS*/
                ) {
                  let sendTo = "";

                  switch (step.action) {
                    case WorkflowActions.EMAIL_HOST:
                      sendTo = bookingInfo.organizer?.email;
                      break;
                    case WorkflowActions.EMAIL_ATTENDEE:
                      sendTo = bookingInfo.attendees[0].email;
                      break;
                    /*case WorkflowActions.EMAIL_ADDRESS:
                      sendTo = step.sendTo || "";*/
                  }

                  await scheduleEmailReminder(
                    bookingInfo,
                    trigger,
                    step.action,
                    {
                      time,
                      timeUnit,
                    },
                    sendTo,
                    step.emailSubject || "",
                    step.reminderBody || "",
                    createdStep.id,
                    step.template,
                    step.senderName || SENDER_NAME
                  );
                } else if (step.action === WorkflowActions.SMS_NUMBER && step.sendTo) {
                  await scheduleSMSReminder(
                    bookingInfo,
                    step.sendTo,
                    trigger,
                    step.action,
                    {
                      time,
                      timeUnit,
                    },
                    step.reminderBody || "",
                    createdStep.id,
                    step.template,
                    step.sender || SENDER_ID,
                    user.id,
                    userWorkflow.teamId
                  );
                }
              });
            }
          }
        });
      }

      //update trigger, name, time, timeUnit
      await ctx.prisma.workflow.update({
        where: {
          id,
        },
        data: {
          name,
          trigger,
          time,
          timeUnit,
        },
      });

      const workflow = await ctx.prisma.workflow.findFirst({
        where: {
          id,
        },
        include: {
          activeOn: {
            select: {
              eventType: true,
            },
          },
          team: {
            select: {
              id: true,
              slug: true,
              members: true,
            },
          },
          steps: {
            orderBy: {
              stepNumber: "asc",
            },
          },
        },
      });

      // Remove or add booking field for sms reminder number
      const smsReminderNumberNeeded =
        activeOn.length && steps.some((step) => step.action === WorkflowActions.SMS_ATTENDEE);

      for (const removedEventType of removedEventTypes) {
        await removeSmsReminderFieldForBooking({
          workflowId: id,
          eventTypeId: removedEventType,
        });
      }

      for (const eventTypeId of activeOn) {
        if (smsReminderNumberNeeded) {
          await upsertSmsReminderFieldForBooking({
            workflowId: id,
            isSmsReminderNumberRequired: steps.some(
              (s) => s.action === WorkflowActions.SMS_ATTENDEE && s.numberRequired
            ),
            eventTypeId,
          });
        } else {
          await removeSmsReminderFieldForBooking({ workflowId: id, eventTypeId });
        }
      }

      return {
        workflow,
      };
    }),
  /* testAction: authedRateLimitedProcedure({ intervalInMs: 10000, limit: 3 })
    .input(
      z.object({
        step: z.object({
          id: z.number(),
          stepNumber: z.number(),
          action: z.enum(WORKFLOW_ACTIONS),
          workflowId: z.number(),
          sendTo: z.string().optional().nullable(),
          reminderBody: z.string().optional().nullable(),
          emailSubject: z.string().optional().nullable(),
          template: z.enum(WORKFLOW_TEMPLATES),
          numberRequired: z.boolean().nullable(),
          sender: z.string().optional().nullable(),
        }),
        emailSubject: z.string(),
        reminderBody: z.string(),
      })
    )
  .mutation(async ({ ctx, input }) => {
  const { user } = ctx;
  const { step, emailSubject, reminderBody } = input;
  const { action, template, sendTo, sender } = step;

  const senderID = sender || SENDER_ID;

  if (action === WorkflowActions.SMS_NUMBER) {
    if (!sendTo) throw new TRPCError({ code: "BAD_REQUEST", message: "Missing sendTo" });
    const verifiedNumbers = await ctx.prisma.verifiedNumber.findFirst({
      where: {
        userId: ctx.user.id,
        phoneNumber: sendTo,
      },
    });
    if (!verifiedNumbers)
      throw new TRPCError({ code: "UNAUTHORIZED", message: "Phone number is not verified" });
  }

  try {
    const userWorkflow = await ctx.prisma.workflow.findUnique({
      where: {
        id: step.workflowId,
      },
      select: {
        userId: true,
        steps: true,
      },
    });

    if (!userWorkflow || userWorkflow.userId !== user.id) {
      throw new TRPCError({ code: "UNAUTHORIZED" });
    }

    if (isSMSAction(step.action) /*|| step.action === WorkflowActions.EMAIL_ADDRESS*/ /*) {
const hasTeamPlan = (await ctx.prisma.membership.count({ where: { userId: user.id } })) > 0;
if (!hasTeamPlan) {
throw new TRPCError({ code: "UNAUTHORIZED", message: "Team plan needed" });
}
}

const booking = await ctx.prisma.booking.findFirst({
orderBy: {
createdAt: "desc",
},
where: {
userId: ctx.user.id,
},
include: {
attendees: true,
user: true,
},
});

let evt: BookingInfo;
if (booking) {
evt = {
uid: booking?.uid,
attendees:
booking?.attendees.map((attendee) => {
return { name: attendee.name, email: attendee.email, timeZone: attendee.timeZone };
}) || [],
organizer: {
language: {
locale: booking?.user?.locale || "",
},
name: booking?.user?.name || "",
email: booking?.user?.email || "",
timeZone: booking?.user?.timeZone || "",
},
startTime: booking?.startTime.toISOString() || "",
endTime: booking?.endTime.toISOString() || "",
title: booking?.title || "",
location: booking?.location || null,
additionalNotes: booking?.description || null,
customInputs: booking?.customInputs,
};
} else {
//if no booking exists create an example booking
evt = {
attendees: [{ name: "John Doe", email: "john.doe@example.com", timeZone: "Europe/London" }],
organizer: {
language: {
locale: ctx.user.locale,
},
name: ctx.user.name || "",
email: ctx.user.email,
timeZone: ctx.user.timeZone,
},
startTime: dayjs().add(10, "hour").toISOString(),
endTime: dayjs().add(11, "hour").toISOString(),
title: "Example Booking",
location: "Office",
additionalNotes: "These are additional notes",
};
}

if (
action === WorkflowActions.EMAIL_ATTENDEE ||
action === WorkflowActions.EMAIL_HOST /*||
action === WorkflowActions.EMAIL_ADDRESS*/
  /*) {
      scheduleEmailReminder(
        evt,
        WorkflowTriggerEvents.NEW_EVENT,
        action,
        { time: null, timeUnit: null },
        ctx.user.email,
        emailSubject,
        reminderBody,
        0,
        template
      );
      return { message: "Notification sent" };
    } else if (action === WorkflowActions.SMS_NUMBER && sendTo) {
      scheduleSMSReminder(
        evt,
        sendTo,
        WorkflowTriggerEvents.NEW_EVENT,
        action,
        { time: null, timeUnit: null },
        reminderBody,
        0,
        template,
        senderID,
        ctx.user.id
      );
      return { message: "Notification sent" };
    }
    return {
      ok: false,
      status: 500,
      message: "Notification could not be sent",
    };
  } catch (_err) {
    const error = getErrorFromUnknown(_err);
    return {
      ok: false,
      status: 500,
      message: error.message,
    };
  }
  }),
  */
  activateEventType: authedProcedure
    .input(
      z.object({
        eventTypeId: z.number(),
        workflowId: z.number(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { eventTypeId, workflowId } = input;

      // Check that vent type belong to the user or team
      const userEventType = await ctx.prisma.eventType.findFirst({
        where: {
          id: eventTypeId,
          OR: [
            { userId: ctx.user.id },
            {
              team: {
                members: {
                  some: {
                    userId: ctx.user.id,
                    accepted: true,
                    NOT: {
                      role: MembershipRole.MEMBER,
                    },
                  },
                },
              },
            },
          ],
        },
      });

      if (!userEventType)
        throw new TRPCError({ code: "UNAUTHORIZED", message: "Not authorized to edit this event type" });

      // Check that the workflow belongs to the user or team
      const eventTypeWorkflow = await ctx.prisma.workflow.findFirst({
        where: {
          id: workflowId,
          OR: [
            {
              userId: ctx.user.id,
            },
            {
              teamId: userEventType.teamId,
            },
          ],
        },
        include: {
          steps: true,
        },
      });

      if (!eventTypeWorkflow)
        throw new TRPCError({
          code: "UNAUTHORIZED",
          message: "Not authorized to enable/disable this workflow",
        });

      //check if event type is already active
      const isActive = await ctx.prisma.workflowsOnEventTypes.findFirst({
        where: {
          workflowId,
          eventTypeId,
        },
      });

      if (isActive) {
        await ctx.prisma.workflowsOnEventTypes.deleteMany({
          where: {
            workflowId,
            eventTypeId,
          },
        });

        await removeSmsReminderFieldForBooking({
          workflowId,
          eventTypeId,
        });
      } else {
        await ctx.prisma.workflowsOnEventTypes.create({
          data: {
            workflowId,
            eventTypeId,
          },
        });

        if (
          eventTypeWorkflow.steps.some((step) => {
            return step.action === WorkflowActions.SMS_ATTENDEE;
          })
        ) {
          const isSmsReminderNumberRequired = eventTypeWorkflow.steps.some((step) => {
            return step.action === WorkflowActions.SMS_ATTENDEE && step.numberRequired;
          });
          await upsertSmsReminderFieldForBooking({
            workflowId,
            isSmsReminderNumberRequired,
            eventTypeId,
          });
        }
      }
    }),
  sendVerificationCode: authedProcedure
    .input(
      z.object({
        phoneNumber: z.string(),
      })
    )
    .mutation(async ({ input }) => {
      const { phoneNumber } = input;
      return sendVerificationCode(phoneNumber);
    }),
  verifyPhoneNumber: authedProcedure
    .input(
      z.object({
        phoneNumber: z.string(),
        code: z.string(),
        teamId: z.number().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { phoneNumber, code, teamId } = input;
      const { user } = ctx;
      const verifyStatus = await verifyPhoneNumber(phoneNumber, code, user.id, teamId);
      return verifyStatus;
    }),
  getVerifiedNumbers: authedProcedure
    .input(
      z.object({
        teamId: z.number().optional(),
      })
    )
    .query(async ({ ctx, input }) => {
      const { user } = ctx;
      const verifiedNumbers = await ctx.prisma.verifiedNumber.findMany({
        where: {
          OR: [{ userId: user.id }, { teamId: input.teamId }],
        },
      });

      return verifiedNumbers;
    }),
  getWorkflowActionOptions: authedProcedure.query(async ({ ctx }) => {
    const { user } = ctx;

    const isCurrentUsernamePremium = user && user.metadata && hasKeyInMetadata(user, "isPremium");

    let isTeamsPlan = false;
    if (!isCurrentUsernamePremium) {
      const { hasTeamPlan } = await viewerTeamsRouter.createCaller(ctx).hasTeamPlan();
      isTeamsPlan = !!hasTeamPlan;
    }
    const t = await getTranslation(ctx.user.locale, "common");
    return getWorkflowActionOptions(t, IS_SELF_HOSTED || isCurrentUsernamePremium || isTeamsPlan);
  }),
  getByViewer: authedProcedure.query(async ({ ctx }) => {
    const { prisma } = ctx;

    const user = await prisma.user.findUnique({
      where: {
        id: ctx.user.id,
      },
      select: {
        id: true,
        username: true,
        avatar: true,
        name: true,
        startTime: true,
        endTime: true,
        bufferTime: true,
        workflows: {
          select: {
            id: true,
            name: true,
          },
        },
        teams: {
          where: {
            accepted: true,
          },
          select: {
            role: true,
            team: {
              select: {
                id: true,
                name: true,
                slug: true,
                members: {
                  select: {
                    userId: true,
                  },
                },
                workflows: {
                  select: {
                    id: true,
                    name: true,
                  },
                },
              },
            },
          },
        },
      },
    });

    if (!user) {
      throw new TRPCError({ code: "INTERNAL_SERVER_ERROR" });
    }

    const userWorkflows = user.workflows;

    type WorkflowGroup = {
      teamId?: number | null;
      profile: {
        slug: (typeof user)["username"];
        name: (typeof user)["name"];
        image?: string;
      };
      metadata?: {
        readOnly: boolean;
      };
      workflows: typeof userWorkflows;
    };

    let workflowGroups: WorkflowGroup[] = [];

    workflowGroups.push({
      teamId: null,
      profile: {
        slug: user.username,
        name: user.name,
        image: user.avatar || undefined,
      },
      workflows: userWorkflows,
      metadata: {
        readOnly: false,
      },
    });

    workflowGroups = ([] as WorkflowGroup[]).concat(
      workflowGroups,
      user.teams.map((membership) => ({
        teamId: membership.team.id,
        profile: {
          name: membership.team.name,
          slug: "team/" + membership.team.slug,
          image: `${CAL_URL}/team/${membership.team.slug}/avatar.png`,
        },
        metadata: {
          readOnly: membership.role === MembershipRole.MEMBER,
        },
        workflows: membership.team.workflows,
      }))
    );

    return {
      workflowGroups: workflowGroups.filter((groupBy) => !!groupBy.workflows?.length),
      profiles: workflowGroups.map((group) => ({
        teamId: group.teamId,
        ...group.profile,
        ...group.metadata,
      })),
    };
  }),
});

async function upsertSmsReminderFieldForBooking({
  workflowId,
  eventTypeId,
  isSmsReminderNumberRequired,
}: {
  workflowId: number;
  isSmsReminderNumberRequired: boolean;
  eventTypeId: number;
}) {
  await upsertBookingField(
    getSmsReminderNumberField(),
    getSmsReminderNumberSource({
      workflowId,
      isSmsReminderNumberRequired,
    }),
    eventTypeId
  );
}

async function removeSmsReminderFieldForBooking({
  workflowId,
  eventTypeId,
}: {
  workflowId: number;
  eventTypeId: number;
}) {
  await removeBookingField(
    {
      name: SMS_REMINDER_NUMBER_FIELD,
    },
    {
      id: "" + workflowId,
      type: "workflow",
    },
    eventTypeId
  );
}
