import { render, screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { ExpandableText } from "./ExpandableText";

// Mock window.matchMedia for usePrefersReducedMotion
Object.defineProperty(window, "matchMedia", {
  writable: true,
  value: vi.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: vi.fn(), // deprecated
    removeListener: vi.fn(), // deprecated
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
    dispatchEvent: vi.fn(),
  })),
});

// Mock useBreakpoint hook
const mockBreakpoint = {
  down: vi.fn(),
  up: vi.fn(),
  only: vi.fn(),
  between: vi.fn(),
};

vi.mock("../../hooks/useBreakpoint", () => ({
  useBreakpoint: () => mockBreakpoint,
}));

describe("ExpandableText", () => {
  const longText =
    "This is a very long text that should be collapsed when displayed with limited lines. It contains multiple sentences to ensure it will overflow the specified line limit.";
  const shortText = "Short text.";

  beforeEach(() => {
    vi.clearAllMocks();
    mockBreakpoint.down.mockReturnValue(true);

    // Mock scrollHeight and clientHeight to simulate truncation
    Object.defineProperty(HTMLElement.prototype, "scrollHeight", {
      configurable: true,
      value: 200,
    });
    Object.defineProperty(HTMLElement.prototype, "clientHeight", {
      configurable: true,
      value: 100,
    });
  });

  it("renders text correctly", () => {
    render(<ExpandableText text={longText} />);
    expect(screen.getByText(longText)).toBeInTheDocument();
  });

  it("applies default props correctly", () => {
    render(<ExpandableText text={longText} />);

    // Should have default max lines (3) via CSS custom property
    const container = screen.getByTestId("expandable-text-content");
    expect(container).toHaveStyle("--line-clamp: 3");
  });

  it("applies custom maxLines prop", () => {
    render(<ExpandableText text={longText} maxLines={5} />);

    const container = screen.getByTestId("expandable-text-content");
    expect(container).toHaveStyle("--line-clamp: 5");
  });
  it("shows accordion when text is collapsed", async () => {
    render(<ExpandableText text={longText} />);

    // Wait for effect to run and check for truncation
    // The accordion should appear due to mocked scrollHeight > clientHeight
    const accordionButton = await screen.findByRole("button");
    expect(accordionButton).toBeInTheDocument();
    expect(accordionButton).toHaveTextContent("Read more");
  });

  it("does not show accordion when text is not collapsed", () => {
    // Mock element as not overflowing
    Object.defineProperty(HTMLElement.prototype, "scrollHeight", {
      configurable: true,
      value: 50,
    });
    Object.defineProperty(HTMLElement.prototype, "clientHeight", {
      configurable: true,
      value: 100,
    });

    render(<ExpandableText text={shortText} />);

    // Should not show accordion button
    expect(screen.queryByRole("button")).not.toBeInTheDocument();
  });

  it("toggles between collapsed and expanded states", async () => {
    const user = userEvent.setup();
    render(<ExpandableText text={longText} />);

    const readMoreButton = await screen.findByRole("button");
    const textContainer = screen.getByTestId("expandable-text-content");

    // Initially collapsed
    expect(textContainer).toHaveClass(
      "mobius-expandable-text__content--collapsed",
    );
    expect(readMoreButton).toHaveTextContent("Read more");

    // Click to expand
    await user.click(readMoreButton);

    // Should show "Read less" and remove truncation class
    const readLessButton = await screen.findByRole("button");
    expect(readLessButton).toHaveTextContent("Read less");
    expect(textContainer).not.toHaveClass(
      "mobius-expandable-text__content--collapsed",
    );

    // Click to collapse
    await user.click(readLessButton);

    // Should show "Read more" and add truncation class back
    const readMoreButtonAgain = await screen.findByRole("button");
    expect(readMoreButtonAgain).toHaveTextContent("Read more");
    expect(textContainer).toHaveClass(
      "mobius-expandable-text__content--collapsed",
    );
  });

  it("uses custom accordion labels", async () => {
    const user = userEvent.setup();
    render(
      <ExpandableText
        text={longText}
        showText="Show full description"
        hideText="Show summary"
      />,
    );

    const expandButton = await screen.findByRole("button");
    expect(expandButton).toHaveTextContent("Show full description");

    // Click to expand
    await user.click(expandButton);

    const collapseButton = await screen.findByRole("button");
    expect(collapseButton).toHaveTextContent("Show summary");
  });

  it("handles breakpoint prop correctly", () => {
    mockBreakpoint.down.mockReturnValue(false); // Above breakpoint

    render(<ExpandableText text={longText} breakpoint="sm" />);

    // Should show full text without accordion when above breakpoint
    expect(screen.getByText(longText)).toBeInTheDocument();
    expect(screen.queryByRole("button")).not.toBeInTheDocument();
  });

  it("calls onToggle callback when state changes", async () => {
    const user = userEvent.setup();
    const onToggle = vi.fn();
    render(<ExpandableText text={longText} onToggle={onToggle} />);

    const readMoreButton = await screen.findByRole("button");

    // Click to expand
    await user.click(readMoreButton);
    expect(onToggle).toHaveBeenCalledWith(true);

    // Click to collapse
    const readLessButton = await screen.findByRole("button");
    await user.click(readLessButton);
    expect(onToggle).toHaveBeenCalledWith(false);
  });

  it("passes textProps to Text component", () => {
    render(
      <ExpandableText
        text={longText}
        textProps={{ variant: "small", className: "custom-text-class" }}
      />,
    );

    // Verify the text is rendered within an element that has all the custom classes
    const content = screen.getByTestId("expandable-text-content");

    // The Text component with custom classes should be within the content
    const textWithCustomClass = within(content).getByText(longText, {
      selector: ".mobius-text.custom-text-class.--is-small *",
    });

    expect(textWithCustomClass).toBeInTheDocument();
  });

  it("passes accordionProps to Accordion component", async () => {
    render(
      <ExpandableText
        text={longText}
        accordionProps={{ headerPosition: "top" }}
      />,
    );

    // Accordion should be present with custom props applied
    const accordionElement = await screen.findByRole("button");
    expect(accordionElement).toBeInTheDocument();
  });

  it("applies custom className", () => {
    render(<ExpandableText text={longText} className="custom-class" />);

    const container = screen.getByTestId("expandable-text");
    expect(container).toHaveClass("custom-class");
  });

  it("prevents event propagation on accordion click", async () => {
    const user = userEvent.setup();
    const containerClickHandler = vi.fn();

    render(
      <div onClick={containerClickHandler}>
        <ExpandableText text={longText} />
      </div>,
    );

    const readMoreButton = await screen.findByRole("button");
    await user.click(readMoreButton);

    // Container click should not be triggered
    expect(containerClickHandler).not.toHaveBeenCalled();
  });
});
