import {
  isPresent,
  keysToCamelCase,
  hyphenate,
  keysToSnakeCase,
  humanize,
} from "@bigbinary/neeto-cist";
import { faker } from "@faker-js/faker";
import {
  joinHyphenCase,
  TAGS_SELECTORS,
  NEETO_EDITOR_SELECTORS,
  currencyUtils,
  NEETO_FILTERS_SELECTORS,
  simulateTypingWithDelay,
} from "@neetoplaywright";
import { expect, Locator, Page } from "@playwright/test";
import { Dayjs } from "dayjs";
import { TFunction } from "i18next";
import { getI18nInstance } from "playwright-i18next-fixture";
import { isEmpty, pluck } from "ramda";

import { SchedulingLinkApis, MeetingOutcomeApis, PaymentsApis } from "@apis";
import { STANDARD_DATE_FORMAT, VISIBILITY_TIMEOUT } from "@constants/common";
import { CommonUtils, ContractPage, FilterUtils, TableUtils } from "@poms";
import {
  SearchAndFindMeetingProps,
  CreateMeetingViaUIProps,
  AtLeastOne,
  updateMeetingViaUIParams,
  GetMeetingDetailsViaRequestProps,
  DeleteMeetingViaRequestProps,
  CreateMeetingViaRequestProps,
  VerifyMeetingColumnLinkProps,
  AddHostType,
  UpdatePaymentViaRequestProps,
  VerifyEnableAndDisableParams,
  EnableAndDisableMeetingLinkFromTableParams,
  VerifyMeetingProps,
  VerifyCustomFieldOrderInMeetingLink,
  AvailabilityDetails,
  VerifyAvailabilityCardDetailsInMembersParams,
  VerifyAvailabilityDetailsInPaneParams,
  VerifyMeetingFeeAmountParams,
  SetDefaultSpotAndVerifyProps,
  ChangeMeetingSpotProps,
  ConfigureShowAsDiscountedPriceProps,
  LeadDaysType,
  MeetingDetailsWithAmount,
  OverrideEventLayoutProps,
} from "@poms/types";
import { ROUTES } from "@routes";
import {
  MEETING_SELECTORS,
  COMMON_SELECTORS,
  CONFIGURE_SECTION_SELECTORS,
  BOOKING_SELECTORS,
  ROUTING_FORM_SELECTORS,
  AVAILABILITY_SELECTORS,
  AllDaysType,
  CONFIGURE_PAYMENT_SELECTORS,
  DISCOUNT_CODE_SELECTORS,
  HOST_SECTION_SELECTORS,
  EUI_SELECTORS,
} from "@selectors";
import { AVAILABILITY_TEXTS, SPOTS_VS_NAMES } from "@texts";
import {
  CalendarIntegration,
  CustomAvailability,
  PaymentProviders,
  SchedulingLinkTypes,
} from "@types";
import {
  getFormattedAvailability,
  buildBookingUrl,
  extractAccessTokenFromUrl,
  getDiscountedPriceFormat,
  phoneNumberFormat,
  getHostName,
} from "@utils";
import PaymentUtils from "@utils/payments";

export default class SchedulingLinkPage {
  page: Page;
  t: TFunction;
  editorContentTextBox: Locator;
  meetingNameTextbox: Locator;
  commonUtils: CommonUtils;
  schedulingLinkApis: SchedulingLinkApis;
  paymentsApis: PaymentsApis;
  meetingOutcomesApis: MeetingOutcomeApis;
  contractPage: ContractPage;
  paymentUtils: PaymentUtils;
  tableUtils: TableUtils;
  filterUtils: FilterUtils;

  constructor(page: Page, commonUtils: CommonUtils) {
    this.page = page;
    this.commonUtils = commonUtils;
    this.t = getI18nInstance().t;
    this.editorContentTextBox = this.page
      .getByTestId(MEETING_SELECTORS.descriptionTextField)
      .getByTestId(NEETO_EDITOR_SELECTORS.contentField);

    this.meetingNameTextbox = this.page.getByTestId(
      COMMON_SELECTORS.customInputField("name")
    );

    this.schedulingLinkApis = new SchedulingLinkApis(commonUtils);
    this.paymentsApis = new PaymentsApis(commonUtils);
    this.meetingOutcomesApis = new MeetingOutcomeApis(commonUtils);
    this.contractPage = new ContractPage({ page, commonUtils });
    this.paymentUtils = new PaymentUtils(commonUtils);
    this.tableUtils = new TableUtils(page, commonUtils);
    this.filterUtils = new FilterUtils(page, commonUtils);
  }

  getAvailabilityDetails = (
    availability: AvailabilityDetails,
    cardDetails: {
      card: Locator;
      userName: string;
    }
  ) => ({
    cardDetails,
    availabilityName: availability.name,
    availability: getFormattedAvailability(availability.customAvailability),
  });

  searchAndVerifyMeeting = async ({
    name,
    customPage = this.page,
  }: SearchAndFindMeetingProps) => {
    extractAccessTokenFromUrl(customPage.url()) !== "scheduling-links" &&
      (await this.commonUtils.navigateAndWaitForPageLoad(
        ROUTES.schedulingLinks.index,
        customPage
      ));

    await this.filterUtils.clearFiltersFromActionBlock(customPage);

    const searchMeetingInput = customPage.getByTestId(
      MEETING_SELECTORS.searchMeetingInput
    );

    await searchMeetingInput.fill(name);
    await searchMeetingInput.blur();

    await expect(
      customPage.getByTestId(NEETO_FILTERS_SELECTORS.neetoFiltersBarClearButton)
    ).toBeVisible();

    await this.commonUtils.waitForPageLoad({
      customPageContext: customPage,
    });

    await expect(
      customPage
        .getByTestId(BOOKING_SELECTORS.meetingColumnText)
        .filter({ hasText: new RegExp(`^${name}$`, "i") })
    ).toBeVisible({ timeout: 10_000 });

    await expect(
      customPage.getByTestId(MEETING_SELECTORS.subheaderMeetingCount)
    ).toHaveText(`1 ${this.t("entity.schedulingLink")}`, {
      timeout: 30_000,
    });
  };

