import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import type { ReactNode } from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";

import { getFlowById } from "@/lib/onboarding";
import { localStorageAdapter } from "@/lib/tours/store/local-storage-adapter";
import { usePageReadyStore } from "@/store/page-ready";
import { SIDE_PANEL_TAB, useSidePanelStore } from "@/store/side-panel";

import { FeedsLoadingFallback, NavbarClient } from "./navbar-client";

const navigationMocks = vi.hoisted(() => ({
  pathname: "/findings",
  push: vi.fn(),
  searchParams: new URLSearchParams(),
}));

const requestReplayMock = vi.hoisted(() => vi.fn());

vi.mock("next/navigation", () => ({
  usePathname: () => navigationMocks.pathname,
  useRouter: () => ({ push: navigationMocks.push }),
  useSearchParams: () => navigationMocks.searchParams,
}));

vi.mock("@/store/onboarding-replay", () => {
  const state = { requestReplay: requestReplayMock };
  const hook = (selector: (s: typeof state) => unknown) => selector(state);
  hook.getState = () => state;
  return { useOnboardingReplayStore: hook };
});

vi.mock("@/components/ThemeSwitch", () => ({
  ThemeSwitch: () => <button type="button">Theme switch</button>,
}));

vi.mock("@/components/shadcn", async (importOriginal) => ({
  ...(await importOriginal<Record<string, unknown>>()),
  BreadcrumbNavigation: ({
    title,
    titleAction,
  }: {
    title: string;
    titleAction?: ReactNode;
  }) => (
    <nav aria-label="Breadcrumb">
      <h1>{title}</h1>
      {titleAction}
    </nav>
  ),
}));

vi.mock("@/components/layout/app-sidebar", () => ({
  MobileAppSidebar: () => <button type="button">Open menu</button>,
}));

vi.mock("../user-nav/user-nav", () => ({
  UserNav: () => <button type="button">User menu</button>,
}));

