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

import { mockCoreApis } from "@e2e/fixtures/api-mocks";
import { apiEnvelope } from "@e2e/fixtures/data/common";
import { e2eEmailUser } from "@e2e/fixtures/data/user";
import {
  clearBrowserState,
  seedEmailSession,
} from "@e2e/fixtures/test-helpers";

const idrPlans = {
  plans: [
    {
      planId: "vip_monthly",
      planName: "Monthly",
      orderType: "vip_monthly",
      amountCents: 19_590_000,
      originalAmountCents: 19_590_000,
      dailyPriceCents: 653_000,
      currency: "IDR",
      vipDays: 30,
      dolAmount: null,
      creditBalance: 3_000,
    },
    {
      planId: "dol_1000",
      planName: "1,000 Credits",
      orderType: "dol",
      amountCents: 8_890_000,
      originalAmountCents: null,
      dailyPriceCents: null,
      currency: "IDR",
      vipDays: null,
      dolAmount: 1_000,
      creditBalance: 1_000,
    },
  ],
};

const idrGiftCatalog = {
  characterId: "elio",
  categories: [
    {
      category: "coffee",
      name: "Coffee",
      productCount: 1,
      imageUrl: null,
    },
  ],
  plans: [
    {
      planId: "tip_coffee_usd_4_99",
      planName: "Velvet Espresso",
      orderType: "tip",
      tipType: "coffee_small",
      category: "coffee",
      characterId: "elio",
      description: "Buy Elio a Velvet Espresso",
      imageUrl: null,
      amountCents: 8_929_900,
      currency: "IDR",
      autoRenew: false,
      isFirstRechargeOffer: false,
      firstRechargeDiscountPercent: 0,
      promotionType: null,
    },
  ],
};

async function registerIndonesiaPaymentMocks(page: Page) {
  const orderStatuses = new Map<string, "pending" | "paid">();
  const orderPlans = new Map<string, { planId: string; orderType: string }>();
  let createOrderCount = 0;

  await page.route("**/api/user/profile", async (route) => {
    await route.fulfill({
      json: apiEnvelope({ ...e2eEmailUser, countryCode: "ID" }),
    });
  });
  await page.route("**/api/payment/plans**", async (route) => {
    await route.fulfill({ json: apiEnvelope(idrPlans) });
  });
  await page.route("**/api/payment/gift-products**", async (route) => {
    await route.fulfill({ json: apiEnvelope(idrGiftCatalog) });
  });
  await page.route("**/api/payment/create-order", async (route) => {
    createOrderCount += 1;
    const body = route.request().postDataJSON() as { planId: string };
    const plan =
      idrPlans.plans.find((item) => item.planId === body.planId) ??
      idrGiftCatalog.plans.find((item) => item.planId === body.planId);
    const orderId = `order_qris_${body.planId}`;
    orderStatuses.set(orderId, "pending");
    orderPlans.set(orderId, {
      planId: body.planId,
      orderType: plan?.orderType ?? "dol",
    });
    await route.fulfill({
      json: apiEnvelope({
        orderId,
        payParams: {
          provider: "ezpay",
          countryCode: "ID",
          channelCode: "ID_QRIS_DYNAMIC_QR",
          channelType: "QR",
          payData: "00020101021226670016COM.NOBUBANK.WWW",
          firstChargeAmountCents: plan?.amountCents ?? 0,
          currency: "IDR",
        },
      }),
    });
  });
  await page.route("**/api/payment/order-status**", async (route) => {
    const url = new URL(route.request().url());
    const orderId = url.searchParams.get("order_id") ?? "";
    const plan = orderPlans.get(orderId);
    await route.fulfill({
      json: apiEnvelope({
        orderId,
        status: orderStatuses.get(orderId) ?? "pending",
        orderType: plan?.orderType ?? "dol",
        planId: plan?.planId ?? null,
        creditsAdded: 0,
      }),
    });
  });
  await page.route("**/api/payment/tip-message", async (route) => {
    const body = route.request().postDataJSON() as { orderId: string };
    await route.fulfill({
      json: apiEnvelope({
        orderId: body.orderId,
        characterId: "elio",
        planId: "tip_coffee_usd_4_99",
        productName: "Velvet Espresso",
        tipCount: 1,
        poolIndex: 0,
        message: "Thank you. Your gift made me smile.",
      }),
    });
  });

  return {
    getCreateOrderCount() {
      return createOrderCount;
    },
    markPaid(orderId: string) {
      orderStatuses.set(orderId, "paid");
    },
  };
}