  navigateToTab = async ({
    name,
    tabName,
  }: {
    name: string;
    tabName: string;
  }) => {
    await this.navigateToMeetingDetailsPage({ name });
    await this.commonUtils.waitForPageLoad();

    await this.page
      .getByTestId(ROUTING_FORM_SELECTORS.headerNavigationLinks)
      .getByTestId(hyphenate(tabName))
      .click();

    await this.commonUtils.waitForPageLoad();
  };

  createMeetingViaUI = async ({
    name,
    description = "",
    type = "oneOnOne",
    duration = 45,
    spot = "jitsi",
    isColoured = false,
  }: CreateMeetingViaUIProps) => {
    await this.commonUtils.waitForPageLoad();
    await this.navigateToWhatSectionPage(type);

    let color: number | undefined;
    if (isColoured) {
      ({ index: color } =
        await this.commonUtils.pickPreferredEventColor(false));
    }

    // Using pressSequentially to properly generate slug input
    await this.meetingNameTextbox.clear();
    await this.meetingNameTextbox.pressSequentially(name, { delay: 30 });
    await this.editorContentTextBox.clear();
    await this.editorContentTextBox.fill(description);
    await this.page
      .getByTestId(MEETING_SELECTORS.durationInMinutes(duration))
      .click();

    await this.commonUtils.saveChanges();

    await expect(
      this.page.getByTestId(MEETING_SELECTORS.meetingHeader)
    ).toHaveText(name, { timeout: 20_000 });

    await this.page.getByTestId(MEETING_SELECTORS.where).click();

    if (await this.page.getByTestId(MEETING_SELECTORS.spot(spot)).isChecked()) {
      return color;
    }

    await this.commonUtils.waitForPageLoad();
    await this.page.getByTestId(MEETING_SELECTORS.spot(spot)).click();

    await this.commonUtils.saveChanges();

    return color;
  };

  updateMeetingViaUI = async ({
    newName,
    newSlug,
  }: AtLeastOne<updateMeetingViaUIParams>) => {
    await this.page.getByTestId(MEETING_SELECTORS.what).click();

    isPresent(newName) &&
      (await this.page
        .getByTestId(COMMON_SELECTORS.customInputField("name"))
        .fill(newName));

    isPresent(newSlug) &&
      (await this.page
        .getByTestId(COMMON_SELECTORS.customInputField("slug"))
        .fill(newSlug));

    await Promise.all([
      this.page.getByTestId(COMMON_SELECTORS.saveChangesButton).click(),
      this.commonUtils.verifyToast(),
    ]);

    await expect(
      this.page.getByTestId(MEETING_SELECTORS.meetingHeader)
    ).toHaveText(newName, { timeout: 20_000 });
  };

  getSlotTimingsFromSlotIndexViaRequest = async ({
    meetingName,
    timezone,
    bookingDate,
    slotIndex = 0,
  }: {
    meetingName: string;
    timezone: string;
    bookingDate: Dayjs;
    slotIndex?: number;
  }) => {
    const [bookingYear, bookingMonth, bookingDay] = bookingDate
      .format(STANDARD_DATE_FORMAT)
      .split("-");

    const response = await this.schedulingLinkApis.getSlots({
      meetingName,
      bookingMonth,
      bookingYear,
      timezone,
    });

    const responseBody = await response.json();
    const slotsForTheDay = responseBody.slots.find(
      ({ day }) => day === Number(bookingDay)
    );

    if (slotsForTheDay) {
      const requiredSlotKey = Object.keys(slotsForTheDay.slots)[slotIndex];
      const requiredSlot = slotsForTheDay.slots[requiredSlotKey];

      return keysToCamelCase(requiredSlot);
    }

    return null;
  };

  getMeetingDetailsViaRequest = ({
    name,
    isTemplate = false,
  }: GetMeetingDetailsViaRequestProps) => {
    const conditions = [
      { type: "text", rule: "contains", node: "name,slug", value: name },
    ];
    const filters = [{ conditions }];

    return this.schedulingLinkApis.fetch(filters, isTemplate);
  };

  getMeetingIds = async (meetingName: string, isTemplate = false) => {
    const response = await this.getMeetingDetailsViaRequest({
      name: meetingName,
      isTemplate,
    });

    const responseBody = await response.json();
    const meeting = responseBody.meetings[0];

    return { sid: meeting?.sid, id: meeting?.id };
  };

  deleteMeetingViaRequest = async ({
    name,
    isTemplate = false,
  }: DeleteMeetingViaRequestProps) => {
    const response = await this.getMeetingDetailsViaRequest({
      name,
      isTemplate,
    });

    const responseBody = await response.json();
    const meetingId = responseBody.meetings[0]?.id;
    meetingId && (await this.schedulingLinkApis.delete(meetingId));
  };

  createSchedulingLinkViaRequest = async ({
    name,
    description = "",
    isTemplate = false,
    kind = "one_on_one",
    leadTime = 15,
    startTimeIncrement = 30,
    spot = "jitsi",
    duration = 30,
    retryCount = 4,
  }: CreateMeetingViaRequestProps) => {
    const response = await this.schedulingLinkApis.create(
      keysToSnakeCase({
        name,
        description,
        spot,
        slug: hyphenate(name),
        kind,
        leadTime,
        duration,
        isTemplate,
        startTimeIncrement,
      })
    );

    if (retryCount-- && response.status() !== 200) {
      return this.createSchedulingLinkViaRequest({
        name,
        description,
        isTemplate,
        kind,
        leadTime,
        startTimeIncrement,
        spot,
        duration,
        retryCount,
      });
    }

    const responseBody = await response.json();

    return responseBody.meeting?.sid;
  };

