import { createRef, useState } from "react";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Modal } from ".";
import { htmlDialogPolyfill } from "../../utils/htmlDialogPolyfill";
import { mockMatchMedia } from "../../utils/mockMatchMedia";

htmlDialogPolyfill();

const MODAL_CLASS_NAME = "mobius-modal";
const MODAL_CLOSE_CLASS_NAME = "mobius-modal__close";
const MODAL_TEST_CLASS_NAME = "my-test-class-name";

describe("Modal", () => {
  beforeEach(() => {
    mockMatchMedia(true);
  });

  afterAll(() => {
    vi.resetAllMocks();
  });

  it("should render without errors", () => {
    const { container } = render(<Modal isOpen />);
    expect(container).toBeTruthy();
  });

  it("should render with default class names", async () => {
    render(
      <Modal isOpen>
        <Modal.Header />
      </Modal>,
    );

    await screen.findByRole("dialog");

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

    expect(dialog).toHaveClass(MODAL_CLASS_NAME);
    expect(closeButton).toHaveClass(MODAL_CLOSE_CLASS_NAME);
  });

  it("should render with custom class name", async () => {
    render(
      <Modal className={MODAL_TEST_CLASS_NAME} isOpen>
        <Modal.Header />
      </Modal>,
    );

    await screen.findByRole("dialog");

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

    expect(dialog).toHaveClass(MODAL_CLASS_NAME);
    expect(dialog).toHaveClass(MODAL_TEST_CLASS_NAME);
    expect(closeButton).toHaveClass(MODAL_CLOSE_CLASS_NAME);
  });

  it("should render additional attributes", async () => {
    const testId = "test-modal";

    render(
      <Modal data-testid={testId} isOpen>
        <Modal.Header />
      </Modal>,
    );

    await screen.findByRole("dialog");

    const dialog = screen.getByTestId(testId);

    expect(dialog).toBeInTheDocument();
  });

  describe("given the animation prop is set to slideUp", () => {
    it("should render with appropriate class name", async () => {
      render(<Modal isOpen animation="slideUp" />);

      await screen.findByRole("dialog");

      const dialog = screen.getByRole("dialog");
      expect(dialog).toHaveClass("--slide-up");
    });
  });

  describe("given the animation prop is set to fade", () => {
    it("should render with appropriate class name", async () => {
      render(<Modal isOpen animation="fade" />);

      await screen.findByRole("dialog");

      const dialog = screen.getByRole("dialog");
      expect(dialog).toHaveClass("--fade");
    });
  });

  it("has open attribute when isOpen is true", async () => {
    render(
      <Modal isOpen onClose={vi.fn()}>
        <Modal.Header>Title</Modal.Header>
        <Modal.Content>Content</Modal.Content>
      </Modal>,
    );
    const dialog = await screen.findByRole("dialog");
    expect(dialog).toHaveAttribute("open");
  });

  describe("pressing close button", () => {
    it("should close the modal", async () => {
      const App = () => {
        const [isOpen, setIsOpen] = useState<boolean>(true);

        return (
          <Modal isOpen={isOpen} onClose={() => setIsOpen(false)}>
            <Modal.Header>Header</Modal.Header>
            <Modal.Content>Sample content</Modal.Content>
          </Modal>
        );
      };

      render(<App />);

      await screen.findByRole("dialog");

      const dialog = screen.getByRole("dialog");
      const drawerContent = screen.getByText("Sample content");

      expect(dialog).toHaveAttribute("open");
      expect(drawerContent).toBeInTheDocument();

      const closeButton = screen.getByLabelText("Close");
      await userEvent.click(closeButton);

      waitFor(() => expect(dialog).not.toHaveAttribute("open"));
    });
  });

  describe("closeLabel prop", () => {
    it("should render the label", () => {
      const closeLabel = "Leave";

      render(
        <Modal isOpen closeLabel={closeLabel}>
          <Modal.Header />
          Sample content
        </Modal>,
      );

      const closeButton = screen.getByText(closeLabel);
      expect(closeButton).toBeInTheDocument();
    });

    it("should render the default close button", () => {
      render(
        <Modal isOpen>
          <Modal.Header />
        </Modal>,
      );

      const closeButton = screen.getByLabelText("Close");
      expect(closeButton).toBeInTheDocument();
    });

    it("should fire onOpen callback if supplied", async () => {
      const onOpen = vi.fn();

      const App = () => {
        const [isOpen, setIsOpen] = useState<boolean>(false);

        return (
          <>
            <button
              aria-label="Show modal"
              id="btn"
              type="button"
              onClick={() => setIsOpen(true)}
            >
              Show modal
            </button>

            <Modal
              isOpen={isOpen}
              onClose={() => setIsOpen(false)}
              onOpen={onOpen}
            >
              <Modal.Header>Header</Modal.Header>
              Sample content
            </Modal>
          </>
        );
      };

      render(<App />);

      const showBtn = screen.getByText("Show modal");

      expect(showBtn).toBeInTheDocument();
      await userEvent.click(showBtn);
      expect(onOpen).toHaveBeenCalled();
    });
  });

  it("forwards ref", async () => {
    const ref = createRef<HTMLDialogElement>();
    render(<Modal ref={ref} isOpen />);
    await screen.findByRole("dialog");
    expect(ref.current).toBeInstanceOf(HTMLDialogElement);
  });
});