async function prepareIndonesiaUser(page: Page) {
  await seedEmailSession(page);
  await page.evaluate(() => {
    const rawUser = localStorage.getItem("cozsweet:user");
    const user = rawUser ? JSON.parse(rawUser) : {};
    localStorage.setItem(
      "cozsweet:user",
      JSON.stringify({ ...user, countryCode: "ID" }),
    );
  });
}

async function expectQrisOrder(
  page: Page,
  buttonName: RegExp,
  planId: string,
  formattedAmount: RegExp,
) {
  const requestPromise = page.waitForRequest("**/api/payment/create-order");
  await page.getByRole("button", { name: buttonName }).click();
  const request = await requestPromise;
  expect(request.postDataJSON()).toMatchObject({
    planId,
    payChannel: "ezpay",
    recipientCharacterId: "elio",
  });

  const dialog = page.getByRole("dialog", { name: "Scan to pay with QRIS" });
  await expect(dialog).toBeVisible();
  await expect(dialog).toContainText(formattedAmount);
  await expect(dialog).toContainText("Waiting for payment");
  await expect(
    dialog.getByRole("img", { name: "QRIS payment QR code" }),
  ).toBeVisible();
  return `order_qris_${planId}`;
}

async function expectCheckoutButtonLayout(page: Page) {
  const viewport = page.viewportSize();
  const checkoutBox = await page
    .getByRole("button", { name: "Pay and Top Up" })
    .boundingBox();
  const stripeBox = await page.getByRole("button", { name: "Stripe" }).boundingBox();
  expect(viewport).not.toBeNull();
  expect(checkoutBox).not.toBeNull();
  expect(stripeBox).not.toBeNull();
  if (!viewport || !checkoutBox || !stripeBox) return;
  expect(checkoutBox.y + checkoutBox.height).toBeLessThanOrEqual(viewport.height);
  expect(stripeBox.y + stripeBox.height).toBeLessThanOrEqual(checkoutBox.y);
}

test.beforeEach(async ({ baseURL, context, page }) => {
  await clearBrowserState(context, page, baseURL);
  await mockCoreApis(page);
});

test("Indonesia VIP defaults to QRIS and completes after status polling", async ({
  page,
}) => {
  const payment = await registerIndonesiaPaymentMocks(page);
  await prepareIndonesiaUser(page);
  await page.goto("/subscription?type=vip&character=elio");

  await expect(page.getByText("Choose who you want to support")).toHaveCount(0);
  await expect(page.getByText("Supporting Elio")).toHaveCount(0);
  await expect(
    page.getByRole("button", { name: "Elio", exact: true }),
  ).toHaveCount(0);

  await expect(page.getByRole("button", { name: "QRIS" })).toHaveAttribute(
    "aria-pressed",
    "true",
  );
  const checkoutButton = page.getByRole("button", { name: "Pay and Top Up" });
  await expect(checkoutButton).toBeEnabled();
  await expectCheckoutButtonLayout(page);
  await expect(
    page.getByRole("dialog", { name: "Automatic Renewal Confirmation" }),
  ).toHaveCount(0);
  expect(payment.getCreateOrderCount()).toBe(0);

  const orderId = await expectQrisOrder(
    page,
    /Pay and Top Up/i,
    "vip_monthly",
    /Rp\s*195[.,]900/,
  );
  expect(payment.getCreateOrderCount()).toBe(1);
  payment.markPaid(orderId);

  const success = page.getByRole("alertdialog", { name: "Payment successful" });
  await expect(success).toBeVisible({ timeout: 10_000 });
  await expect(
    page.getByRole("dialog", { name: "Scan to pay with QRIS" }),
  ).toBeHidden();
});

test("Indonesia credit top-up uses QRIS display cents", async ({ page }) => {
  const payment = await registerIndonesiaPaymentMocks(page);
  await prepareIndonesiaUser(page);
  await page.goto("/subscription?type=topup&character=elio");
  await expect(page.getByText("Supporting Elio")).toHaveCount(0);
  await expectCheckoutButtonLayout(page);

  const orderId = await expectQrisOrder(
    page,
    /Pay and Top Up/i,
    "dol_1000",
    /Rp\s*88[.,]900/,
  );
  payment.markPaid(orderId);

  await expect(
    page.getByRole("alertdialog", { name: "Payment successful" }),
  ).toBeVisible({ timeout: 10_000 });
});