  verifyMeetingColumnLink = async ({
    columnLink,
    scheduledMeetingSideTab,
    noDataTitle,
  }: VerifyMeetingColumnLinkProps) => {
    await this.commonUtils.waitForPageLoad();
    await this.page.getByTestId(columnLink).click();
    await expect(
      this.page
        .getByTestId(MEETING_SELECTORS.scheduledMeetingSideTab)
        .filter({ hasText: scheduledMeetingSideTab })
    ).toContainText("0");

    await expect(
      this.page.getByTestId(COMMON_SELECTORS.noDataTitle)
    ).toHaveText(noDataTitle);
  };

  changeMeetingSpot = async ({
    meetingId,
    spots = ["jitsi"],
    address = faker.location.streetAddress(),
    clickShowLocationToggle = false,
  }: ChangeMeetingSpotProps) => {
    !this.page.url().includes("where") &&
      (await this.commonUtils.navigateAndWaitForPageLoad(
        ROUTES.schedulingLinks.where(meetingId)
      ));

    for (const spot of spots) {
      const spotSelector = MEETING_SELECTORS.spot(spot);

      const spotAlreadyChecked = await this.page
        .getByTestId(spotSelector)
        .getByTestId(COMMON_SELECTORS.checkbox)
        .isChecked();

      if (spotAlreadyChecked) continue;
      await this.page.getByTestId(spotSelector).click();

      if (spot === "in_person" || spot === "custom" || spot === "phone_call") {
        await expect(
          this.page.getByTestId(MEETING_SELECTORS.spotHeader)
        ).toBeVisible();

        if (spot === "phone_call") {
          address = phoneNumberFormat(address);

          await this.commonUtils.selectCountryLabel(
            EUI_SELECTORS.enterPhoneNumberInput
          );

          await this.page
            .getByTestId(MEETING_SELECTORS.spotNotes)
            .fill(address);
        } else {
          await simulateTypingWithDelay({
            field: this.page.getByTestId(MEETING_SELECTORS.spotNotes),
            value: address,
            delay: 10,
          });
        }

        clickShowLocationToggle &&
          (await this.page
            .getByTestId(BOOKING_SELECTORS.showLocationAfterBooking)
            .click());

        const continueButton = this.page
          .getByTestId(MEETING_SELECTORS.spotOptions)
          .getByTestId(MEETING_SELECTORS.meetingSpotContinueButton);

        await continueButton.click();
        await expect(
          continueButton.getByTestId(COMMON_SELECTORS.buttonSpinner)
        ).toBeHidden({ timeout: 10_000 });

        await expect(this.page.getByTestId(spotSelector)).toContainText(
          address
        );

        await this.page.getByTestId(spotSelector).getByRole("checkbox").check();
        await expect(
          this.page.getByTestId(COMMON_SELECTORS.backdrop)
        ).toBeHidden();
      }

      await this.commonUtils.saveChanges();
    }
  };

  setDefaultSpotAndVerify = async ({
    meetingId,
    spots,
    meetingName,
  }: SetDefaultSpotAndVerifyProps) => {
    for (const spot of spots) {
      await this.commonUtils.navigateAndWaitForPageLoad(
        ROUTES.schedulingLinks.where(meetingId)
      );

      await this.page
        .getByTestId(MEETING_SELECTORS.spot(spot))
        .getByTestId(MEETING_SELECTORS.defaultMeetingSpot)
        .click();

      await this.commonUtils.saveChanges();

      await this.commonUtils.navigateAndWaitForPageLoad(
        buildBookingUrl(hyphenate(meetingName))
      );

      await this.commonUtils.waitForSlotsVisibility();

      await expect(
        this.page.getByTestId(MEETING_SELECTORS.spotInput(spot))
      ).toBeChecked();
    }
  };

  openChangeAvailabilityPane = async (
    memberName = getHostName(),
    timezone = AVAILABILITY_TEXTS.indianStandardTime
  ) => {
    const availabilityCard = this.page.getByTestId(
      AVAILABILITY_SELECTORS.availabilityNameCard(memberName)
    );

    await Promise.all([
      expect(
        availabilityCard.getByTestId(HOST_SECTION_SELECTORS.memberName)
      ).toHaveText(memberName),
      expect(
        availabilityCard.getByTestId(HOST_SECTION_SELECTORS.memberTimeZone)
      ).toContainText(timezone),
    ]);

    const changeAvailabilityPaneMemberDetails = this.page.getByTestId(
      HOST_SECTION_SELECTORS.changeAvailabilityPaneMemberDetails
    );

    await availabilityCard
      .getByTestId(AVAILABILITY_SELECTORS.editAvailabilityButton)
      .click();

    await Promise.all([
      expect(
        changeAvailabilityPaneMemberDetails.getByTestId(
          HOST_SECTION_SELECTORS.memberName
        )
      ).toHaveText(memberName),
      expect(
        changeAvailabilityPaneMemberDetails.getByTestId(
          HOST_SECTION_SELECTORS.memberTimeZone
        )
      ).toContainText(timezone),
    ]);
  };

  openMemberAvailabilityPane = async () => {
    await this.page
      .getByTestId(HOST_SECTION_SELECTORS.viewAvailabilitiesButton)
      .click();

    await expect(
      this.page.getByTestId(COMMON_SELECTORS.paneHeader)
    ).toContainText(
      this.t("meetings.form.members.memberAvailabilitiesPane.title"),
      { timeout: 20_000 }
    );
  };

  changeHost = async ({ firstName, lastName, email }: AddHostType) => {
    const host = firstName && lastName ? `${firstName} ${lastName}` : email;
    await this.commonUtils.selectOptionFromDropdown({ value: host });

    await this.commonUtils.saveChanges();
  };

