import { act } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

const stripeConfirmPayment = vi.fn(async () => ({}));
const elementsSubmit = vi.fn(async () => ({}));
let capturedElementsOptions: Record<string, unknown> | null = null;
let capturedExpressProps: Record<string, unknown> | null = null;
let capturedPaymentProps: Record<string, unknown> | null = null;

vi.hoisted(() => {
  process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = "pk_test_checkout";
});

vi.mock("@stripe/stripe-js", () => ({
  loadStripe: vi.fn(() => Promise.resolve({})),
}));

vi.mock("@stripe/react-stripe-js", () => ({
  Elements: ({
    children,
    options,
  }: {
    children: React.ReactNode;
    options: Record<string, unknown>;
  }) => {
    capturedElementsOptions = options;
    return <>{children}</>;
  },
  ExpressCheckoutElement: (props: Record<string, unknown>) => {
    capturedExpressProps = props;
    return <div data-testid="express-checkout" />;
  },
  PaymentElement: (props: Record<string, unknown>) => {
    capturedPaymentProps = props;
    return <div data-testid="payment-element" />;
  },
  useElements: () => ({ submit: elementsSubmit }),
  useStripe: () => ({ confirmPayment: stripeConfirmPayment }),
}));

import { StripePaymentDialog } from "../stripe-payment-dialog";

describe("StripePaymentDialog", () => {
  let container: HTMLDivElement;
  let root: Root;

  beforeEach(() => {
    vi.clearAllMocks();
    capturedElementsOptions = null;
    capturedExpressProps = null;
    capturedPaymentProps = null;
    (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean })
      .IS_REACT_ACT_ENVIRONMENT = true;
    container = document.createElement("div");
    document.body.appendChild(container);
    root = createRoot(container);
  });

  afterEach(() => {
    act(() => root.unmount());
    container.remove();
    document.body.style.overflow = "";
  });

  it("renders Express Checkout first and a collapsed Pay by card section last", () => {
    act(() =>
      root.render(
        <StripePaymentDialog
          clientSecret="pi_123_secret_payment"
          customerSessionClientSecret="cuss_123_secret_saved"
          savedPaymentMethodsEnabled
          onClose={vi.fn()}
        />,
      ),
    );

    expect(capturedElementsOptions).toMatchObject({
      clientSecret: "pi_123_secret_payment",
      customerSessionClientSecret: "cuss_123_secret_saved",
    });
    expect(capturedExpressProps?.options).toMatchObject({
      paymentMethodOrder: [
        "apple_pay",
        "google_pay",
        "link",
        "paypal",
        "amazon_pay",
        "klarna",
      ],
      paymentMethods: {
        applePay: "always",
        googlePay: "always",
        link: "auto",
        paypal: "auto",
        amazonPay: "auto",
        klarna: "auto",
      },
    });
    expect(capturedPaymentProps?.options).toMatchObject({
      layout: { type: "accordion", defaultCollapsed: true },
      paymentMethodOrder: ["card"],
      wallets: { applePay: "never", googlePay: "never", link: "never" },
    });
    const text = document.body.textContent ?? "";
    expect(text).toContain("Pay by card");
    const express = document.body.querySelector('[data-testid="express-checkout"]');
    const card = document.body.querySelector('[data-testid="payment-element"]');
    expect(express).not.toBeNull();
    expect(card).not.toBeNull();
    expect(
      express!.compareDocumentPosition(card!) &
        Node.DOCUMENT_POSITION_FOLLOWING,
    ).toBeTruthy();
  });

  it("does not pass an invalid or disabled Customer Session secret", () => {
    act(() =>
      root.render(
        <StripePaymentDialog
          clientSecret="pi_123_secret_payment"
          customerSessionClientSecret="invalid"
          savedPaymentMethodsEnabled={false}
          onClose={vi.fn()}
        />,
      ),
    );

    expect(capturedElementsOptions).not.toHaveProperty(
      "customerSessionClientSecret",
    );
  });

  it("uses the same confirmation path for an Express Checkout approval", async () => {
    const onConfirmed = vi.fn();
    act(() =>
      root.render(
        <StripePaymentDialog
          clientSecret="pi_123_secret_payment"
          onClose={vi.fn()}
          onConfirmed={onConfirmed}
        />,
      ),
    );

    const onConfirm = capturedExpressProps?.onConfirm as
      | ((event: { paymentFailed: ReturnType<typeof vi.fn> }) => Promise<void>)
      | undefined;
    expect(onConfirm).toBeTypeOf("function");
    await act(async () => {
      await onConfirm?.({ paymentFailed: vi.fn() });
    });

    expect(stripeConfirmPayment).toHaveBeenCalledTimes(1);
    expect(stripeConfirmPayment).toHaveBeenCalledWith(
      expect.objectContaining({
        elements: expect.any(Object),
        redirect: "if_required",
      }),
    );
    expect(onConfirmed).toHaveBeenCalledTimes(1);
  });
});
