import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Toaster, toast } from ".";

// Mock setPointerCapture which JSDOM doesn't support (used by Sonner)
beforeAll(() => {
  Element.prototype.setPointerCapture = vi.fn();
  Element.prototype.releasePointerCapture = vi.fn();
});

describe("Toast", () => {
  describe("Toaster", () => {
    it("renders without errors", () => {
      render(<Toaster />);
      // Sonner renders a section element with aria-label
      expect(screen.getByRole("region")).toBeInTheDocument();
    });
  });

  describe("toast functions", () => {
    beforeEach(() => {
      toast.dismiss();
    });

    it("shows info toast with correct content", async () => {
      render(<Toaster />);

      toast.info("Info message");

      await waitFor(() => {
        expect(screen.getByText("Info message")).toBeInTheDocument();
      });
    });

    it("shows success toast with correct content", async () => {
      render(<Toaster />);

      toast.success("Success message");

      await waitFor(() => {
        expect(screen.getByText("Success message")).toBeInTheDocument();
      });
    });

    it("shows warning toast with correct content", async () => {
      render(<Toaster />);

      toast.warning("Warning message");

      await waitFor(() => {
        expect(screen.getByText("Warning message")).toBeInTheDocument();
      });
    });

    it("shows error toast with correct content", async () => {
      render(<Toaster />);

      toast.error("Error message");

      await waitFor(() => {
        expect(screen.getByText("Error message")).toBeInTheDocument();
      });
    });

    it("shows toast with title", async () => {
      render(<Toaster />);

      toast.info("Description text", { title: "Title text" });

      await waitFor(() => {
        expect(screen.getByText("Title text")).toBeInTheDocument();
      });
    });

    it("shows toast with description when title is provided", async () => {
      render(<Toaster />);

      toast.info("Description text", { title: "Title text" });

      await waitFor(() => {
        expect(screen.getByText("Description text")).toBeInTheDocument();
      });
    });

    it("renders toast with action button", async () => {
      render(<Toaster />);

      toast.info("Action toast", {
        action: { label: "Undo", onClick: vi.fn() },
      });

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

    it("shows toast with cancel button", async () => {
      render(<Toaster />);

      toast.info("Cancel toast", {
        cancel: { label: "Cancel" },
      });

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

    it("calls action onClick and dismisses toast when action button is clicked", async () => {
      const user = userEvent.setup();
      const onClickMock = vi.fn();
      render(<Toaster />);

      toast.info("Action toast", {
        action: { label: "Confirm", onClick: onClickMock },
      });

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

      await user.click(screen.getByText("Confirm"));

      expect(onClickMock).toHaveBeenCalledTimes(1);

      await waitFor(() => {
        expect(screen.queryByText("Action toast")).not.toBeInTheDocument();
      });
    });

    it("calls cancel onClick and dismisses toast when cancel button is clicked", async () => {
      const user = userEvent.setup();
      const onClickMock = vi.fn();
      render(<Toaster />);

      toast.info("Cancel toast", {
        cancel: { label: "Dismiss", onClick: onClickMock },
      });

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

      await user.click(screen.getByText("Dismiss"));

      expect(onClickMock).toHaveBeenCalledTimes(1);

      await waitFor(() => {
        expect(screen.queryByText("Cancel toast")).not.toBeInTheDocument();
      });
    });

    it("dismisses toast when dismiss is called", async () => {
      render(<Toaster />);

      toast.info("Dismissable toast");

      await waitFor(() => {
        expect(screen.getByText("Dismissable toast")).toBeInTheDocument();
      });

      toast.dismiss();

      await waitFor(() => {
        expect(screen.queryByText("Dismissable toast")).not.toBeInTheDocument();
      });
    });

    it("calls onDismiss callback when toast is dismissed via close button", async () => {
      const user = userEvent.setup();
      const onDismissMock = vi.fn();
      render(<Toaster closeButton />);

      toast.info("Dismissable toast", { onDismiss: onDismissMock });

      await waitFor(() => {
        expect(screen.getByText("Dismissable toast")).toBeInTheDocument();
      });

      const closeButton = screen.getByRole("button", { name: "Close" });
      await user.click(closeButton);

      await waitFor(() => {
        expect(onDismissMock).toHaveBeenCalledTimes(1);
      });
    });
  });
});
