/* eslint-disable no-console */
import os from "os";

import { hyphenate } from "@bigbinary/neeto-cist";
import { faker } from "@faker-js/faker";
import {
  currencyUtils,
  NEETO_EDITOR_SELECTORS,
  getGlobalUserState,
  initializeTestData,
} from "@neetoplaywright";
import { expect, Locator, Page } from "@playwright/test";
import dayjs from "dayjs";

import { SLOT_TIME_FORMAT, UPI_PROVIDERS } from "@constants/common";
import { ROUTES } from "@constants/routes";
import { EUI_SELECTORS } from "@selectors";
import { AVAILABILITY_TEXTS } from "@texts";
import { Credentials, KeyValuePairs, NavigateToTargetMonthProps } from "@types";

export const replaceNewLinesWithSpace = (text: string) =>
  text.replaceAll("\n", " ");

export const normalizeWhitespace = (text: string) =>
  text
    .replace(/\s+/g, " ")
    .replace(/\s+<\//g, "</")
    .trim();

export const getFileNameFromPath = (filePath: string) =>
  filePath.split("/").pop().split(".").shift();

export const getFileNameFromPathWithExt = (filePath: string) =>
  filePath.split("/").pop();

export const getFileExtFromPath = (filePath: string) =>
  filePath.split(".").pop();

export const escapeRegex = str => str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");

export const buildRegexFromStrings = (...values: string[]) => {
  const escapedValues = values.map(escapeRegex).join("|");

  return new RegExp(`^(${escapedValues})$`);
};

export const getVideoCallURL = (
  bookingId: string,
  baseURL = process.env.BASE_URL
) => `${baseURL}/booking/${bookingId}/video-call`;

export const getMeetingLink = (
  meetingName: string,
  baseURL = process.env.BASE_URL
) => `${baseURL}/${hyphenate(meetingName)}`;

export const getEnvVariable = (
  keyValue: string,
  keyIndex: number
): string | undefined => {
  if (!keyValue || typeof keyValue !== "string") return undefined;
  const parts = keyValue.split(",");

  return parts[keyIndex];
};

export const generateStagingDataAndBaseURL = () =>
  initializeTestData("Cal") as { credentials: Credentials; baseURL: string };

export const generatePreFilledLink = ({
  fieldData,
  meetingName,
}: {
  fieldData: { fieldCode: string; fieldValue: string }[];
  meetingName: string;
}) => {
  const baseUrl = `${process.env.BASE_URL}/${hyphenate(meetingName)}`;

  const queryParams = fieldData
    .map(
      ({ fieldCode, fieldValue }) =>
        `${fieldCode}=${encodeURIComponent(fieldValue)}`
    )
    .join("&");

  return `${baseUrl}?${queryParams}`;
};

export const solveCaptcha = ({
  firstNumber,
  operator,
  secondNumber,
}: {
  firstNumber: string;
  operator: string;
  secondNumber: string;
}): number => {
  const num1 = parseFloat(firstNumber);
  const num2 = parseFloat(secondNumber);

  switch (operator) {
    case "+":
      return num1 + num2;
    case "-":
      return num1 - num2;
    case "*":
      return num1 * num2;
    case "/":
      return num2 !== 0 ? num1 / num2 : NaN;
    default:
      return 0;
  }
};

export const phoneNumberFormat = (phoneNumber: string) =>
  `${"+91"} ${phoneNumber.slice(0, 5)} ${phoneNumber.slice(5, 10)}`;

// Applicable to only valid payments with the card 4242 4242 4242 4242
export const stripeFee = (meetingFee: number) => (2.9 / 100) * meetingFee + 0.3;

export const platformShareDetails = ({
  meetingFee,
  splitPercentage,
}: {
  meetingFee: number;
  splitPercentage: number;
}) => {
  const hostSplitAmount = (splitPercentage / 100) * meetingFee;
  const trnxnNetAmnt = (meetingFee - stripeFee(meetingFee)).toFixed(2);
  const platformShare = (Number(trnxnNetAmnt) - hostSplitAmount).toFixed(2);

  return { hostSplitAmount, trnxnNetAmnt, platformShare };
};

export const extractAccessTokenFromUrl = (
  url: string,
  n: number = -1
): string | undefined => {
  const segments = url.split("/").filter(Boolean);
  const index = n >= 0 ? n : segments.length + n;

  return segments[index];
};

export const getBookingSidFromUrl = (url: string) =>
  extractAccessTokenFromUrl(url.split("?")[0]);

export const toSnakeCase = (str: string) =>
  str
    .replace(/([a-z])([A-Z])/g, "$1_$2")
    .replace(/\s+/g, "_")
    .replace(/-/g, "_")
    .toLowerCase();

export const generateDummyUPIID = () => {
  const domain = faker.helpers.arrayElement(UPI_PROVIDERS);

  return faker.internet.email({ provider: domain }).replaceAll("_", "-");
};

export const getUsername = ({
  firstName,
  lastName,
}: {
  firstName: string;
  lastName: string;
}) => `${firstName} ${lastName}`;

// Element click embed code has a dummy button for demo which conflicts with the id of link inserted later
export const removeButtonFromEmbedCode = (embedCode: string) =>
  embedCode.replace(/<button[^>]*>.*?<\/button>/gs, "");

export const addAsterisk = (questions, indices) =>
  questions.map(
    (question, index) => `${question}${indices.includes(index) ? "*" : ""}`
  );

export const verifyEditorDescriptionPreview = (
  previewLocator: Page | Locator,
  emailSelectors: KeyValuePairs
) =>
  Promise.all(
    emailSelectors.flatMap(({ key, value }) => {
      const elementReplacement = {
        bold: "strong",
        italic: "em",
        underline: "u",
        link: "a",
        strike: "s",
        bulletList: "ul li",
        orderedList: "ol li",
        codeBlock: "pre",
        emoji: "span",
      };

      if (key.includes("highlight")) {
        const highlightIndex = key.split("-")[1];
        const bgVar = `--neeto-editor-highlight-bg-${highlightIndex}`;
        const textVar = `--neeto-editor-highlight-text-${highlightIndex}`;

        return expect(
          previewLocator.locator(
            `span[style*="background-color: var(${bgVar})"][style*="color: var(${textVar})"]`,
            { hasText: value }
          )
        ).toBeVisible();
      }

      if (key === "todoList") {
        return expect(
          previewLocator.locator(NEETO_EDITOR_SELECTORS.todoList, {
            hasText: value,
          })
        ).toBeVisible();
      }

      const replacement = elementReplacement[key] ?? key;
      const locator =
        key === "image-upload"
          ? `img[src*="BigBinary"]` //TODO: Replace with the actual image src once fixed in playwright-commons
          : `${replacement}:has-text("${value}")`;

      return expect(previewLocator.locator(locator)).toBeVisible();
    })
  );

export const verifyEmailContent = async (
  page: Page,
  emailSelectors: KeyValuePairs,
  htmlContent: string
) => {
  await page.setContent(htmlContent);
  await verifyEditorDescriptionPreview(page, emailSelectors);
};

export const getDiscountedPriceFormat = (
  discountedPrice: number,
  originalPrice: number,
  currency: string
) => ({
  amount: currencyUtils.currencyFormat.withSymbol(
    discountedPrice,
    currency.toUpperCase(),
    { maximumFractionDigits: 2, minimumFractionDigits: 2 }
  ),
  originalPrice: currencyUtils.currencyFormat.withSymbol(
    originalPrice,
    currency.toUpperCase(),
    { maximumFractionDigits: 2, minimumFractionDigits: 2 }
  ),
});

export const getBookingStartIST = (bookingDate, istTimeString) => {
  const bookingDateIST = dayjs(bookingDate).tz("Asia/Kolkata");

  const [hourStr, minuteStr, meridiem] = istTimeString.split(/[: ]/);
  let hour = parseInt(hourStr, 10);
  const minute = parseInt(minuteStr, 10);

  if (meridiem === "PM" && hour !== 12) hour += 12;

  if (meridiem === "AM" && hour === 12) hour = 0;

  return bookingDateIST.hour(hour).minute(minute);
};

export const verifyBookingTimes = async (
  bookingTimeLocator: Locator,
  nextWeekMonday: dayjs.Dayjs
) => {
  const timeText = await bookingTimeLocator.allTextContents();
  const rawText = timeText[0];

  const matches = rawText.match(
    /\d{2}:\d{2} [AP]M - \d{2}:\d{2} [AP]M - [A-Za-z ]+(?:\([^)]+\))?/g
  );

  const extractStartTime = (text: string) => {
    const match = text.match(/^([\d:]+ [AP]M)/);

    return match ? match[1] : null;
  };

  const [istStartTime, estStartTime] = [
    AVAILABILITY_TEXTS.indianStandardTime,
    AVAILABILITY_TEXTS.easternTime,
  ].map(text => extractStartTime(matches.find(t => t.includes(text))!)!);

  const bookingStartIST = getBookingStartIST(nextWeekMonday, istStartTime).tz(
    AVAILABILITY_TEXTS.asiaKolkata
  );

  const expectedStartEST = bookingStartIST.tz(
    AVAILABILITY_TEXTS.americaNewYork
  );

  const expectedESTFormatted = expectedStartEST.format(SLOT_TIME_FORMAT);
  await Promise.all([
    expect(istStartTime).toBe(bookingStartIST.format(SLOT_TIME_FORMAT)),
    expect(estStartTime).toBe(expectedESTFormatted),
  ]);
};

