import { expect, type BrowserContext, type Page } from "@playwright/test";

export const e2eEmailCredentials = {
  email: "chanwillian0@gmail.com",
  password: "12345678",
};

export const splashStartChatButtonName = "Start Chatting";

export function getSplashStartChatButton(page: Page) {
  return page.getByRole("button", { name: splashStartChatButtonName });
}

export async function clearBrowserState(
  context: BrowserContext,
  page: Page,
  baseURL?: string,
) {
  await context.clearCookies();

  const client = await context.newCDPSession(page);
  const origins = new Set([
    new URL(baseURL ?? "http://127.0.0.1:3000").origin,
    "http://127.0.0.1:3000",
    "http://localhost:3000",
  ]);

  for (const origin of origins) {
    await client
      .send("Storage.clearDataForOrigin", {
        origin,
        storageTypes: "all",
      })
      .catch(() => {});
  }
}

export async function enterChatFromSplash(
  page: Page,
  options: {
    expectedUrl?: RegExp;
    timeout?: number;
    waitUntil?: "commit" | "domcontentloaded" | "load" | "networkidle";
  } = {},
) {
  const {
    expectedUrl = /\/chat$/,
    timeout = 10_000,
    waitUntil,
  } = options;

  await page.goto("/splash", { waitUntil });

  const startChatButton = getSplashStartChatButton(page);
  await expect(startChatButton).toBeVisible({ timeout });
  await expect(startChatButton).toBeEnabled();

  for (let attempt = 0; attempt < 3; attempt += 1) {
    await startChatButton.click();
    try {
      await expect(page).toHaveURL(expectedUrl, { timeout: 3_000 });
      return;
    } catch {
      await page.waitForTimeout(500);
    }
  }

  await expect(page).toHaveURL(expectedUrl, { timeout });
}

export async function dismissChatInterruptions(page: Page) {
  for (const buttonName of ["Later", "Cancel", "OK"]) {
    const button = page.getByRole("button", { name: buttonName }).first();
    if (await button.isVisible().catch(() => false)) {
      await button.click();
    }
  }
}

export async function switchToEmailSignIn(page: Page) {
  const otherOptionsButton = page.getByRole("button", {
    name: /Other Sign In Options/i,
  });
  const otherOptionsDialog = page.getByRole("dialog", {
    name: /Other sign-in options/i,
  });

  await expect(otherOptionsButton).toBeVisible({ timeout: 10_000 });
  await expect(otherOptionsButton).toBeEnabled();

  for (let attempt = 0; attempt < 3; attempt += 1) {
    await otherOptionsButton.click();
    try {
      await expect(otherOptionsDialog).toBeVisible({ timeout: 2_000 });
      break;
    } catch (error) {
      if (attempt === 2) throw error;
      await otherOptionsButton.evaluate((button) =>
        (button as HTMLButtonElement).click(),
      );
      if (await otherOptionsDialog.isVisible().catch(() => false)) {
        break;
      }
      await page.waitForTimeout(500);
    }
  }

  await page.getByRole("button", { name: "Email Sign In" }).click();
  await expect(page.getByPlaceholder("Email")).toHaveValue(
    e2eEmailCredentials.email,
  );
  await expect(page.getByPlaceholder("Password")).toHaveValue(
    e2eEmailCredentials.password,
  );
}

export async function submitEmailLogin(
  page: Page,
  options: {
    expectedUrl?: RegExp;
    waitForRequest?: boolean;
    urlTimeout?: number;
  } = {},
) {
  const {
    expectedUrl,
    waitForRequest = true,
    urlTimeout = 10_000,
  } = options;
  const loginButton = page.getByRole("button", { name: "Login" });

  if (!waitForRequest) {
    await Promise.all([
      expectedUrl
        ? page.waitForURL(expectedUrl, { timeout: urlTimeout })
        : Promise.resolve(),
      loginButton.click(),
    ]);
    return null;
  }

  const loginRequestPromise = page.waitForRequest("**/api/auth/login");
  await loginButton.click();

  const loginRequest = await loginRequestPromise;
  expect(loginRequest.postDataJSON()).toMatchObject({
    email: e2eEmailCredentials.email,
    password: e2eEmailCredentials.password,
    isTestAccount: true,
  });

  if (expectedUrl) {
    await expect(page).toHaveURL(expectedUrl, { timeout: urlTimeout });
  }

  return loginRequest;
}

export async function signInWithEmailAndOpenChat(page: Page) {
  await page.goto("/auth");
  await switchToEmailSignIn(page);
  const historyResponsePromise = page
    .waitForResponse("**/api/chat/history**", { timeout: 10_000 })
    .catch(() => null);
  await submitEmailLogin(page, { expectedUrl: /\/chat$/ });
  await historyResponsePromise;
  await dismissChatInterruptions(page);
}

export async function completeVipPayment(page: Page) {
  const createOrderRequestPromise = page.waitForRequest(
    "**/api/payment/create-order",
  );
  const orderStatusRequestPromise = page.waitForRequest(
    "**/api/payment/order-status**",
  );

  await page.getByRole("button", { name: /Pay and Activ/i }).click();

  const createOrderRequest = await createOrderRequestPromise;
  expect(createOrderRequest.postDataJSON()).toMatchObject({
    planId: "vip_monthly",
    payChannel: "stripe",
  });
  await orderStatusRequestPromise;

  const paymentSuccessDialog = page.getByRole("alertdialog", {
    name: "Payment successful",
  });
  await expect(paymentSuccessDialog).toBeVisible();
  await paymentSuccessDialog.getByRole("button", { name: "OK" }).click();
}

export async function expectInsufficientCreditsDialog(page: Page) {
  const insufficientCreditsDialog = page.getByRole("dialog", {
    name: "Not enough credits",
  });
  await expect(insufficientCreditsDialog).toBeVisible();
  await expect(insufficientCreditsDialog.getByText("Balance")).toBeVisible();
  await expect(insufficientCreditsDialog.getByText("Required")).toBeVisible();
  await expect(
    insufficientCreditsDialog.getByText("Still needed"),
  ).toBeVisible();

  return insufficientCreditsDialog;
}

export async function expectVipChatSubscriptionUrl(page: Page) {
  await expect(page).toHaveURL(/\/subscription\?/);
  const url = new URL(page.url());
  expect(url.pathname).toBe("/subscription");
  expect(url.searchParams.get("type")).toBe("vip");
  expect(url.searchParams.get("returnTo")).toBe("chat");
}

export function expectAuthRedirectToVipChatSubscription(page: Page) {
  const redirect = new URL(page.url()).searchParams.get("redirect");
  expect(redirect).not.toBeNull();

  const redirectUrl = new URL(redirect ?? "", "http://e2e.local");
  expect(redirectUrl.pathname).toBe("/subscription");
  expect(redirectUrl.searchParams.get("type")).toBe("vip");
  expect(redirectUrl.searchParams.get("returnTo")).toBe("chat");
}
