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

const mocks = vi.hoisted(() => {
  const characters = [
    { id: "elio", slug: "elio", displayName: "Elio Silvestri", shortName: "Elio" },
    { id: "maya-tan", slug: "maya", displayName: "Maya Tan", shortName: "Maya" },
    {
      id: "nayeli-cervantes",
      slug: "nayeli",
      displayName: "Nayeli Cervantes",
      shortName: "Nayeli",
    },
  ];
  const payment = {
    status: "ready",
    plans: [
      {
        planId: "vip_monthly",
        planName: "Monthly",
        orderType: "vip_monthly",
        vipDays: 30,
        dolAmount: null,
        amountCents: 1990,
        originalAmountCents: 3990,
        currency: "USD",
      },
      {
        planId: "vip_quarterly",
        planName: "Quarterly",
        orderType: "vip_quarterly",
        vipDays: 90,
        dolAmount: null,
        amountCents: 4990,
        originalAmountCents: 6990,
        currency: "USD",
      },
      {
        planId: "coin_1000",
        planName: "1000 Coins",
        orderType: "coins_1000",
        vipDays: null,
        dolAmount: 1000,
        amountCents: 990,
        originalAmountCents: null,
        currency: "USD",
      },
    ],
    isFirstRecharge: false,
    commercialOffer: null,
    selectedPlanId: "vip_monthly",
    payChannel: "stripe" as const,
    autoRenew: true,
    agreed: true,
    currentOrderId: null,
    isCreatingOrder: false,
    isPollingOrder: false,
    isLoadingPlans: false,
  };
  const paymentDispatch = vi.fn((event: { type: string; planId?: string }) => {
    if (event.type === "PaymentPlanSelected" && event.planId) {
      payment.selectedPlanId = event.planId;
    }
  });
  return {
    characterCatalog: {
      characters,
      getBySlug: (slug: string | null) =>
        characters.find((character) => character.slug === slug) ?? null,
    },
    payment,
    paymentDispatch,
    paymentFlowInput: vi.fn(),
    planClick: vi.fn(),
  };
});

vi.mock("@/app/_components", () => ({
  BackButton: () => <button type="button">Back</button>,
}));

vi.mock("@/app/_components/core", () => ({
  MobileShell: ({ children }: { children: React.ReactNode }) => <>{children}</>,
}));

vi.mock("@/app/_components/payment/payment-method-selector", () => ({
  PaymentMethodSelector: () => <div>Payment methods</div>,
}));

vi.mock("@/app/_hooks/use-payment-method-selection", () => ({
  usePaymentMethodSelection: () => undefined,
}));

vi.mock("@/app/_hooks/use-payment-plan-analytics", () => ({
  usePaymentPlanAnalytics: () => undefined,
}));

vi.mock("@/hooks/use-has-hydrated", () => ({
  useHasHydrated: () => true,
}));

vi.mock("@/stores/user/user-context", () => ({
  useUserState: () => ({
    currentUser: { id: "user-renewal-1", countryCode: "ID" },
  }),
}));

vi.mock("@/providers/character-catalog-provider", () => ({
  useCharacterCatalog: () => mocks.characterCatalog,
}));

vi.mock("@/lib/payment/payment_method", () => ({
  getPaymentMethodConfig: () => ({
    initialPayChannel: "stripe",
    showPaymentMethodSelector: true,
    ezpayDisplayName: "QRIS",
  }),
}));

vi.mock("@/lib/analytics", () => ({
  behaviorAnalytics: { planClick: mocks.planClick },
  getDefaultPaymentAnalyticsContext: () => ({}),
}));

vi.mock("../use-subscription-payment-flow", () => ({
  useSubscriptionPaymentFlow: (input: unknown) => {
    mocks.paymentFlowInput(input);
    return {
      payment: mocks.payment,
      paymentDispatch: mocks.paymentDispatch,
      showPaymentSuccessDialog: false,
      handleBackClick: vi.fn(),
      handlePaymentSuccessClose: vi.fn(),
    };
  },
}));

vi.mock("../components", () => ({
  SubscriptionVipOfferSection: ({
    plans,
    onSelectPlan,
  }: {
    plans: Array<{ id: string }>;
    onSelectPlan: (id: string) => void;
  }) => (
    <div>
      {plans.map((plan) => (
        <button key={plan.id} type="button" onClick={() => onSelectPlan(plan.id)}>
          {plan.id}
        </button>
      ))}
    </div>
  ),
  SubscriptionCoinsOfferSection: ({
    plans,
    onSelectPlan,
  }: {
    plans: Array<{ id: string }>;
    onSelectPlan: (id: string) => void;
  }) => (
    <div>
      {plans.map((plan) => (
        <button key={plan.id} type="button" onClick={() => onSelectPlan(plan.id)}>
          {plan.id}
        </button>
      ))}
    </div>
  ),
  SubscriptionCheckoutButton: ({
    disabled,
    renewalPlan,
    renewalConsentSubjectId,
  }: {
    disabled: boolean;
    renewalPlan: { id: string } | null;
    renewalConsentSubjectId: string | null;
  }) => (
    <button
      type="button"
      data-testid="checkout"
      data-renewal-plan={renewalPlan?.id ?? ""}
      data-renewal-subject={renewalConsentSubjectId ?? ""}
      disabled={disabled}
    >
      Pay and Top Up
    </button>
  ),
  SubscriptionPaymentIssueDialog: () => null,
  SubscriptionPaymentSuccessDialog: () => null,
}));

import { SubscriptionScreen } from "../subscription-screen";

