/* @vitest-environment jsdom */

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

import {
  GiftCategorySchema,
  GiftProductSchema,
  PaymentPlanSchema,
  TipMessageResponseSchema,
  type PaymentPlan,
} from "@/data/schemas/payment";
import type { PaymentContextState } from "@/stores/payment/payment-context";

const mocks = vi.hoisted(() => ({
  payment: null as unknown as PaymentContextState,
  paymentDispatch: vi.fn(),
  planClick: vi.fn(),
}));

vi.mock("@/app/_components", () => ({
  BackButton: () => <span data-testid="tip-back-button" />,
  CharacterAvatar: ({ alt }: { alt: string }) => (
    <span data-testid="tip-character-avatar">{alt}</span>
  ),
}));
vi.mock("@/app/_components/core", () => ({
  MobileShell: ({ children }: { children: React.ReactNode }) => children,
}));
vi.mock("@/app/_components/payment/payment-method-selector", () => ({
  PaymentMethodSelector: ({ density }: { density?: string }) => (
    <span data-testid="tip-payment-method" data-density={density} />
  ),
}));
vi.mock("@/app/_hooks/use-payment-method-selection", () => ({
  usePaymentMethodSelection: () => undefined,
}));
vi.mock("@/app/_hooks/use-payment-plan-analytics", () => ({
  usePaymentPlanAnalytics: () => undefined,
}));
vi.mock("@/app/_hooks/use-payment-route-flow", () => ({
  usePaymentRouteFlow: () => ({
    payment: mocks.payment,
    paymentDispatch: mocks.paymentDispatch,
  }),
}));
vi.mock("@/lib/analytics", () => ({
  behaviorAnalytics: { planClick: mocks.planClick },
}));
vi.mock("@/providers/character-provider", () => ({
  useActiveCharacter: () => ({
    id: "maya-tan",
    slug: "maya",
    displayName: "Maya Tan",
    assets: {
      avatar: "/images/avatar/maya.png",
      cover: "/images/cover/maya.png",
    },
    copy: {
      tipHeader: "Tip Maya",
      tipTitle: "Send Maya a gift",
    },
  }),
  useActiveCharacterRoutes: () => ({
    splash: "/characters/maya/splash",
    tip: "/characters/maya/tip",
  }),
}));
vi.mock("@/stores/user/user-context", () => ({
  useUserState: () => ({ currentUser: null }),
}));
vi.mock("../tip-checkout-button", () => ({
  TipCheckoutButton: ({
    disabled,
    onOrder,
  }: {
    disabled?: boolean;
    onOrder: () => void;
  }) => (
    <button
      type="button"
      data-testid="tip-checkout"
      disabled={disabled}
      onClick={onOrder}
    >
      Checkout
    </button>
  ),
}));
vi.mock("../tip-gift-product-selector", () => ({
  TipGiftProductSelector: ({
    products,
  }: {
    products: readonly { planId: string; planName: string }[];
  }) => (
    <span data-testid="tip-product-selector">
      {products.map((product) => product.planName).join(",")}
    </span>
  ),
}));
vi.mock("../tip-product-image", () => ({
  TipProductImage: ({ alt }: { alt: string }) => (
    <span data-testid="tip-product-image">{alt}</span>
  ),
}));
vi.mock("../use-tip-support-prompt", () => ({
  useTipSupportPrompt: () => ({
    prompt: "A warm coffee prompt.",
    isReady: true,
  }),
}));

import { TipScreen } from "../tip-screen";

const giftCategory = GiftCategorySchema.parse({
  category: "coffee",
  name: "Coffee",
  productCount: 1,
  imageUrl: null,
});

const giftProduct = GiftProductSchema.parse({
  planId: "tip_coffee_usd_9_99",
  planName: "Golden Reserve",
  orderType: "tip",
  tipType: "coffee_medium",
  category: "coffee",
  characterId: "maya-tan",
  description: "A warm reserve coffee",
  imageUrl: null,
  amountCents: 999,
  currency: "USD",
  autoRenew: false,
  isFirstRechargeOffer: false,
  firstRechargeDiscountPercent: 0,
  promotionType: null,
});

