import type { ReactNode } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { beforeEach, describe, expect, it, vi } from "vitest";

import { getCharacterBySlug } from "@/data/constants/character";
import { CharacterProvider } from "@/providers/character-provider";

import { BrowserHintOverlay } from "../browser-hint-overlay";
import { ChatHeader } from "../chat-header";
import { ChatInsufficientCreditsBanner } from "../chat-insufficient-credits-banner";
import { ChatInputTextField } from "../chat-input-text-field";
import { ChatSendButton } from "../chat-send-button";
import { HistoryUnlockDialog } from "../history-unlock-dialog";
import { ImageBubble } from "../image-bubble";
import { InsufficientCreditsDialog } from "../insufficient-credits-dialog";
import { MessageAvatar } from "../message-avatar";
import { PrivateMessageCard } from "../private-message-card";
import { PaymentGuidanceCard } from "../payment-guidance-card";
import { PwaInstallDialog } from "../pwa-install-dialog";
import { TextBubble } from "../text-bubble";

const userMocks = vi.hoisted(() => ({
  avatarUrl: null as string | null,
}));

vi.mock("@/router/use-app-navigator", () => ({
  useAppNavigator: () => ({
    openSubscription: vi.fn(),
  }),
}));

vi.mock("@/stores/user/user-context", () => ({
  useUserSelector: (
    selector: (state: { context: { avatarUrl: string | null } }) => unknown,
  ) => selector({ context: { avatarUrl: userMocks.avatarUrl } }),
}));