describe("SubscriptionScreen payment selection flow", () => {
  let container: HTMLDivElement;
  let root: Root;

  beforeEach(() => {
    (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean })
      .IS_REACT_ACT_ENVIRONMENT = true;
    mocks.payment.isFirstRecharge = false;
    mocks.payment.commercialOffer = null;
    for (const plan of mocks.payment.plans) {
      delete (plan as Record<string, unknown>).isFirstRechargeOffer;
      delete (plan as Record<string, unknown>).firstRechargeDiscountPercent;
      delete (plan as Record<string, unknown>).promotionType;
    }
    mocks.payment.selectedPlanId = "vip_monthly";
    mocks.paymentDispatch.mockClear();
    mocks.paymentFlowInput.mockClear();
    mocks.planClick.mockClear();
    container = document.createElement("div");
    document.body.appendChild(container);
    root = createRoot(container);
  });

  afterEach(() => {
    act(() => root.unmount());
    container.remove();
  });

  it("allows immediate checkout and changes VIP plans without opening renewal confirmation", () => {
    act(() => root.render(<SubscriptionScreen sourceCharacterSlug="elio" />));
    const checkout = container.querySelector<HTMLButtonElement>(
      '[data-testid="checkout"]',
    );
    expect(checkout?.disabled).toBe(false);
    expect(checkout?.dataset.renewalPlan).toBe("vip_monthly");
    expect(checkout?.dataset.renewalSubject).toBe("user-renewal-1");

    act(() => clickButton(container, "vip_monthly"));
    expect(mocks.paymentDispatch).toHaveBeenCalledWith({
      type: "PaymentPlanSelected",
      planId: "vip_monthly",
    });
    expect(container.querySelector('[role="dialog"]')).toBeNull();

    act(() => clickButton(container, "vip_quarterly"));
    expect(container.querySelector('[role="dialog"]')).toBeNull();
    expect(mocks.paymentDispatch).toHaveBeenCalledWith({
      type: "PaymentPlanSelected",
      planId: "vip_quarterly",
    });
  });

  it("selects a coin package without showing the renewal dialog", () => {
    act(() => root.render(<SubscriptionScreen sourceCharacterSlug="elio" />));
    act(() => clickButton(container, "coin_1000"));

    expect(container.querySelector('[role="dialog"]')).toBeNull();
    expect(mocks.paymentDispatch).toHaveBeenCalledWith({
      type: "PaymentPlanSelected",
      planId: "coin_1000",
    });
    expect(mocks.paymentDispatch).not.toHaveBeenCalledWith(
      expect.objectContaining({ type: "PaymentCreateOrderSubmitted" }),
    );
  });

  it("keeps payment guidance bound to the source character", () => {
    act(() =>
      root.render(
        <SubscriptionScreen
          sourceCharacterSlug="maya"
          chatActionId="019c8f8d-17d3-7a42-b1cc-b6927b1927d5"
        />,
      ),
    );

    const guidance = container.querySelector(
      '[data-payment-guidance-character="maya-tan"]',
    );
    expect(guidance?.textContent).toContain("Keep talking with Maya");
    expect(guidance?.textContent).toContain("support Maya");
    expect(guidance?.textContent).toContain("cannot continue");
    expect(guidance?.textContent).not.toContain("Elio");
  });

  it("does not render the first recharge banner on the subscription page", () => {
    mocks.payment.isFirstRecharge = true;
    Object.assign(mocks.payment.plans[0] as Record<string, unknown>, {
      isFirstRechargeOffer: true,
      firstRechargeDiscountPercent: 50,
      promotionType: "first_recharge_half_price",
    });

    act(() => root.render(<SubscriptionScreen sourceCharacterSlug="elio" />));

    expect(container.textContent).not.toContain("First Recharge Offer");
    expect(container.textContent).not.toContain(
      "Your first recharge price is already applied",
    );
  });

  it.each(["elio", "maya", "nayeli"])(
    "inherits the %s chat character without rendering a recipient selector",
    (sourceCharacterSlug) => {
      mocks.payment.selectedPlanId = "coin_1000";

      act(() =>
        root.render(
          <SubscriptionScreen
            subscriptionType="topup"
            sourceCharacterSlug={sourceCharacterSlug}
          />,
        ),
      );

      const checkout = container.querySelector<HTMLButtonElement>(
        '[data-testid="checkout"]',
      );
      expect(container.textContent).not.toContain("Supporting ");
      expect(container.textContent).not.toContain(
        "Choose who you want to support",
      );
      expect(container.textContent).not.toContain(
        "Your VIP or credit purchase supports the character you choose",
      );
      expect(checkout?.disabled).toBe(false);
      expect(mocks.paymentFlowInput).toHaveBeenLastCalledWith(
        expect.objectContaining({ sourceCharacterSlug }),
      );
    },
  );

  it("does not invent a recipient when opened without a source character", () => {
    mocks.payment.selectedPlanId = "coin_1000";

    act(() =>
      root.render(
        <SubscriptionScreen
          subscriptionType="topup"
          sourceCharacterSlug={null}
        />,
      ),
    );

    const checkout = container.querySelector<HTMLButtonElement>(
      '[data-testid="checkout"]',
    );
    expect(container.textContent).not.toContain("Supporting ");
    expect(container.textContent).not.toContain(
      "Choose who you want to support",
    );
    expect(container.textContent).not.toContain(
      "Your VIP or credit purchase supports the character you choose",
    );
    expect(checkout?.disabled).toBe(true);
    expect(mocks.paymentFlowInput).toHaveBeenLastCalledWith(
      expect.objectContaining({ sourceCharacterSlug: null }),
    );
  });
});

function clickButton(root: ParentNode, label: string): void {
  const button = Array.from(root.querySelectorAll("button")).find(
    (item) => item.textContent?.trim() === label,
  );
  if (!button) throw new Error(`Expected ${label} button`);
  button.click();
}