const giftPlan: PaymentPlan = PaymentPlanSchema.parse({
  planId: giftProduct.planId,
  planName: giftProduct.planName,
  orderType: "tip",
  vipDays: null,
  dolAmount: null,
  creditBalance: 0,
  amountCents: giftProduct.amountCents,
  originalAmountCents: null,
  dailyPriceCents: null,
  currency: giftProduct.currency,
});

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

  beforeEach(() => {
    (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean })
      .IS_REACT_ACT_ENVIRONMENT = true;
    mocks.payment = makePaymentState();
    mocks.paymentDispatch.mockReset();
    mocks.planClick.mockReset();
    container = document.createElement("div");
    document.body.append(container);
    root = createRoot(container);
  });

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

  it("creates a dynamic character-attributed gift order", () => {
    renderScreen();
    const checkout = getCheckoutButton();

    expect(container.textContent).toContain("Golden Reserve");
    expect(checkout.disabled).toBe(false);
    act(() => checkout.click());

    expect(mocks.planClick).toHaveBeenCalledWith(
      giftPlan,
      expect.objectContaining({ entryPoint: "tip_page" }),
    );
    expect(mocks.paymentDispatch).toHaveBeenCalledWith({
      type: "PaymentCreateOrderSubmitted",
      recipientCharacterId: "maya-tan",
    });
  });

  it("renders the enlarged purchase flow without a character Cover", () => {
    renderScreen();

    expect(container.textContent).toContain("Send Maya a gift");
    expect(container.textContent).toContain("A warm coffee prompt.");
    expect(container.querySelector('[data-testid="tip-back-button"]')).not.toBeNull();
    expect(container.querySelector('[data-testid="tip-character-avatar"]')).not.toBeNull();
    expect(container.querySelector('[data-testid="tip-product-image"]')).not.toBeNull();
    expect(container.querySelector('[data-testid="tip-product-selector"]')).not.toBeNull();
    expect(
      container
        .querySelector('[data-testid="tip-payment-method"]')
        ?.getAttribute("data-density"),
    ).toBe("compact");
    expect(container.textContent).not.toContain("A little sweetness for today");
    expect(container.textContent).not.toContain("Tip Maya");
    expect(container.querySelector("main")?.getAttribute("style")).toBeNull();
    expect(
      container.querySelectorAll('main > div[aria-hidden="true"]'),
    ).toHaveLength(2);
  });

  it.each([
    ["an order is being created", { isCreatingOrder: true }],
    ["an order is being polled", { isPollingOrder: true }],
  ] as const)("disables checkout when %s", (_label, overrides) => {
    mocks.payment = makePaymentState(overrides);
    renderScreen();
    expect(getCheckoutButton().disabled).toBe(true);
  });

  it.each([
    ["catalog is loading", { isLoadingPlans: true }],
    [
      "the catalog is empty",
      { plans: [], giftProducts: [], selectedPlanId: "" },
    ],
  ] as const)("hides payment controls when %s", (_label, overrides) => {
    mocks.payment = makePaymentState(overrides);
    renderScreen();

    expect(container.querySelector('[data-testid="tip-checkout"]')).toBeNull();
    expect(container.querySelector('[data-testid="tip-payment-method"]')).toBeNull();
  });

  it("shows a compact retry state when the catalog fails", () => {
    mocks.payment = makePaymentState({
      plans: [],
      giftProducts: [],
      selectedPlanId: "",
      errorMessage: "catalog unavailable",
    });
    renderScreen();

    expect(container.textContent).toContain(
      "We could not load gifts for this character.",
    );
    act(() => getButton("Try again").click());
    expect(mocks.paymentDispatch).toHaveBeenCalledWith({
      type: "PaymentCatalogRetryRequested",
    });
  });

  it("shows fixed copy for the first character Tip", () => {
    mocks.payment = makePaymentState({
      status: "paid",
      isPaid: true,
      orderStatus: "paid",
      tipMessage: TipMessageResponseSchema.parse({
        orderId: "pay_first",
        characterId: "maya-tan",
        planId: giftProduct.planId,
        productName: giftProduct.planName,
        tipCount: 1,
        poolIndex: 4,
        message: "This backend message is ignored for the first Tip.",
      }),
    });
    renderScreen();

    expect(container.textContent).toContain(
      "Did you really just buy me a coffee?",
    );
    expect(container.textContent).toContain("That honestly made me smile.");
    expect(container.textContent).toContain(
      "I'll definitely think of you while I enjoy it.",
    );
    expect(container.textContent).not.toContain(
      "This backend message is ignored for the first Tip.",
    );
  });

  it("shows the backend message after the first Tip", () => {
    mocks.payment = makePaymentState({
      status: "paid",
      isPaid: true,
      orderStatus: "paid",
      tipMessage: TipMessageResponseSchema.parse({
        orderId: "pay_xxx",
        characterId: "maya-tan",
        planId: giftProduct.planId,
        productName: giftProduct.planName,
        tipCount: 2,
        poolIndex: 17,
        message: "This complete message came directly from the backend.",
      }),
    });
    renderScreen();

    expect(container.querySelector('[data-testid="tip-checkout"]')).toBeNull();
    expect(container.textContent).toContain(
      "This complete message came directly from the backend.",
    );
    expect(container.textContent).not.toContain(
      "Did you really just buy me a coffee?",
    );
    expect(document.activeElement?.id).toBe("tip-success-title");

    act(() => getButton("Send another gift").click());
    expect(mocks.paymentDispatch).toHaveBeenCalledWith({ type: "PaymentReset" });
  });

  it("keeps paid success visible and retries only a failed message", () => {
    mocks.payment = makePaymentState({
      status: "tipMessageFailed",
      isPaid: true,
      orderStatus: "paid",
      tipMessageError: "message unavailable",
    });
    renderScreen();

    expect(container.textContent).toContain("Thank you. Your gift made me smile.");
    act(() => getButton("Retry message").click());
    expect(mocks.paymentDispatch).toHaveBeenCalledWith({
      type: "PaymentTipMessageRetryRequested",
    });
  });

  function renderScreen(): void {
    act(() => root.render(<TipScreen />));
  }

  function getCheckoutButton(): HTMLButtonElement {
    const button = container.querySelector<HTMLButtonElement>(
      '[data-testid="tip-checkout"]',
    );
    if (!button) throw new Error("Missing Tip checkout button");
    return button;
  }

  function getButton(label: string): HTMLButtonElement {
    const button = Array.from(
      container.querySelectorAll<HTMLButtonElement>("button"),
    ).find((item) => item.textContent?.trim() === label);
    if (!button) throw new Error(`Missing button: ${label}`);
    return button;
  }
});

function makePaymentState(
  overrides: Partial<PaymentContextState> = {},
): PaymentContextState {
  return {
    status: "ready",
    planCatalog: "tip",
    plans: [giftPlan],
    giftCategories: [giftCategory],
    giftProducts: [giftProduct],
    selectedGiftCategory: giftCategory.category,
    isFirstRecharge: false,
    selectedPlanId: giftPlan.planId,
    payChannel: "stripe",
    autoRenew: false,
    agreed: true,
    currentOrderId: null,
    payParams: null,
    orderStatus: null,
    tipMessage: null,
    tipMessageError: null,
    errorMessage: null,
    launchNonce: 0,
    isLoadingPlans: false,
    isLoadingTipMessage: false,
    isCreatingOrder: false,
    isPollingOrder: false,
    isPaid: false,
    ...overrides,
  };
}