  addHost = async ({ firstName, lastName, email }: AddHostType) => {
    const host = firstName && lastName ? `${firstName} ${lastName}` : email;
    await this.page.getByTestId(HOST_SECTION_SELECTORS.addTeamMember).click();

    await this.page
      .getByTestId(COMMON_SELECTORS.customSelectValueContainer())
      .click();

    await this.page.keyboard.type(host);
    await this.page.getByTestId(COMMON_SELECTORS.selectOption(host)).click();

    await this.commonUtils.saveChanges();
    await expect(
      this.page.getByTestId(COMMON_SELECTORS.saveChangesButton)
    ).toBeDisabled();
  };

  updatePaymentViaRequest = async ({
    meetingName,
    amount,
    currency,
    paymentProvider = "stripe",
    isTemplate = false,
  }: UpdatePaymentViaRequestProps) => {
    const [connectedStripeStandardAccountId, { sid: meetingSid }] =
      await Promise.all([
        this.paymentUtils.getConnectedStripeStandardAccountId(),
        this.getMeetingIds(meetingName, isTemplate),
      ]);

    const baseFeeAttributes = {
      amount,
      currency: currency.toLowerCase(),
      paymentProvider,
      isEnabled: true,
      isDisplayAmountEnabled: false,
    };

    if (paymentProvider === "stripe") {
      baseFeeAttributes["recipientAttributes"] = {
        accountableId: connectedStripeStandardAccountId,
      };
    }

    const feesAttributes = [baseFeeAttributes];

    meetingSid &&
      (await this.schedulingLinkApis.update(meetingSid, {
        fees_attributes: keysToSnakeCase(feesAttributes),
        allowed_durations_attributes: [],
      }));
  };

  disableOrEnableMeetingLink = async (meetingName: string) => {
    await this.page
      .getByRole("cell", { name: meetingName })
      .getByTestId(COMMON_SELECTORS.dropdownIcon)
      .click();

    const disableOrEnableButton = this.page.getByTestId(
      MEETING_SELECTORS.disableOrEnableButton(meetingName)
    );
    await this.commonUtils.bringElementIntoView(disableOrEnableButton);
    await disableOrEnableButton.dispatchEvent("click");
    await this.commonUtils.verifyToast();
  };

  navigateToMeetingDetailsPage = async ({
    name,
  }: SearchAndFindMeetingProps) => {
    await this.commonUtils.waitForPageLoad();
    await this.searchAndVerifyMeeting({ name });
    await this.page.getByRole("cell", { name }).getByRole("link").click();

    await this.commonUtils.waitForNeetoPageLoad();
  };

  navigateToThankYouPage = async (meetingName: string) => {
    await this.navigateToTab({
      name: meetingName,
      tabName: MEETING_SELECTORS.settingsTab,
    });

    await this.page
      .getByTestId(
        CONFIGURE_SECTION_SELECTORS.configureCategoryTab(
          this.t("neetoThankYou.common.thankYouPage")
        )
      )
      .click();
  };

  fillMeetingNameAndSubmitForm = async ({ name }: { name: string }) => {
    await this.page
      .getByTestId(COMMON_SELECTORS.customInputField("name"))
      .fill(name);

    await this.commonUtils.clickSaveChangesButton();
    await this.commonUtils.verifyToast();

    await expect(
      this.page.getByTestId(MEETING_SELECTORS.meetingHeader)
    ).toHaveText(name);
  };

  navigateToWhatSectionPage = async (
    type: SchedulingLinkTypes = "oneOnOne"
  ) => {
    await this.commonUtils.waitForNeetoPageLoad();
    await this.page.getByTestId(MEETING_SELECTORS.newMeetingButton).click();
    await this.commonUtils.waitForPageLoad();

    const schedulingLinkCard = this.page
      .getByTestId(MEETING_SELECTORS.selectMeetingType)
      .filter({ hasText: this.t(`meetings.form.types.${type}.title`) });

    await expect(schedulingLinkCard).toContainText(
      this.t(`meetings.form.types.${type}.description`)
    );

    await schedulingLinkCard
      .getByTestId(MEETING_SELECTORS.continueButton)
      .click();

    await expect(
      this.page.getByTestId(COMMON_SELECTORS.customInputLabel("name"))
    ).toBeVisible({ timeout: 10_000 });
  };

  verifyDeleteMeetingAlertModal = async (name: string) => {
    await this.page.getByTestId(MEETING_SELECTORS.dropdownLink(name)).click();
    await this.page.getByTestId(MEETING_SELECTORS.deleteButton(name)).click();
    await expect(this.page.getByTestId(COMMON_SELECTORS.alertTitle)).toHaveText(
      this.t("alert.delete.title", { entity: this.t("entity.schedulingLink") })
    );

    await expect(
      this.page.getByTestId(COMMON_SELECTORS.alertModalMessage)
    ).toHaveText(
      this.t("alert.delete.message", {
        entity: this.t("entity.schedulingLink"),
        title: name,
      })
    );
  };

  navigateToWhereSectionPage = async ({ name }: SearchAndFindMeetingProps) => {
    await this.navigateToMeetingDetailsPage({ name });
    await this.page.getByTestId(MEETING_SELECTORS.where).click();
  };

  verifyTimeInputErrors = async ({
    timeAndErrors,
  }: {
    timeAndErrors: { duration: string; unit: string; error?: string }[];
  }) => {
    for (const timeAndError of timeAndErrors) {
      await this.commonUtils.selectOptionFromDropdown({
        value: timeAndError.unit,
      });

      await this.page
        .getByTestId(COMMON_SELECTORS.timeDurationInput)
        .fill(timeAndError.duration);

      const inputFieldError = this.page.getByTestId(
        COMMON_SELECTORS.inputFieldError
      );

      timeAndError.error
        ? await expect(inputFieldError).toHaveText(timeAndError.error)
        : await expect(inputFieldError).toBeHidden();
    }
  };