describe("chat Tailwind components", () => {
  beforeEach(() => {
    userMocks.avatarUrl = null;
  });

  it("renders MessageAvatar AI and user branches with image utilities", () => {
    const aiHtml = renderWithCharacter(<MessageAvatar isFromAI={true} />);
    const userHtml = renderWithCharacter(
      <MessageAvatar isFromAI={false} userAvatarUrl="/user-avatar.png" />,
    );

    expect(aiHtml).toContain("size-(--chat-avatar-size,43px)");
    expect(aiHtml).toContain("size-full object-cover");
    expect(aiHtml).toContain("%2Fimages%2Favatar%2Felio.png");
    expect(userHtml).toContain('aria-label="User avatar"');
    expect(userHtml).toContain("%2Fuser-avatar.png");
  });

  it("renders message avatar actions with explicit destinations", () => {
    const aiHtml = renderWithCharacter(
      <MessageAvatar isFromAI={true} onClick={() => undefined} />,
    );
    const userHtml = renderWithCharacter(
      <MessageAvatar isFromAI={false} onClick={() => undefined} />,
    );

    expect(aiHtml).toContain('<button type="button"');
    expect(aiHtml).toContain(
      'data-analytics-key="chat.open_private_zone_from_avatar"',
    );
    expect(aiHtml).toContain(
      'aria-label="Open Elio Silvestri&#x27;s private zone"',
    );
    expect(userHtml).toContain('<button type="button"');
    expect(userHtml).toContain(
      'data-analytics-key="chat.open_profile_from_avatar"',
    );
    expect(userHtml).toContain('aria-label="Open profile"');
  });

  it("renders the active character avatar", () => {
    const maya = getCharacterBySlug("maya");
    expect(maya).not.toBeNull();
    const html = renderToStaticMarkup(
      <CharacterProvider character={maya!}>
        <MessageAvatar isFromAI={true} />
      </CharacterProvider>,
    );

    expect(html).toContain("%2Fimages%2Favatar%2Fmaya.png");
    expect(html).toContain('alt="Maya Tan"');
  });

  it("renders TextBubble AI and user visual variants", () => {
    const aiHtml = renderToStaticMarkup(
      <TextBubble content={"hello\nthere"} isFromAI={true} />,
    );
    const userHtml = renderToStaticMarkup(
      <TextBubble content="from user" isFromAI={false} />,
    );

    expect(aiHtml).toContain("whitespace-pre-wrap");
    expect(aiHtml).toContain("rounded-tl-none");
    expect(aiHtml).toContain("bg-white");
    expect(aiHtml).toContain("hello\nthere");
    expect(userHtml).toContain("rounded-tr-none");
    expect(userHtml).toContain(
      "bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))]",
    );
    expect(userHtml).toContain("from user");
  });

  it("renders PrivateMessageCard with fallback and loading states", () => {
    const fallbackHtml = renderWithCharacter(<PrivateMessageCard />);
    const unlockingHtml = renderWithCharacter(
      <PrivateMessageCard hint="Premium secret" isUnlocking onUnlock={() => undefined} />,
    );

    expect(fallbackHtml).toContain('role="group"');
    expect(fallbackHtml).toContain('aria-label="Locked private message"');
    expect(fallbackHtml).toContain("rounded-tl-none");
    expect(fallbackHtml).toContain("Elio has a private message for you.");
    expect(fallbackHtml).toContain("disabled");
    expect(unlockingHtml).toContain("Premium secret");
    expect(unlockingHtml).toContain("Unlocking...");
  });

  it("renders ChatSendButton visual states", () => {
    const activeHtml = renderToStaticMarkup(
      <ChatSendButton
        disabled={false}
        hasContent={true}
        isMenuOpen={false}
        menuId="chat-actions"
        onClick={() => undefined}
        onMenuToggle={() => undefined}
        onPointerDownSend={() => undefined}
      />,
    );
    const emptyHtml = renderToStaticMarkup(
      <ChatSendButton
        disabled={false}
        hasContent={false}
        isMenuOpen={false}
        menuId="chat-actions"
        onClick={() => undefined}
        onMenuToggle={() => undefined}
        onPointerDownSend={() => undefined}
      />,
    );
    const openHtml = renderToStaticMarkup(
      <ChatSendButton
        disabled={false}
        hasContent={false}
        isMenuOpen={true}
        menuId="chat-actions"
        onClick={() => undefined}
        onMenuToggle={() => undefined}
        onPointerDownSend={() => undefined}
      />,
    );
    const disabledHtml = renderToStaticMarkup(
      <ChatSendButton
        disabled={true}
        hasContent={true}
        isMenuOpen={false}
        menuId="chat-actions"
        onClick={() => undefined}
        onMenuToggle={() => undefined}
        onPointerDownSend={() => undefined}
      />,
    );

    expect(activeHtml).toContain('aria-label="Send message"');
    expect(activeHtml).toContain('data-analytics-ignore="true"');
    expect(activeHtml).not.toContain("data-analytics-key");
    expect(activeHtml).toContain("size-(--chat-send-button-size,42px)");
    expect(activeHtml).toContain(
      "bg-[linear-gradient(to_right,var(--color-button-gradient-start,#ff67e0),var(--color-button-gradient-end,#ff52a2))]",
    );
    expect(emptyHtml).toContain('aria-label="Open chat actions"');
    expect(emptyHtml).toContain('aria-expanded="false"');
    expect(emptyHtml).toContain('aria-controls="chat-actions"');
    expect(emptyHtml).toContain('data-analytics-key="chat.toggle_actions"');
    expect(openHtml).toContain('aria-label="Close chat actions"');
    expect(openHtml).toContain('aria-expanded="true"');
    expect(openHtml).toContain("bg-[#38262d]");
    expect(disabledHtml).toContain("disabled");
  });

  it("renders ImageBubble openable and paywalled states", () => {
    const openableHtml = renderToStaticMarkup(
      <ImageBubble
        characterId="elio"
        displayMessageId="server:message-1:assistant"
        remoteMessageId="message-1"
        imageUrl="/chat-image.png"
        onOpenImage={() => undefined}
      />,
    );
    const paywalledHtml = renderToStaticMarkup(
      <ImageBubble
        characterId="elio"
        displayMessageId="server:message-2:assistant"
        imageUrl="/locked-image.png"
        imagePaywalled
      />,
    );

    expect(openableHtml).toContain('role="button"');
    expect(openableHtml).toContain('tabindex="0"');
    expect(openableHtml).toContain('aria-label="Open image in fullscreen"');
    expect(openableHtml).toContain("rounded-tl-none");
    expect(openableHtml).toContain("block h-auto w-full object-cover");
    expect(openableHtml).toContain("data:image/png;base64,/chat-image.png");
    expect(paywalledHtml).toContain("scale-104 blur-sm");
    expect(paywalledHtml).toContain(
      "bg-[linear-gradient(180deg,rgba(255,255,255,0.04)_0%,rgba(255,255,255,0.14)_100%)]",
    );
    expect(paywalledHtml).toContain("data:image/png;base64,/locked-image.png");
  });

  it("renders ChatInsufficientCreditsBanner with split title and CTA", () => {
    const html = renderToStaticMarkup(
      <ChatInsufficientCreditsBanner
        title={"Need credits\nTop up now"}
        description="Credits keep the conversation going."
        ctaLabel="Top up"
        onUnlock={() => undefined}
        guidance={{
          guidanceId: "guidance-elio",
          characterId: "elio",
          characterName: "Elio Silvestri",
          scene: "insufficientCredits",
          mode: "guide",
          title: "Keep talking with Elio",
          copy: "Baby, I want to keep talking with you. Adding credits supports Elio.",
          ctaLabel: "View VIP & credit options",
          purchaseOptions: ["vip", "topup"],
          target: "subscription",
          ruleId: "payment_guidance_insufficientCredits",
        }}
      />,
    );

    expect(html).toContain('role="status"');
    expect(html).toContain('aria-live="polite"');
    expect(html).toContain("Need credits");
    expect(html).toContain("<br/>");
    expect(html).toContain("Top up now");
    expect(html).toContain("Credits keep the conversation going.");
    expect(html).toContain("supports Elio");
    expect(html).toContain('data-payment-guidance-id="guidance-elio"');
    expect(html).toContain('aria-label="Top up"');
    expect(html).toContain("bg-[linear-gradient(135deg,#f96ade_0%,#f657a0_100%)]");
    expect(html).toContain("active:scale-98");
  });

  it("renders guidance only for the active character and hides care-only CTA", () => {
    const guide = {
      guidanceId: "019c8f8d-17d3-7a42-b1cc-b6927b1927d5",
      characterId: "elio",
      characterName: "Elio Silvestri",
      scene: "insufficientCredits" as const,
      mode: "guide" as const,
      title: "Keep talking with Elio",
      copy: "Baby, I want to keep talking with you.",
      ctaLabel: "View VIP & credit options",
      purchaseOptions: ["vip", "topup"] as const,
      target: "subscription" as const,
      ruleId: "payment_guidance_insufficientCredits",
    };
    const guideHtml = renderWithCharacter(
      <PaymentGuidanceCard guidance={guide} />,
    );
    const mismatchHtml = renderWithCharacter(
      <PaymentGuidanceCard
        guidance={{ ...guide, characterId: "maya-tan", characterName: "Maya Tan" }}
      />,
    );
    const careHtml = renderWithCharacter(
      <PaymentGuidanceCard
        guidance={{
          ...guide,
          mode: "careOnly",
          title: null,
          ctaLabel: null,
          purchaseOptions: [],
          target: null,
          copy: "Please keep money you need for food or medicine.",
        }}
      />,
    );

    expect(guideHtml).toContain('data-payment-guidance-mode="guide"');
    expect(guideHtml).toContain("View VIP &amp; credit options");
    expect(mismatchHtml).toBe("");
    expect(careHtml).toContain("food or medicine");
    expect(careHtml).not.toContain("View VIP");
  });

  it("renders ChatHeader guest and member branches", () => {
    const guestHtml = renderWithCharacter(<ChatHeader isGuest={true} />);
    const memberHtml = renderWithCharacter(
      <ChatHeader isGuest={false} offerBanner={<div>Offer</div>} />,
    );

    expect(guestHtml).toContain('aria-label="Sign up to unlock more features"');
    expect(guestHtml).toContain("bg-accent");
    expect(guestHtml).toContain("size-(--icon-size-sm,16px)");
    expect(guestHtml).toContain('href="/characters/elio/splash"');
    expect(guestHtml).toContain('aria-label="Back to home"');
    expect(guestHtml).toContain('data-analytics-key="chat.back_to_home"');
    expect(guestHtml).toContain("px-(--chat-inline-padding,16px)");
    expect(guestHtml).not.toContain('aria-label="Profile"');
    expect(guestHtml).toContain('aria-label="Save CozSweet"');
    expect(memberHtml).toContain("Offer");
    expect(memberHtml).toContain('href="/characters/elio/splash"');
    expect(memberHtml).toContain('aria-label="Back to home"');
    expect(memberHtml).toContain('aria-label="Save CozSweet"');
    expect(memberHtml).toContain('data-analytics-key="favorite.favorite"');
    expect(memberHtml).toContain('data-analytics-key="chat.back_to_home"');
    expect(memberHtml).toContain("px-(--chat-inline-padding,16px)");
    expect(memberHtml).not.toContain("px-(--spacing-md,12px)");
    expect(memberHtml).toContain(
      "grid-cols-[auto_minmax(0,1fr)_auto]",
    );
    expect(memberHtml).not.toContain(
      'data-analytics-key="chat.external_browser_hint"',
    );
  });

  it("replaces the ChatHeader Profile avatar with the favorite action", () => {
    userMocks.avatarUrl = "/images/avatar/profile-user.png";

    const html = renderWithCharacter(<ChatHeader isGuest={false} />);

    expect(html).not.toContain("%2Fimages%2Favatar%2Fprofile-user.png");
    expect(html).not.toContain('aria-label="Profile"');
    expect(html).not.toContain('data-analytics-key="chat.open_profile"');
    expect(html).toContain('aria-label="Save CozSweet"');
    expect(html).toContain("lucide-star");
  });

  it("links guest chat back to the active character splash", () => {
    const maya = getCharacterBySlug("maya");
    if (!maya) throw new Error("Missing Maya character fixture");

    const html = renderToStaticMarkup(
      <CharacterProvider character={maya}>
        <ChatHeader isGuest={true} />
      </CharacterProvider>,
    );

    expect(html).toContain('href="/characters/maya/splash"');
    expect(html).not.toContain('aria-label="Profile"');
    expect(html).toContain('aria-label="Save CozSweet"');
    expect(html).not.toContain(
      'data-analytics-key="chat.external_browser_hint"',
    );
  });

  it("renders ChatInputTextField with textarea utilities", () => {
    const html = renderToStaticMarkup(
      <ChatInputTextField
        value="Hello"
        onChange={() => undefined}
        onSubmit={() => undefined}
        placeholder="Message Elio"
      />,
    );

    expect(html).toContain("<textarea");
    expect(html).toContain('aria-label="Message"');
    expect(html).toContain("min-h-(--chat-send-button-size,40px)");
    expect(html).toContain("max-h-[min(30vh,120px)]");
    expect(html).toContain("caret-accent");
    expect(html).toContain("placeholder:text-(--color-text-hint,#757575)");
    expect(html).toContain("Message Elio");
    expect(html).toContain("Hello");
  });

  it("renders BrowserHintOverlay visibility and expanded styles", () => {
    const html = renderToStaticMarkup(
      <BrowserHintOverlay
        title="Open elsewhere"
        description="Better experience"
      />,
    );

    expect(html).not.toContain("aria-expanded");
    expect(html).toContain('aria-label="Open elsewhere. Better experience"');
    expect(html).toContain("grid-cols-[auto_minmax(0,1fr)]");
    expect(html).not.toContain("absolute");
    expect(html).toContain("max-w-full");
    expect(html).toContain("size-7.5");
    expect(html).toContain("bg-[#17131f]");
    expect(html).toContain("text-[#f3dfe8]");
    expect(html).not.toContain("Show external browser hint");
    expect(html).toContain("Open elsewhere");
    expect(html).toContain("Better experience");
  });

  it("renders PwaInstallDialog with Tailwind dialog layout", () => {
    const html = renderToStaticMarkup(
      <PwaInstallDialog onClose={() => undefined} onInstall={() => undefined} />,
    );

    expect(html).toContain('role="dialog"');
    expect(html).toContain('aria-modal="true"');
    expect(html).toContain('aria-labelledby="pwa-dialog-title"');
    expect(html).toContain("fixed inset-0");
    expect(html).toContain("max-w-(--pwa-install-dialog-max-width,360px)");
    expect(html).toContain("size-(--icon-size-86,86px)");
    expect(html).toContain("min-h-(--pwa-button-height,44px)");
    expect(html).toContain("text-(--color-pwa-button-text,#ffffff)");
    expect(html).toContain("%2Fimages%2Ficons%2Ficon-add-to-home.png");
    expect(html).toContain("Add to Home Screen");
    expect(html).toContain("Cancel");
    expect(html).toContain("OK");
  });

  it("renders HistoryUnlockDialog open and loading states", () => {
    expect(
      renderToStaticMarkup(
        <HistoryUnlockDialog
          open={false}
          lockedCount={2}
          onClose={() => undefined}
          onConfirm={() => undefined}
        />,
      ),
    ).toBe("");

    const html = renderToStaticMarkup(
      <HistoryUnlockDialog
        open
        lockedCount={3}
        isLoading
        errorMessage="Could not unlock history."
        onClose={() => undefined}
        onConfirm={() => undefined}
      />,
    );

    expect(html).toContain('aria-labelledby="history-unlock-title"');
    expect(html).toContain("z-200");
    expect(html).toContain("Unlock previous messages?");
    expect(html).toContain("We found 3 locked messages");
    expect(html).toContain("Could not unlock history.");
    expect(html).toContain("Unlocking...");
    expect(html).toContain('data-analytics-key="chat.unlock_history"');
    expect(html).toContain("text-(--color-pwa-button-text,#ffffff)");
    expect(html).toContain("disabled");
  });

  it("renders InsufficientCreditsDialog with credit details", () => {
    expect(
      renderToStaticMarkup(
        <InsufficientCreditsDialog
          open={false}
          creditBalance={0}
          requiredCredits={0}
          shortfallCredits={0}
          onClose={() => undefined}
          onConfirm={() => undefined}
        />,
      ),
    ).toBe("");

    const html = renderToStaticMarkup(
      <InsufficientCreditsDialog
        open
        creditBalance={2}
        requiredCredits={5}
        shortfallCredits={3}
        onClose={() => undefined}
        onConfirm={() => undefined}
      />,
    );

    expect(html).toContain('aria-labelledby="insufficient-credits-title"');
    expect(html).toContain("z-220");
    expect(html).toContain("Not enough credits");
    expect(html).toContain("Balance");
    expect(html).toContain("Required");
    expect(html).toContain("Still needed");
    expect(html).toContain("text-(--color-pwa-button-text,#ffffff)");
    expect(html).toContain("bg-[linear-gradient(90deg,#ff67e0_0%,#ff52a2_100%)]");
    expect(html).toContain("Top up now");
    expect(html).toContain('data-analytics-key="chat.topup"');
  });

});

function renderWithCharacter(node: ReactNode, slug = "elio"): string {
  const character = getCharacterBySlug(slug);
  if (!character) throw new Error(`Missing ${slug} character fixture`);
  return renderToStaticMarkup(
    <CharacterProvider character={character}>{node}</CharacterProvider>,
  );
}