export const getStripeTestUSNumber = () => {
  const lineNumber = faker.string.numeric(4);

  return `415555${lineNumber}`;
};

export const getBookingIdFromText = (text: string) =>
  text.replace(/^Booking ID\s*/, "").trim();
export const getRoomURLFromMessageBody = (body: string): string | null => {
  const match = body.match(/Where\s+(https?:\/\/[^\s]+)\s+Booking ID/);

  return match ? match[1] : null;
};

export const logSystemResources = (phase: string): void => {
  const memoryUsage = process.memoryUsage();
  const totalMem = os.totalmem();
  const freeMem = os.freemem();
  const cpus = os.cpus();
  console.log(`\n[${phase}] System Resource Usage:`);
  console.log(`  Total Memory: ${(totalMem / 1024 / 1024).toFixed(2)} MB`);
  console.log(`  Free Memory: ${(freeMem / 1024 / 1024).toFixed(2)} MB`);
  console.log(
    `  Process RSS: ${(memoryUsage.rss / 1024 / 1024).toFixed(2)} MB`
  );

  console.log(
    `  Process Heap Total: ${(memoryUsage.heapTotal / 1024 / 1024).toFixed(2)} MB`
  );

  console.log(
    `  Process Heap Used: ${(memoryUsage.heapUsed / 1024 / 1024).toFixed(2)} MB`
  );

  console.log(
    `  Process External: ${(memoryUsage.external / 1024 / 1024).toFixed(2)} MB`
  );
  console.log(`  CPU Count: ${cpus.length}`);
  console.log(`  CPU Model: ${cpus[0]?.model}`);
  console.log(`  CPU Speed: ${cpus[0]?.speed} MHz`);
};

