import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { Popover } from ".";

const CONTAINER_CLASS_NAME = "mobius-popover__container";
const TOGGLE_CLASS_NAME = "mobius-popover__toggle";
const POPOVER_CLASS_NAME = "mobius-popover";
const HEADER_CLASS_NAME = "mobius-popover__header";
const CLOSE_BUTTON_CLASS_NAME = "mobius-popover__close-button";
const CLOSE_ICON_CLASS_NAME = "mobius-popover__close-icon";
const BODY_CLASS_NAME = "mobius-popover__body";

describe("Popover", () => {
  it("should render without errors", () => {
    const view = render(
      <Popover trigger={<button type="button">Click me</button>}>
        Sample Text
      </Popover>,
    );

    expect(view).toBeTruthy();
  });

  it("should open when clicked", async () => {
    const sampleText = "Sample Text";
    const triggerText = "Click me";

    render(
      <Popover trigger={<button type="button">{triggerText}</button>}>
        {sampleText}
      </Popover>,
    );

    const button = screen.getByText(triggerText);

    fireEvent.click(button);

    await waitFor(() => {
      expect(screen.queryByText(sampleText)).toBeInTheDocument();
    });

    const closeButton = screen.getByLabelText("Close");

    fireEvent.click(closeButton);

    expect(screen.queryByText(sampleText)).not.toBeInTheDocument();
  });

  describe("uses correct class names", () => {
    it("uses correct classes", async () => {
      const sampleText = "Sample Text";
      const triggerText = "Click me";

      render(
        <Popover trigger={<button type="button">{triggerText}</button>}>
          {sampleText}
        </Popover>,
      );

      const button = screen.getByText(triggerText);

      expect(button).toHaveClass(TOGGLE_CLASS_NAME);

      fireEvent.click(button);

      const popoverContainer = document.querySelector(
        `.${CONTAINER_CLASS_NAME}`,
      );

      await waitFor(() => {
        expect(popoverContainer).toHaveClass("mobius");
      });

      const closeButton = screen.getByLabelText("Close");
      const body = screen.getByText(sampleText);

      expect(popoverContainer).toHaveClass(CONTAINER_CLASS_NAME);
      expect(popoverContainer?.firstChild).toHaveClass(POPOVER_CLASS_NAME);
      expect(closeButton.parentElement).toHaveClass(HEADER_CLASS_NAME);
      expect(closeButton).toHaveClass(CLOSE_BUTTON_CLASS_NAME);
      expect(closeButton.firstChild).toHaveClass(CLOSE_ICON_CLASS_NAME);
      expect(body).toHaveClass(BODY_CLASS_NAME);
    });

    it("includes custom class name", async () => {
      const customClassName = "my-class";
      const sampleText = "Sample Text";
      const triggerText = "Click me";

      render(
        <Popover
          className={customClassName}
          trigger={<button type="button">{triggerText}</button>}
        >
          {sampleText}
        </Popover>,
      );

      const button = screen.getByText(triggerText);

      fireEvent.click(button);

      const popoverContainer = document.querySelector(
        `.${CONTAINER_CLASS_NAME}`,
      );

      await waitFor(() => {
        expect(popoverContainer).toHaveClass(customClassName);
      });
    });

    it("passes trigger class names", () => {
      const sampleText = "Sample Text";
      const triggerText = "Click me";
      const customClassName = "custom-trigger-class-name";

      render(
        <Popover
          trigger={
            <button type="button" className={customClassName}>
              {triggerText}
            </button>
          }
        >
          {sampleText}
        </Popover>,
      );

      const button = screen.getByText(triggerText);

      expect(button).toHaveClass(customClassName);
    });
  });

  describe("click isolation", () => {
    it("does not activate a parent label when clicking popover content", async () => {
      const triggerText = "Click me";
      const bodyText = "Popover body";
      const onCheckboxChange = vi.fn();

      render(
        <label htmlFor="test-checkbox">
          <input
            id="test-checkbox"
            type="checkbox"
            onChange={onCheckboxChange}
          />
          <Popover trigger={<button type="button">{triggerText}</button>}>
            <span>{bodyText}</span>
          </Popover>
        </label>,
      );

      fireEvent.click(screen.getByText(triggerText));

      await waitFor(() => {
        expect(screen.getByText(bodyText)).toBeInTheDocument();
      });

      fireEvent.click(screen.getByText(bodyText));

      expect(onCheckboxChange).not.toHaveBeenCalled();
    });

    it("allows default behavior for links inside the popover", async () => {
      const triggerText = "Click me";
      const onCheckboxChange = vi.fn();

      render(
        <label htmlFor="test-checkbox">
          <input
            id="test-checkbox"
            type="checkbox"
            onChange={onCheckboxChange}
          />
          <Popover trigger={<button type="button">{triggerText}</button>}>
            <a href="https://example.com">Example link</a>
          </Popover>
        </label>,
      );

      fireEvent.click(screen.getByText(triggerText));

      await waitFor(() => {
        expect(screen.getByText("Example link")).toBeInTheDocument();
      });

      const link = screen.getByText("Example link");

      const clickEvent = new MouseEvent("click", { bubbles: true });
      const preventDefaultSpy = vi.spyOn(clickEvent, "preventDefault");

      link.dispatchEvent(clickEvent);

      expect(preventDefaultSpy).not.toHaveBeenCalled();
      expect(onCheckboxChange).not.toHaveBeenCalled();
    });

    it("does not activate a parent label when clicking the close button", async () => {
      const triggerText = "Click me";
      const bodyText = "Popover body";
      const onCheckboxChange = vi.fn();

      render(
        <label htmlFor="test-checkbox">
          <input
            id="test-checkbox"
            type="checkbox"
            onChange={onCheckboxChange}
          />
          <Popover trigger={<button type="button">{triggerText}</button>}>
            <span>{bodyText}</span>
          </Popover>
        </label>,
      );

      fireEvent.click(screen.getByText(triggerText));

      await waitFor(() => {
        expect(screen.getByText(bodyText)).toBeInTheDocument();
      });

      fireEvent.click(screen.getByLabelText("Close"));

      expect(onCheckboxChange).not.toHaveBeenCalled();
    });
  });

  describe("events", () => {
    it("calls onOpen when Popover is opened", async () => {
      const sampleText = "Sample Text";
      const triggerText = "Click me";
      const onOpen = vi.fn();
      const onClose = vi.fn();

      render(
        <Popover
          trigger={<button type="button">{triggerText}</button>}
          onOpen={onOpen}
          onClose={onClose}
        >
          {sampleText}
        </Popover>,
      );

      const toggle = screen.getByText(triggerText);

      fireEvent.click(toggle);

      await waitFor(() => {
        expect(onOpen).toHaveBeenCalled();
      });
      expect(onClose).not.toHaveBeenCalled();
    });

    it("calls onClose when Popover is closed", async () => {
      const sampleText = "Sample Text";
      const triggerText = "Click me";
      const onOpen = vi.fn();
      const onClose = vi.fn();

      render(
        <Popover
          trigger={<button type="button">{triggerText}</button>}
          onOpen={onOpen}
          onClose={onClose}
        >
          {sampleText}
        </Popover>,
      );

      const toggle = screen.getByText(triggerText);

      fireEvent.click(toggle);

      const closeButton = screen.getByLabelText("Close");

      fireEvent.click(closeButton);

      await waitFor(() => {
        expect(onClose).toHaveBeenCalled();
      });
      expect(onOpen).toHaveBeenCalledTimes(1);
    });
  });
});