  verifyMeetingDetails = async ({
    name,
    url,
    description,
  }: VerifyMeetingProps) => {
    await expect(
      this.page.getByTestId(COMMON_SELECTORS.customInputField("name"))
    ).toHaveValue(name);

    await expect(
      this.page.getByTestId(COMMON_SELECTORS.customInputField("slug"))
    ).toHaveValue(joinHyphenCase(url));

    await expect(
      this.page
        .getByTestId(NEETO_EDITOR_SELECTORS.contentField)
        .getByText(description)
    ).toBeVisible();
  };

  setMeetingDateRange = async ({
    name,
    noOfDays,
  }: {
    name: string;
    noOfDays: string;
  }) => {
    await this.navigateToWhereSectionPage({ name });
    await this.page
      .getByTestId(MEETING_SELECTORS.numberOfDaysInput)
      .fill(noOfDays);

    await Promise.all([
      this.page.getByTestId(COMMON_SELECTORS.saveChangesButton).click(),
      this.commonUtils.verifyToast(),
    ]);
    await this.page.getByTestId(COMMON_SELECTORS.homeButton).click();
    await this.commonUtils.waitForPageLoad();
  };

  searchColumn = async (columnName: string) => {
    await this.tableUtils.toggleColumnsContainer();
    await this.page
      .getByTestId(COMMON_SELECTORS.columnsSearchInput)
      .fill(columnName);

    await expect(this.tableUtils.checkboxLabel(columnName)).toBeVisible();
  };

  verifyEnableAndDisable = async ({
    enable = true,
    meetingName,
  }: VerifyEnableAndDisableParams) => {
    const tag = enable ? this.t("common.enabled") : this.t("common.disabled");
    await expect(
      this.page
        .getByRole("row")
        .filter({ hasText: meetingName })
        .getByTestId(TAGS_SELECTORS.tagContainer)
    ).toContainText(tag);
  };

  enableAndDisableMeetingLinkFromTable = async ({
    enable = true,
    meetingName,
  }: EnableAndDisableMeetingLinkFromTableParams) => {
    await this.verifyEnableAndDisable({ enable: !enable, meetingName });

    await this.page
      .getByRole("cell", { name: meetingName })
      .getByTestId(COMMON_SELECTORS.dropdownIcon)
      .click();

    await this.page
      .getByTestId(MEETING_SELECTORS.enableDisableButton(meetingName))
      .click();

    await this.commonUtils.verifyToast();
    await this.verifyEnableAndDisable({ enable, meetingName });
  };

  verifyEnableDisableMeetingLinkFromClientUI = async ({
    page = this.page,
    bookingURL,
    isEnabled = true,
  }: {
    page?: Page;
    bookingURL: string;
    isEnabled?: boolean;
  }) => {
    await this.commonUtils.navigateAndWaitForPageLoad(bookingURL, page);

    isEnabled
      ? await expect(page.getByTestId(EUI_SELECTORS.monthLabel)).toBeVisible({
          timeout: 10_000,
        })
      : await Promise.all([
          expect(page.getByTestId(COMMON_SELECTORS.noDataTitle)).toHaveText(
            this.t("live.booking.disabled.header")
          ),
          expect(
            page.getByTestId(COMMON_SELECTORS.noDataHelpText)
          ).toContainText(this.t("booking.notAvailable.helpText")),
        ]);
  };

  enableAndDisableFromMeetingDetailsPage = async (enable = true) => {
    await this.commonUtils.reloadAndWaitForPageLoad();

    await this.page
      .getByTestId(COMMON_SELECTORS.navigationHeaderLeftBlock)
      .getByTestId(COMMON_SELECTORS.neetoMoleculesMenuButton)
      .click();

    await this.page
      .getByRole("menuitem", {
        name: enable ? this.t("operation.enable") : this.t("operation.disable"),
      })
      // eslint-disable-next-line playwright/no-nth-methods
      .first() //TODO: Use data-testid once this resolves https://github.com/neetozone/neeto-cal-playwright/issues/856
      .click();

    await this.commonUtils.verifyToast();
  };

  cloneMeeting = async ({
    meetingName,
    newMeetingName,
  }: {
    meetingName: string;
    newMeetingName: string;
  }) => {
    await this.page
      .getByTestId(MEETING_SELECTORS.dropdownLink(meetingName))
      .click();

    await this.page
      .getByTestId(MEETING_SELECTORS.meetingCloneButton(meetingName))
      .click();

    await Promise.all([
      this.commonUtils.waitForUIComponentsToLoad(),
      this.commonUtils.verifyToast({ timeout: VISIBILITY_TIMEOUT }),
    ]);

    await expect(
      this.page.getByTestId(MEETING_SELECTORS.meetingHeader)
    ).toBeVisible();

    await this.meetingNameTextbox.clear();
    await this.meetingNameTextbox.fill(newMeetingName);

    const slugInput = this.page.getByTestId(
      COMMON_SELECTORS.customInputField("slug")
    );

    await slugInput.clear();
    await slugInput.pressSequentially(hyphenate(newMeetingName), { delay: 10 });

    await this.commonUtils.saveChanges();

    await expect(
      this.page.getByTestId(MEETING_SELECTORS.meetingHeader)
    ).toHaveText(newMeetingName, { timeout: VISIBILITY_TIMEOUT });
  };

  allowPrebookingViaRequest = async (
    meetingName: string,
    enablePrebooking: boolean = true
  ) => {
    const response = await this.getMeetingDetailsViaRequest({
      name: meetingName,
    });

    const responseBody = await response.json();
    const meetingId = responseBody.meetings[0]?.id;

    meetingId &&
      (await this.schedulingLinkApis.update(meetingId, {
        pre_booking_allowed: enablePrebooking,
      }));
  };

  changeDurationOfMeeting = async (meetingName: string, duration: number) => {
    const { sid: meetingSid } = await this.getMeetingIds(meetingName);

    meetingSid &&
      (await this.schedulingLinkApis.update(meetingSid, { duration }));
  };