describe("NavbarClient", () => {
  beforeEach(() => {
    navigationMocks.pathname = "/findings";
    navigationMocks.push.mockClear();
    requestReplayMock.mockClear();
    navigationMocks.searchParams = new URLSearchParams();
    window.localStorage.clear();
    // Replay icon is Cloud-only.
    vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "true");
    // Default: the current route's content has loaded, so the icon is enabled.
    usePageReadyStore.setState({ readyPath: "/findings" });
    useSidePanelStore.setState({
      isOpen: false,
      selectedTab: SIDE_PANEL_TAB.AI_CHAT,
    });
  });

  it("renders an accessible contextual onboarding button in the breadcrumb", async () => {
    // Given
    const user = userEvent.setup();
    render(
      <NavbarClient
        title="Findings"
        onboardingAction={{ flowId: "explore-findings" }}
      />,
    );

    // When
    await user.hover(
      screen.getByRole("button", {
        name: /start product tour: explore your findings/i,
      }),
    );

    // Then
    expect(
      screen.getByRole("heading", { name: "Findings" }),
    ).toBeInTheDocument();
    expect(screen.getAllByText("See how it works")).not.toHaveLength(0);
    expect(
      screen.getByRole("button", {
        name: /start product tour: explore your findings/i,
      }),
    ).toHaveClass("size-7");
  });

  it("pulses the onboarding icon while the tour has not been seen", () => {
    render(
      <NavbarClient
        title="Findings"
        onboardingAction={{ flowId: "explore-findings" }}
      />,
    );

    const icon = screen
      .getByRole("button", {
        name: /start product tour: explore your findings/i,
      })
      .querySelector("svg");
    expect(icon).toHaveClass("animate-pulse");
  });

  it("keeps the onboarding button but stops pulsing once the tour is closed", () => {
    const flow = getFlowById("explore-findings");
    localStorageAdapter.set(flow!.tour, {
      tourId: flow!.tour.id,
      version: flow!.tour.version,
      state: "dismissed",
      completedAt: "2026-01-01T00:00:00.000Z",
    });

    render(
      <NavbarClient
        title="Findings"
        onboardingAction={{ flowId: "explore-findings" }}
      />,
    );

    const button = screen.getByRole("button", {
      name: /start product tour: explore your findings/i,
    });
    expect(button).toBeInTheDocument();
    expect(button.querySelector("svg")).not.toHaveClass("animate-pulse");
  });

  it("starts the replay in-memory (no navigation) when already on the flow's route", async () => {
    // Given the user is already on the flow's page
    navigationMocks.pathname = "/compliance";
    navigationMocks.searchParams = new URLSearchParams("scanId=scan-1&foo=bar");
    usePageReadyStore.setState({ readyPath: "/compliance" });
    const user = userEvent.setup();
    render(
      <NavbarClient
        title="Compliance"
        onboardingAction={{ flowId: "view-compliance" }}
      />,
    );

    // When
    await user.click(
      screen.getByRole("button", {
        name: /start product tour: check compliance/i,
      }),
    );

    // Then the replay is requested via the in-memory store — no router.push, so no
    // `?onboarding=` URL param and no Next.js RSC refetch of the page.
    expect(requestReplayMock).toHaveBeenCalledWith("view-compliance");
    expect(navigationMocks.push).not.toHaveBeenCalled();
  });

  it("navigates to the fallback flow without leaking current-route params", async () => {
    // Given
    navigationMocks.pathname = "/findings";
    navigationMocks.searchParams = new URLSearchParams(
      "filter%5Bseverity__in%5D=critical",
    );
    const user = userEvent.setup();
    render(
      <NavbarClient
        title="Findings"
        onboardingAction={{
          flowId: "explore-findings",
          fallbackFlowId: "view-first-scan",
          useFallback: true,
        }}
      />,
    );

    // When
    await user.click(
      screen.getByRole("button", {
        name: /start product tour: run your first scan/i,
      }),
    );

    // Then: the flow's own route (incl. its `?tab=active`) is preserved and the
    // onboarding param is appended with `&` — the current /findings filter is dropped.
    expect(navigationMocks.push).toHaveBeenCalledWith(
      "/scans?tab=active&onboarding=view-first-scan",
    );
  });

  it("hides the replay icon until the route's content has loaded", () => {
    // Given the page has not signalled ready for the current route
    usePageReadyStore.setState({ readyPath: null });

    // When
    render(
      <NavbarClient
        title="Findings"
        onboardingAction={{ flowId: "explore-findings" }}
      />,
    );

    // Then the icon is not rendered at all
    expect(
      screen.queryByRole("button", {
        name: /start product tour: explore your findings/i,
      }),
    ).not.toBeInTheDocument();
  });

  it("hides the replay icon entirely in self-hosted (OSS) deployments", () => {
    vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");

    render(
      <NavbarClient
        title="Findings"
        onboardingAction={{ flowId: "explore-findings" }}
      />,
    );
    expect(
      screen.queryByRole("button", { name: /start product tour/i }),
    ).not.toBeInTheDocument();
  });

  it("shows the Lighthouse AI side-panel trigger in cloud", () => {
    // Given / When
    render(<NavbarClient title="Findings" />);

    // Then
    expect(screen.getByTestId("side-panel-ai-trigger")).toBeInTheDocument();
    expect(
      screen.getByRole("button", { name: "Ask Lighthouse AI" }),
    ).toBeInTheDocument();
  });

  it("hides the Lighthouse AI trigger while the AI chat panel is already open", () => {
    // Given
    useSidePanelStore.setState({
      isOpen: true,
      selectedTab: SIDE_PANEL_TAB.AI_CHAT,
    });

    // When
    render(<NavbarClient title="Findings" />);

    // Then
    expect(
      screen.queryByTestId("side-panel-ai-trigger"),
    ).not.toBeInTheDocument();
  });

  it("keeps the Lighthouse AI trigger while the panel shows a detail tab", () => {
    // Given: the panel is open but on the context (detail) tab, so the trigger
    // is still the way to switch to the AI chat.
    useSidePanelStore.setState({
      isOpen: true,
      selectedTab: SIDE_PANEL_TAB.CONTEXT,
    });

    // When
    render(<NavbarClient title="Findings" />);

    // Then
    expect(screen.getByTestId("side-panel-ai-trigger")).toBeInTheDocument();
  });

  it("hides the Lighthouse AI side-panel trigger in self-hosted (OSS) deployments", () => {
    // Given
    vi.stubEnv("NEXT_PUBLIC_IS_CLOUD_ENV", "false");

    // When
    render(<NavbarClient title="Findings" />);

    // Then
    expect(
      screen.queryByTestId("side-panel-ai-trigger"),
    ).not.toBeInTheDocument();
  });

  it("does not render a contextual onboarding button for unknown flows", () => {
    // Given / When
    render(
      <NavbarClient
        title="Findings"
        onboardingAction={{ flowId: "unknown-flow" }}
      />,
    );

    // Then
    expect(
      screen.queryByRole("button", { name: /start product tour/i }),
    ).not.toBeInTheDocument();
  });

  it("draws a bottom separator that reaches the sidebar's edge", () => {
    // Given / When
    render(<NavbarClient title="Findings" />);

    // Then: same token as the sidebar's border-r, bled 16px left to meet it
    const header = screen.getByRole("banner");
    expect(header).toHaveClass(
      "border-b",
      "border-border-neutral-secondary",
      "-ml-4",
      "pl-4",
    );
    expect(header).not.toHaveClass("w-full");
  });

  it("keeps the feeds fallback on the shared ghost icon treatment", () => {
    // Given / When
    render(<FeedsLoadingFallback />);

    // Then: same 32px square as the rest of the navbar action cluster
    const button = screen.getByRole("button", { name: "Loading updates" });
    expect(button).toHaveClass("size-8");
    expect(button).not.toHaveClass("rounded-full");
  });
});