test("closing QRIS restores a resumable checkout without creating a duplicate order", async ({
  page,
}) => {
  const payment = await registerIndonesiaPaymentMocks(page);
  await prepareIndonesiaUser(page);
  await page.goto("/subscription?type=topup&character=elio");

  const orderId = await expectQrisOrder(
    page,
    /Pay and Top Up/i,
    "dol_1000",
    /Rp\s*88[.,]900/,
  );
  const dialog = page.getByRole("dialog", { name: "Scan to pay with QRIS" });
  await dialog.getByRole("button", { name: "Close" }).click();

  await expect(dialog).toBeHidden();
  const resumeButton = page.getByRole("button", {
    name: "Resume QRIS payment",
  });
  await expect(resumeButton).toBeEnabled();
  expect(payment.getCreateOrderCount()).toBe(1);

  await resumeButton.click();
  await expect(dialog).toBeVisible();
  expect(payment.getCreateOrderCount()).toBe(1);

  payment.markPaid(orderId);
  await expect(
    page.getByRole("alertdialog", { name: "Payment successful" }),
  ).toBeVisible({ timeout: 10_000 });
});

test("payment issue submits Other to the feedback API without creating an order", async ({
  page,
}) => {
  const payment = await registerIndonesiaPaymentMocks(page);
  await prepareIndonesiaUser(page);
  let feedbackBody = "";
  let idempotencyKey = "";
  await page.route("**/api/feedback", async (route) => {
    feedbackBody = route.request().postData() ?? "";
    idempotencyKey = route.request().headers()["idempotency-key"] ?? "";
    await route.fulfill({
      json: apiEnvelope({ feedbackId: "feedback-payment-1", duplicate: false }),
    });
  });

  await page.goto("/subscription?type=topup&character=elio");
  await page.getByRole("button", { name: "Payment issue?" }).click();
  const dialog = page.getByRole("dialog", {
    name: "What problem did you encounter?",
  });
  await expect(dialog).toBeVisible();
  await dialog.getByRole("radio", { name: "Other" }).check();
  await dialog
    .getByLabel("Please describe the issue")
    .fill("QRIS did not open correctly.");
  await dialog.getByRole("button", { name: "Submit" }).click();

  await expect(page.getByRole("status")).toHaveText(
    "Thanks. Your payment issue has been submitted.",
  );
  await expect(dialog).toBeHidden();
  expect(payment.getCreateOrderCount()).toBe(0);
  expect(idempotencyKey).toMatch(/^payment_feedback_[A-Za-z0-9_-]+$/);
  expect(feedbackBody).toContain('name="category"');
  expect(feedbackBody).toContain("payment");
  expect(feedbackBody).toContain(
    "Payment issue: Other\r\nDetails: QRIS did not open correctly.",
  );
  expect(feedbackBody).toContain('name="context"');
  expect(feedbackBody).toContain('"paymentIssueReason":"other"');
  expect(feedbackBody).toContain('"characterId":"elio"');
});

test("Indonesia gift checkout keeps IDR price through QRIS and thank-you flow", async ({
  page,
}) => {
  const payment = await registerIndonesiaPaymentMocks(page);
  await prepareIndonesiaUser(page);
  await page.goto("/characters/elio/tip");

  await expect(page.getByText(/IDR\s*89[.,]299/)).toBeVisible();
  const orderId = await expectQrisOrder(
    page,
    /Order and Buy/i,
    "tip_coffee_usd_4_99",
    /Rp\s*89[.,]299/,
  );
  const dialog = page.getByRole("dialog", { name: "Scan to pay with QRIS" });
  await dialog.getByRole("button", { name: "Close" }).click();
  await expect(dialog).toBeHidden();

  const resumeButton = page.getByRole("button", {
    name: "Resume QRIS payment",
  });
  await expect(resumeButton).toBeEnabled();
  await resumeButton.click();
  await expect(dialog).toBeVisible();
  expect(payment.getCreateOrderCount()).toBe(1);

  payment.markPaid(orderId);

  await expect(page.getByText("Gift received")).toBeVisible({ timeout: 10_000 });
  await expect(page.getByRole("button", { name: "Send another gift" })).toBeVisible();
  await expect(
    page.getByRole("dialog", { name: "Scan to pay with QRIS" }),
  ).toBeHidden();
});