  changeSlugViaRequest = async (meetingName: string, slug: string) => {
    const { sid: meetingSid } = await this.getMeetingIds(meetingName);

    meetingSid && (await this.schedulingLinkApis.update(meetingSid, { slug }));
  };

  changeGroupSizeViaRequest = async (
    meetingName: string,
    groupSize: number
  ) => {
    const { sid: meetingSid } = await this.getMeetingIds(meetingName);

    await this.schedulingLinkApis.update(meetingSid, {
      maximum_bookings_per_slot: groupSize.toString(),
      is_remaining_slots_count_visible: true,
    });
  };

  addMeetingDuration = async (meetingDuration: number) => {
    const durationSidebarCard = this.page.getByTestId(
      MEETING_SELECTORS.durationSidebarCard
    );

    const meetingDurationSwitch = durationSidebarCard.getByTestId(
      COMMON_SELECTORS.neetoUiSwitch
    );

    const isMeetingDurationEnabled = await meetingDurationSwitch.isChecked();

    !isMeetingDurationEnabled && (await meetingDurationSwitch.click());

    await durationSidebarCard
      .getByTestId(MEETING_SELECTORS.addDurationButton)
      .click();

    const durationInput = durationSidebarCard.getByTestId(
      MEETING_SELECTORS.durationPossible(2)
    );

    const duration = this.t(
      `meetings.form.what.durations.options.time.${meetingDuration}Minutes`
    );

    await durationInput.click();
    await this.page
      .getByTestId(COMMON_SELECTORS.dropdownMenu)
      .getByText(duration, { exact: true })
      .click();
    await expect(durationInput).toContainText(duration);

    await durationSidebarCard
      .getByTestId(MEETING_SELECTORS.durationFavoriteButton(2))
      .click();
  };

  toggleMeetingLinkViaRequest = (meetingSlugId: string, shouldDisable = true) =>
    expect
      .poll(async () => {
        const response = await this.schedulingLinkApis.update(meetingSlugId, {
          disabled: shouldDisable,
        });

        return response.status();
      })
      .toBe(200);

  verifyCustomFieldOrderInMeetingLink = async ({
    currentOrder,
    meetingSid,
    customPageContext,
  }: VerifyCustomFieldOrderInMeetingLink) => {
    await this.commonUtils.navigateAndWaitForPageLoad(
      ROUTES.schedulingLinks.what(meetingSid),
      customPageContext
    );

    await expect(
      customPageContext.getByTestId(
        COMMON_SELECTORS.labelContainer(currentOrder[0])
      )
    ).toBeVisible();

    await expect(
      customPageContext
        .getByTestId(/-container$/)
        .filter({ has: customPageContext.getByTestId(/-label$/) })
        .getByText(RegExp(currentOrder.join("|"), "i"))
    ).toHaveText(currentOrder, { ignoreCase: true });
  };

  verifyAvailabilityCardDetailsInMembers = async ({
    userNames,
    adminRoleAvailability,
    standardRoleAvailability,
  }: VerifyAvailabilityCardDetailsInMembersParams) => {
    const [adminAvailabilityCard, standardAvailabilityCard] = userNames.map(
      userName => ({
        card: this.page.getByTestId(
          AVAILABILITY_SELECTORS.availabilityNameCard(userName)
        ),
        userName,
      })
    );

    const availabilityCardsWithDetails = [
      this.getAvailabilityDetails(adminRoleAvailability, adminAvailabilityCard),
      this.getAvailabilityDetails(
        standardRoleAvailability,
        standardAvailabilityCard
      ),
    ];

    await this.openMemberAvailabilityPane();

    await Promise.all(
      availabilityCardsWithDetails
        .map(({ cardDetails, availability }) =>
          availability.map(({ day, timeSlot }) =>
            expect
              .soft(
                cardDetails.card
                  .getByTestId(AVAILABILITY_SELECTORS.weekDayPeriod(day))
                  .getByText(timeSlot)
              )
              .toBeVisible()
          )
        )
        .flat()
    );

    for (const availabilityDetails of availabilityCardsWithDetails) {
      await this.verifyAvailabilityDetailsInPane(availabilityDetails);
    }
  };

  verifyAvailabilityDetailsInPane = async ({
    cardDetails,
    availabilityName,
    availability,
  }: VerifyAvailabilityDetailsInPaneParams) => {
    await cardDetails.card
      .getByTestId(AVAILABILITY_SELECTORS.editAvailabilityButton)
      .click();

    await this.verifyWeekdaysAvailabilities({
      ...cardDetails,
      availabilityName,
      availability,
    });

    await this.page
      .getByTestId(COMMON_SELECTORS.paneModalCrossIcon)
      // eslint-disable-next-line playwright/no-nth-methods
      .last() // Two pane modals shown and to close the top modal we need to use nth method
      .click();
  };

  verifyWeekdaysAvailabilities = async ({
    userName,
    availabilityName,
    availability,
  }: {
    userName: string;
    availabilityName: string;
    availability: {
      day: AllDaysType;
      timeSlot: string;
    }[];
  }) => {
    const paneBody = this.page.getByTestId(COMMON_SELECTORS.paneBody);

    await Promise.all([
      expect(
        this.page
          .getByTestId(COMMON_SELECTORS.paneBody)
          .getByRole("radio", { name: availabilityName })
      ).toBeVisible(),
      expect(paneBody.getByText(userName)).toBeVisible(),
    ]);

    await Promise.all(
      availability.map(({ day, timeSlot }) =>
        expect
          .soft(
            this.page
              .getByTestId(HOST_SECTION_SELECTORS.chooseAvailabilityPane)
              .getByTestId(AVAILABILITY_SELECTORS.dayCards(day))
          )
          .toHaveText(RegExp(timeSlot.split(" - ").join("-")))
      )
    );
  };

