import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { WorkspaceMemberRole } from "@mosaic/shared";
import { InviteMember } from "./InviteMember";

/**
 * Helper to get the invite form element from the rendered component.
 * The form wraps the submit button, so we locate it via the button.
 */
function getForm(): HTMLFormElement {
  const button = screen.getByRole("button", { name: /send invitation/i });
  const form = button.closest("form");
  if (!form) {
    throw new Error("Could not locate <form> element in InviteMember");
  }
  return form;
}

describe("InviteMember", (): void => {
  const mockOnInvite = vi.fn<(email: string, role: WorkspaceMemberRole) => Promise<void>>();

  beforeEach((): void => {
    mockOnInvite.mockReset();
    mockOnInvite.mockResolvedValue(undefined);
    vi.spyOn(window, "alert").mockImplementation((): undefined => undefined);
  });

  it("should render the invite form", (): void => {
    render(<InviteMember onInvite={mockOnInvite} />);
    expect(screen.getByLabelText(/email address/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/role/i)).toBeInTheDocument();
    expect(screen.getByRole("button", { name: /send invitation/i })).toBeInTheDocument();
  });

  it("should show error for empty email", async (): Promise<void> => {
    render(<InviteMember onInvite={mockOnInvite} />);

    fireEvent.submit(getForm());

    expect(await screen.findByText("Email is required")).toBeInTheDocument();
    expect(mockOnInvite).not.toHaveBeenCalled();
  });

  it("should show error for invalid email without domain", async (): Promise<void> => {
    render(<InviteMember onInvite={mockOnInvite} />);

    const emailInput = screen.getByLabelText(/email address/i);
    fireEvent.change(emailInput, { target: { value: "notanemail" } });
    fireEvent.submit(getForm());

    expect(await screen.findByText("Please enter a valid email address")).toBeInTheDocument();
    expect(mockOnInvite).not.toHaveBeenCalled();
  });

  it("should show error for email with only @ sign", async (): Promise<void> => {
    render(<InviteMember onInvite={mockOnInvite} />);

    const emailInput = screen.getByLabelText(/email address/i);
    fireEvent.change(emailInput, { target: { value: "user@" } });
    fireEvent.submit(getForm());

    expect(await screen.findByText("Please enter a valid email address")).toBeInTheDocument();
    expect(mockOnInvite).not.toHaveBeenCalled();
  });

  it("should accept valid email and invoke onInvite", async (): Promise<void> => {
    const user = userEvent.setup();
    render(<InviteMember onInvite={mockOnInvite} />);

    await user.type(screen.getByLabelText(/email address/i), "valid@example.com");
    await user.click(screen.getByRole("button", { name: /send invitation/i }));

    expect(mockOnInvite).toHaveBeenCalledWith("valid@example.com", WorkspaceMemberRole.MEMBER);
  });

  it("should allow selecting a different role", async (): Promise<void> => {
    const user = userEvent.setup();
    render(<InviteMember onInvite={mockOnInvite} />);

    await user.type(screen.getByLabelText(/email address/i), "admin@example.com");
    await user.selectOptions(screen.getByLabelText(/role/i), WorkspaceMemberRole.ADMIN);
    await user.click(screen.getByRole("button", { name: /send invitation/i }));

    expect(mockOnInvite).toHaveBeenCalledWith("admin@example.com", WorkspaceMemberRole.ADMIN);
  });

  it("should show error message when onInvite rejects", async (): Promise<void> => {
    mockOnInvite.mockRejectedValueOnce(new Error("Invite failed"));
    const user = userEvent.setup();
    render(<InviteMember onInvite={mockOnInvite} />);

    await user.type(screen.getByLabelText(/email address/i), "user@example.com");
    await user.click(screen.getByRole("button", { name: /send invitation/i }));

    expect(await screen.findByText("Invite failed")).toBeInTheDocument();
  });

  it("should have maxLength of 254 on email input", (): void => {
    render(<InviteMember onInvite={mockOnInvite} />);
    const emailInput = screen.getByLabelText(/email address/i);
    expect(emailInput).toHaveAttribute("maxLength", "254");
  });

  it("should reset form after successful invite", async (): Promise<void> => {
    const user = userEvent.setup();
    render(<InviteMember onInvite={mockOnInvite} />);

    const emailInput = screen.getByLabelText(/email address/i);
    await user.type(emailInput, "user@example.com");
    await user.click(screen.getByRole("button", { name: /send invitation/i }));

    expect(emailInput).toHaveValue("");
  });
});
