import type { FormResponse, Fields } from "@calcom/app-store/routing-forms/types/types";
import { zodRoutes } from "@calcom/app-store/routing-forms/zod";
import { acrossQueryValueCompatiblity } from "@calcom/lib/raqb/raqbUtils";
import { withReporting } from "@calcom/lib/sentryWrapper";
import { getUsersAttributes } from "@calcom/lib/service/attribute/server/getAttributes";
import prisma from "@calcom/prisma";
import { AssignmentReasonEnum } from "@calcom/prisma/enums";

const { getAttributesQueryValue } = acrossQueryValueCompatiblity;

export enum RRReassignmentType {
  ROUND_ROBIN = "round_robin",
  MANUAL = "manual",
}

export default class AssignmentReasonRecorder {
  /**
   * We should use decorators to wrap the methods with withReporting
   * but we can't don't have support for static methods in decorators
   * so this is a workaround to wrap the methods with withReporting.
   */
  static routingFormRoute = withReporting(
    AssignmentReasonRecorder._routingFormRoute,
    "AssignmentReasonRecorder.routingFormRoute"
  );
  static async _routingFormRoute({
    bookingId,
    routingFormResponseId,
    organizerId,
    teamId,
    isRerouting,
    reroutedByEmail,
  }: {
    bookingId: number;
    routingFormResponseId: number;
    organizerId: number;
    teamId: number;
    isRerouting: boolean;
    reroutedByEmail?: string | null;
  }) {
    // Get the routing form data
    const routingFormResponse = await prisma.app_RoutingForms_FormResponse.findUnique({
      where: {
        id: routingFormResponseId,
      },
      include: {
        form: {
          select: {
            routes: true,
            fields: true,
          },
        },
      },
    });

    if (!routingFormResponse) return;
    // Figure out which route was called
    const { form } = routingFormResponse;
    if (!form.routes || !form.fields) return;

    const parsedRoutes = zodRoutes.safeParse(form.routes);

    if (!parsedRoutes.success || !parsedRoutes.data) return;

    const chosenRouteId = routingFormResponse.chosenRouteId;

    const takenRoute = parsedRoutes.data.find((route) => route.id === chosenRouteId);

    if (!takenRoute) return;

    // Get a user's attributes
    const usersAttributes = await getUsersAttributes({ userId: organizerId, teamId });

    if (!("attributesQueryValue" in takenRoute)) return;

    const formAttributesQuery = takenRoute.attributesQueryValue;

    // Figure out the attributes associated with that route
    const attributesQueryValue = getAttributesQueryValue({
      attributesQueryValue: formAttributesQuery,
      attributes: usersAttributes,
      dynamicFieldValueOperands: {
        fields: (form.fields as Fields) || [],
        response: routingFormResponse.response as FormResponse,
      },
    });

    if (!attributesQueryValue) return;

    const attributesUsedToRoute = attributesQueryValue.children1;

    if (!attributesUsedToRoute) return;

    const attributeValues: string[] = [];

    for (const attribute of Object.keys(attributesUsedToRoute)) {
      const attributeToFilter = attributesUsedToRoute[attribute].properties;

      if (!attributeToFilter) continue;

      const userAttribute = usersAttributes.find((attribute) => attributeToFilter.field === attribute.id);

      const attributeValue = attributeToFilter.value;

      if (!userAttribute || !attributeValue || typeof attributeValue[0] === null) continue;

      if (attributeValue && attributeValue[0]) {
        const attributeValueString = (() => {
          if (Array.isArray(attributeValue[0])) {
            return attributeValue[0][0];
          } else {
            return attributeValue[0];
          }
        })();

        attributeValues.push(`${userAttribute?.name}: ${attributeValueString}`);
      }
    }

    const reasonEnum = isRerouting
      ? AssignmentReasonEnum.REROUTED
      : AssignmentReasonEnum.ROUTING_FORM_ROUTING;
    const reasonString = `${
      isRerouting && reroutedByEmail ? `Rerouted by ${reroutedByEmail}` : ""
    } ${attributeValues.join(", ")}`;

    await prisma.assignmentReason.create({
      data: {
        bookingId: bookingId,
        reasonEnum,
        reasonString,
      },
    });

    return {
      reasonEnum,
      reasonString,
    };
  }

  // Separate method to handle rerouting
  static CRMOwnership = withReporting(
    AssignmentReasonRecorder._CRMOwnership,
    "AssignmentReasonRecorder.CRMOwnership"
  );
  static async _CRMOwnership({
    bookingId,
    crmAppSlug,
    teamMemberEmail,
    recordType,
    routingFormResponseId,
    recordId,
  }: {
    bookingId: number;
    crmAppSlug: string;
    teamMemberEmail: string;
    recordType: string;
    routingFormResponseId: number;
    recordId?: string;
  }) {
    const appAssignmentReasonHandler = (await import("./appAssignmentReasonHandler")).default;
    const appHandler = appAssignmentReasonHandler[crmAppSlug];
    if (!appHandler) return;

    const crmRoutingReason = await appHandler({
      recordType,
      teamMemberEmail,
      routingFormResponseId,
      recordId,
    });

    if (!crmRoutingReason || !crmRoutingReason.assignmentReason) return;

    const { reasonEnum, assignmentReason } = crmRoutingReason;

    await prisma.assignmentReason.create({
      data: {
        bookingId,
        reasonEnum,
        reasonString: assignmentReason,
      },
    });

    return {
      reasonEnum,
      reasonString: assignmentReason,
    };
  }
  static roundRobinReassignment = withReporting(
    AssignmentReasonRecorder._roundRobinReassignment,
    "AssignmentReasonRecorder.roundRobinReassignment"
  );
  static async _roundRobinReassignment({
    bookingId,
    reassignById,
    reassignReason,
    reassignmentType,
  }: {
    bookingId: number;
    reassignById: number;
    reassignReason?: string;
    reassignmentType: RRReassignmentType;
  }) {
    const reassignedBy = await prisma.user.findUnique({
      where: {
        id: reassignById,
      },
      select: {
        username: true,
      },
    });

    const reasonEnum =
      reassignmentType === RRReassignmentType.MANUAL
        ? AssignmentReasonEnum.REASSIGNED
        : AssignmentReasonEnum.RR_REASSIGNED;

    const reasonString = `Reassigned by: ${reassignedBy?.username || "team member"}. ${
      reassignReason ? `Reason: ${reassignReason}` : ""
    }`;

    await prisma.assignmentReason.create({
      data: {
        bookingId: bookingId,
        reasonEnum,
        reasonString,
      },
    });

    return {
      reasonEnum,
      reasonString,
    };
  }
}
