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

import { stripePaymentDialogStyles } from "@/app/_components/payment/stripe-payment-dialog.styles";
import { mockCoreApis } from "@e2e/fixtures/api-mocks";

const viewports = [
  { width: 390, height: 844 },
  { width: 405, height: 797 },
  { width: 540, height: 900 },
] as const;

test.beforeEach(async ({ page }) => {
  await mockCoreApis(page);
});

for (const viewport of viewports) {
  test(`payment portal stays viewport-bound at ${viewport.width}x${viewport.height}`, async ({
    page,
  }) => {
    await page.setViewportSize(viewport);
    await page.goto("/subscription?type=topup", {
      waitUntil: "domcontentloaded",
    });

    await mountPaymentPanel(page, 180);
    await expectViewportScrim(page, viewport);
    await expectCenteredShortPanel(page, viewport);

    await mountPaymentPanel(page, 1_200);
    await expectViewportScrim(page, viewport);
    await expectScrollableTallPanel(page, viewport);
  });
}

async function mountPaymentPanel(page: Page, contentHeight: number) {
  await page.evaluate(
    ({ dialogClassName, overlayClassName, requestedContentHeight }) => {
      document.querySelector('[data-testid="payment-layout-overlay"]')?.remove();
      document.querySelector('[data-testid="transformed-checkout-host"]')?.remove();

      const transformedCheckoutHost = document.createElement("div");
      transformedCheckoutHost.dataset.testid = "transformed-checkout-host";
      transformedCheckoutHost.style.transform = "translateX(50%)";
      document.body.appendChild(transformedCheckoutHost);

      const overlay = document.createElement("div");
      overlay.dataset.testid = "payment-layout-overlay";
      overlay.className = overlayClassName;

      const panel = document.createElement("div");
      panel.dataset.testid = "payment-layout-panel";
      panel.className = dialogClassName;

      const content = document.createElement("div");
      content.style.height = `${requestedContentHeight}px`;
      content.textContent = "Complete payment";
      panel.appendChild(content);
      overlay.appendChild(panel);

      // Mirrors ModalPortal: the overlay must be a body child, never a child of
      // the transformed checkout host that triggered it.
      document.body.appendChild(overlay);
    },
    {
      dialogClassName: stripePaymentDialogStyles.dialog,
      overlayClassName: stripePaymentDialogStyles.overlay,
      requestedContentHeight: contentHeight,
    },
  );
}

async function expectViewportScrim(
  page: Page,
  viewport: { width: number; height: number },
) {
  const overlay = page.getByTestId("payment-layout-overlay");
  await expect(overlay).toBeVisible();
  const box = await overlay.boundingBox();

  expect(box).not.toBeNull();
  expect(box?.x).toBeCloseTo(0, 0);
  expect(box?.y).toBeCloseTo(0, 0);
  expect(box?.width).toBeCloseTo(viewport.width, 0);
  expect(box?.height).toBeCloseTo(viewport.height, 0);
  await expect(overlay.evaluate((node) => node.parentElement === document.body))
    .resolves.toBe(true);
}

async function expectCenteredShortPanel(
  page: Page,
  viewport: { width: number; height: number },
) {
  const panel = page.getByTestId("payment-layout-panel");
  const box = await panel.boundingBox();

  expect(box).not.toBeNull();
  expect(box?.y ?? 0).toBeGreaterThan(100);
  expect(Math.abs((box?.y ?? 0) * 2 + (box?.height ?? 0) - viewport.height))
    .toBeLessThanOrEqual(2);
}

async function expectScrollableTallPanel(
  page: Page,
  viewport: { width: number; height: number },
) {
  const panel = page.getByTestId("payment-layout-panel");
  const box = await panel.boundingBox();
  const scrollMetrics = await panel.evaluate((node) => ({
    clientHeight: node.clientHeight,
    scrollHeight: node.scrollHeight,
  }));

  expect(box).not.toBeNull();
  expect(box?.y ?? 0).toBeGreaterThanOrEqual(15);
  expect(box?.y ?? 100).toBeLessThanOrEqual(25);
  expect((box?.y ?? 0) + (box?.height ?? 0))
    .toBeLessThanOrEqual(viewport.height - 15);
  expect(scrollMetrics.scrollHeight).toBeGreaterThan(scrollMetrics.clientHeight);
}