  verifyMeetingFeeAmount = async ({
    customPage = this.page,
    name,
    amount,
    currency,
  }: VerifyMeetingFeeAmountParams) => {
    await this.commonUtils.navigateAndWaitForPageLoad(
      buildBookingUrl(hyphenate(name)),
      customPage
    );

    await expect(
      customPage.getByTestId(MEETING_SELECTORS.meetingFeeText)
    ).toHaveText(
      currencyUtils.currencyFormat.withSymbol(amount, currency, {
        minimumFractionDigits: 2,
      })
    );
  };

  verifyAmountAndRefundEnabledInTable = async (
    meeting: MeetingDetailsWithAmount,
    refundEnabled = "Disabled"
  ) => {
    await this.searchAndVerifyMeeting(meeting);
    await Promise.all([
      expect(
        this.page.getByTestId(MEETING_SELECTORS.paymentAmountText(meeting.name))
      ).toHaveText(
        currencyUtils.currencyFormat.withSymbol(
          meeting.amount,
          meeting.currency,
          { minimumFractionDigits: 2 }
        )
      ),
      expect(
        this.page.getByTestId(MEETING_SELECTORS.refundEnabledText(meeting.name))
      ).toHaveText(refundEnabled),
    ]);
  };

  verifySpotInMeetingLinkTable = async (
    meetingName: string,
    spots: string[]
  ) => {
    await this.commonUtils.navigateAndWaitForPageLoad(
      ROUTES.schedulingLinks.index
    );

    await this.tableUtils.toggleColumnCheckboxAndVerifyVisibility({
      shouldBeChecked: true,
      tableColumns: [this.t("meetings.table.columns.videoConferencingTool")],
    });

    await this.searchAndVerifyMeeting({ name: meetingName });

    await Promise.all(
      spots.map(spot =>
        expect(
          this.page
            .getByRole("row", { name: meetingName })
            .getByRole("cell", { name: SPOTS_VS_NAMES[spot] })
        ).toBeVisible()
      )
    );
  };

  assertNoMeetingLinks = () =>
    expect(this.page.getByTestId(COMMON_SELECTORS.noDataTitle)).toHaveText(
      this.t("emptyState.schedulingLinks.title")
    );

  fetchAllMeetingLinks = async () => {
    const { meetings: allMeetingLinks } = await this.schedulingLinkApis
      .fetchAll()
      .then(repsonse => repsonse.json());

    return allMeetingLinks;
  };

  assertMeetingLinkAndCount = async (
    meetingName: string,
    count: number = 1
  ) => {
    await expect(
      this.page
        .getByTestId(BOOKING_SELECTORS.meetingColumnText)
        .filter({ hasText: new RegExp(`^${meetingName}$`, "i") })
    ).toBeVisible({ timeout: 10_000 });

    await expect(
      this.page.getByTestId(MEETING_SELECTORS.subheaderMeetingCount)
    ).toHaveText(`${count} ${this.t("entity.schedulingLink", { count })}`);
  };

  configureShowAsDiscountedPrice = async ({
    originalPrice,
    discountedPrice,
    currency,
    paymentProvider = "stripe",
  }: ConfigureShowAsDiscountedPriceProps) => {
    const expectedPriceFormat = getDiscountedPriceFormat(
      discountedPrice,
      originalPrice,
      currency
    );

    await this.togglePspProviderContainer(paymentProvider);

    await this.page
      .getByTestId(DISCOUNT_CODE_SELECTORS.showAsDiscountedPriceSwitch)
      .click();

    await this.page
      .getByTestId(DISCOUNT_CODE_SELECTORS.discountedPriceInput())
      .fill(String(originalPrice));

    await this.page
      .getByTestId(CONFIGURE_PAYMENT_SELECTORS.amountInput)
      .fill(String(discountedPrice));

    await this.commonUtils.saveChanges();

    await expect
      .soft(this.page.getByTestId(DISCOUNT_CODE_SELECTORS.discountedPriceNote))
      .toHaveText(this.t("meetings.discountedPrice.note", expectedPriceFormat));
  };

  removeMembers = async (meetingName: string, members: string[] = []) => {
    await this.commonUtils.waitForPageLoad();
    let membersToRemove = members;

    if (isEmpty(members)) {
      const meetingResponse = await this.getMeetingDetailsViaRequest({
        name: meetingName,
      });

      const meetingDetails = await meetingResponse.json();
      membersToRemove = pluck("name", meetingDetails.meetings[0].members);
    }

    for (const member of membersToRemove) {
      await this.removeMember(member);
    }
  };

  removeMember = async (
    memberName: string,
    shouldAssertRemoval: boolean = true
  ) => {
    const memberCard = this.page.getByTestId(
      HOST_SECTION_SELECTORS.memberCard(memberName)
    );

    await memberCard.getByTestId(COMMON_SELECTORS.dropdownIcon).click();

    await this.page
      .getByTestId(COMMON_SELECTORS.dropdownMenuButton("remove"))
      .click();

    shouldAssertRemoval && (await expect(memberCard).toBeHidden());
  };

  verifyAvailabilityForRoundRobinMeeting = async (
    availabilityDetails: {
      userName: string;
      availability: CustomAvailability[];
      availabilityName: string;
    }[]
  ) => {
    for (const {
      userName,
      availability,
      availabilityName,
    } of availabilityDetails) {
      await this.page
        .getByTestId(HOST_SECTION_SELECTORS.memberCard(userName))
        .getByTestId(COMMON_SELECTORS.dropdownIcon)
        .click();

      await this.page
        .getByTestId(COMMON_SELECTORS.dropdownMenuButton("edit"))
        .click();

      await this.verifyWeekdaysAvailabilities({
        availabilityName,
        availability: getFormattedAvailability(availability),
        userName,
      });

      await this.commonUtils.closePane();
    }
  };