export const prependMinusSymbol = (value: number | string) => `-${value}`;

export const clonedName = (name: string) => `${name}-2`;

export const navigateToTargetMonth = async ({
  newMonth,
  page,
  isPreviousMonth = true,
}: NavigateToTargetMonthProps) => {
  const currentMonthLabel = await page
    .getByTestId(EUI_SELECTORS.monthLabel)
    .innerText({ timeout: 35_000 });

  const monthNavigationButton = isPreviousMonth
    ? page.getByTestId(EUI_SELECTORS.previousMonthNavigationButton)
    : page.getByTestId(EUI_SELECTORS.nextMonthNavigationButton);

  !currentMonthLabel.includes(newMonth) &&
    (await expect(async () => {
      await monthNavigationButton.click();

      await expect(page.getByTestId(EUI_SELECTORS.monthLabel)).toContainText(
        newMonth,
        { timeout: 5_000 }
      );
    }).toPass());
};

export const buildEventTitle = ({ client, host, meetingName }) =>
  new RegExp(
    `^(${client} and ${host} \\| ${meetingName})|(${client} & ${host})$`
  );

export const buildDefaultGoogleEventTitle = ({ client, host }) =>
  `${client} and ${host}`;

export const getCustomBaseUrl = (subdomainName: string) =>
  `https://${subdomainName}.neetocal.net`;

export const getSpotDetailsURL = (baseURL: string = process.env.BASE_URL) =>
  `${baseURL}/booking/`;

export const meetingRoomURL = (
  bookingId: string,
  baseURL: string = process.env.BASE_URL
) => `${baseURL}/booking/${bookingId}/video-call`;

export const isDevEnv = () => process.env.TEST_ENV === "development";

export const getHostName = () =>
  isDevEnv() ? "Oliver Smith" : "André O'Reilly";

export const formatBearerAccessToken = (accessToken: string) =>
  `Bearer ${accessToken}`;

export const shouldSkipOnboarding = () => {
  let _a;

  return (
    ((_a = getGlobalUserState()) === null || _a === void 0
      ? void 0
      : _a.isOnboarded) && process.env.SKIP_SETUP === "true"
  );
};

export const isStorageStateUpdated = () => {
  let _a;

  return (
    ((_a = getGlobalUserState()) === null || _a === void 0
      ? void 0
      : _a.isStorageStateUpdated) && process.env.SKIP_SETUP === "true"
  );
};

export const shouldWaitForFloatingActionMenu = (url: string) =>
  url.includes("admin/") || url === ROUTES.admin;

export const basicHTMLContent = (content: string) => `
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
  </head>
  <body>
    ${content}
  </body>
</html>`;

export const convertIcsToContentMap = (icsContent: string) => {
  const unfolded = icsContent.split(/\r?\n/).reduce<string[]>((acc, line) => {
    if (/^[ \t]/.test(line)) {
      acc[acc.length - 1] += line.trim();
    } else {
      acc.push(line.trim());
    }

    return acc;
  }, []);

  return unfolded.reduce(
    (acc, line) => {
      const [rawKey, ...rest] = line.split(":");
      if (!rawKey || rest.length === 0) return acc;

      const key = rawKey.split(";")[0].trim();
      const value = rest.join(":").trim();

      if (key === "ATTENDEE") {
        acc[key] = acc[key] ? [...acc[key], value] : [value];
      } else {
        acc[key] = value;
      }

      return acc;
    },
    {} as Record<string, string | string[]>
  );
};

export const neetoCalWebpageTitle = (prefix: string) => `${prefix} | NeetoCal`;

export const schedulingLinkSlugCellText = (meetingName: string) =>
  `/${hyphenate(meetingName)}`;