  enableRequireApproval = async (meetingId: string) => {
    await this.commonUtils.navigateAndWaitForPageLoad(
      ROUTES.schedulingLinks.settings.approvals(meetingId)
    );

    await this.page
      .getByTestId(CONFIGURE_PAYMENT_SELECTORS.featureCardContainer)
      .getByTestId(COMMON_SELECTORS.neetoUiSwitch)
      .click();

    await this.commonUtils.verifyToast();

    await expect(
      this.page.getByTestId(CONFIGURE_SECTION_SELECTORS.approvalRequiredSwitch)
    ).toBeChecked();
  };

  disablePayments = async (
    meetingId: string,
    paymentProvider: PaymentProviders = "stripe"
  ) => {
    await this.commonUtils.navigateAndWaitForPageLoad(
      ROUTES.schedulingLinks.settings.payment(meetingId)
    );

    await this.page
      .getByTestId(CONFIGURE_PAYMENT_SELECTORS.paymentCard(paymentProvider))
      .getByTestId(COMMON_SELECTORS.neetoUiSwitch)
      .click();

    await Promise.all([
      expect(this.page.getByTestId(COMMON_SELECTORS.alertTitle)).toHaveText(
        this.t("alert.payment.disablePayment.title", {
          paymentProvider: humanize(paymentProvider),
        })
      ),
      expect(
        this.page.getByTestId(COMMON_SELECTORS.alertModalMessage)
      ).toHaveText(
        this.t("alert.payment.disablePayment.defaultMessage", {
          paymentProvider: humanize(paymentProvider),
        })
      ),
    ]);

    await this.page
      .getByTestId(COMMON_SELECTORS.alertModalSubmitButton)
      .click();
    await this.commonUtils.verifyToast();
  };

  generateOneTimeSchedulingLink = async (meetingId: string) => {
    await this.commonUtils.navigateAndWaitForPageLoad(
      ROUTES.schedulingLinks.share.oneOffLink(meetingId)
    );

    await Promise.all([
      expect(this.page.getByTestId(COMMON_SELECTORS.heading)).toContainText(
        this.t("meetings.form.share.oneOffSchedulingLink.title")
      ),
      expect(this.page.getByTestId(COMMON_SELECTORS.qrCodeImage)).toBeVisible(),
    ]);

    return this.page
      .getByTestId(MEETING_SELECTORS.oneOffInputField)
      .innerText();
  };

  setCustomGroupSize = async (groupSize: number | string = 2) => {
    const maxGroupSizeCard = this.page.getByTestId(
      MEETING_SELECTORS.sidebarCard(
        this.t("meetings.form.what.maximumGroupSize.title")
      )
    );

    const maxGroupSizeCustomInput = maxGroupSizeCard.getByTestId(
      COMMON_SELECTORS.inputField
    );

    await maxGroupSizeCard.getByText(this.t("common.custom")).click();

    await maxGroupSizeCustomInput.fill(groupSize.toString());
    await this.commonUtils.clickSaveChangesButton();
  };

  togglePspProviderContainer = async (
    paymentProvider: PaymentProviders = "stripe",
    shouldOpenContainer = true
  ) => {
    const paymentCard = this.page.getByTestId(
      CONFIGURE_PAYMENT_SELECTORS.paymentCard(paymentProvider)
    );

    await expect(paymentCard).toBeVisible({ timeout: 10_000 });
    const visibilityAssertion = shouldOpenContainer
      ? "toBeVisible"
      : "toBeHidden";

    await expect(async () => {
      await paymentCard
        .getByTestId(CONFIGURE_PAYMENT_SELECTORS.paymentProviderDescription)
        .click();

      await expect(paymentCard.getByTestId(COMMON_SELECTORS.saveChangesButton))[
        visibilityAssertion
      ]();
    }).toPass({ timeout: 15_000 });
  };

  setLeadTime = async (
    meetingId: string,
    leadTime: number,
    leadDaysType: LeadDaysType = "business"
  ) => {
    await this.commonUtils.navigateAndWaitForPageLoad(
      ROUTES.schedulingLinks.where(meetingId)
    );

    await expect(
      this.page.getByTestId(MEETING_SELECTORS.meetingHeader)
    ).toBeVisible({ timeout: 10_000 });

    await this.page.getByTestId(MEETING_SELECTORS.customLeadTime).click();
    await this.commonUtils.selectOptionFromDropdown({
      value: this.t("common.days"),
    });

    await this.page
      .getByTestId(COMMON_SELECTORS.timeDurationInput)
      .fill(String(leadTime));

    leadDaysType === "business" &&
      (await this.page
        .getByTestId(
          COMMON_SELECTORS.checkboxInput(
            this.t("meetings.form.when.businessDays")
          )
        )
        .check());

    await this.commonUtils.saveChanges();
  };

  waitForIframeToLoad = async (meetingName: string) => {
    const iframe = this.page.frameLocator("iframe");
    await expect(iframe.getByTestId(COMMON_SELECTORS.pageLoader)).toHaveCount(
      0,
      { timeout: VISIBILITY_TIMEOUT }
    );

    await expect(
      iframe.getByTestId(EUI_SELECTORS.meetingNameHeader)
    ).toHaveText(meetingName, { timeout: VISIBILITY_TIMEOUT });
  };

  overrideEventLayout = (
    meetingId: string,
    { body, customSummary }: OverrideEventLayoutProps,
    calendarIntegration: CalendarIntegration = "googleCalendar"
  ) =>
    this.schedulingLinkApis.overrideEventLayout(
      meetingId,
      { body, customSummary },
      calendarIntegration
    );

  verifyDayColumnHeaders = async (weekDay: string, weekDays: string[]) => {
    const dayColumnHeaders = await this.page
      .getByRole("rowgroup")
      .getByRole("columnheader")
      .all();

    await Promise.all(
      dayColumnHeaders.map((dayColumnHeader, index) => {
        const currentDayIndex = (weekDays.indexOf(weekDay) + index) % 7;
        const expectedDay = weekDays[currentDayIndex].slice(0, 3);

        return expect(dayColumnHeader).toHaveText(expectedDay);
      })
    );
  };
}
