import { render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { CreateUserButton } from "./CreateUserButton";
import * as networking from "./networking";
import NotificationsManager from "./molecules/notifications_manager";

vi.mock("./networking", () => ({
  userCreateCall: vi.fn(),
  modelAvailableCall: vi.fn().mockResolvedValue({ data: [] }),
  invitationCreateCall: vi.fn(),
  organizationMemberAddCall: vi.fn(),
  getProxyUISettings: vi.fn().mockResolvedValue({
    PROXY_BASE_URL: null,
    PROXY_LOGOUT_URL: null,
    DEFAULT_TEAM_DISABLED: false,
    SSO_ENABLED: false,
  }),
  getProxyBaseUrl: vi.fn().mockReturnValue("http://localhost"),
}));

vi.mock("./bulk_create_users_button", () => ({
  default: () => <div data-testid="bulk-create-users">Bulk Create Users</div>,
}));

vi.mock("@/app/(dashboard)/hooks/organizations/useOrganizations", () => ({
  useOrganizations: vi.fn().mockReturnValue({ data: [], isLoading: false }),
}));

const mockUserCreateCall = vi.mocked(networking.userCreateCall);
const mockInvitationCreateCall = vi.mocked(networking.invitationCreateCall);
const mockGetProxyUISettings = vi.mocked(networking.getProxyUISettings);
const mockOrganizationMemberAddCall = vi.mocked(networking.organizationMemberAddCall);
const mockNotificationsManager = vi.mocked(NotificationsManager);

const createQueryClient = () =>
  new QueryClient({
    defaultOptions: { queries: { retry: false, gcTime: 0 } },
  });

const defaultProps = {
  userID: "123",
  accessToken: "token",
  teams: [],
  possibleUIRoles: null as Record<string, Record<string, string>> | null,
};

function renderWithProviders(ui: React.ReactElement) {
  const qc = createQueryClient();
  return render(<QueryClientProvider client={qc}>{ui}</QueryClientProvider>);
}

describe("CreateUserButton", { timeout: 20000 }, () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockGetProxyUISettings.mockResolvedValue({
      PROXY_BASE_URL: null,
      PROXY_LOGOUT_URL: null,
      DEFAULT_TEAM_DISABLED: false,
      SSO_ENABLED: false,
    });
  });

  it("should render the create user form when embedded", () => {
    renderWithProviders(
      <CreateUserButton {...defaultProps} isEmbedded />,
    );
    expect(screen.getByRole("button", { name: /create user/i })).toBeInTheDocument();
  });

  it("should render the invite user button when not embedded", async () => {
    renderWithProviders(<CreateUserButton {...defaultProps} />);
    await waitFor(() => {
      expect(screen.getByRole("button", { name: /\+ invite user/i })).toBeInTheDocument();
    });
  });

  it("should open the invite modal when invite user button is clicked", async () => {
    const user = userEvent.setup();
    renderWithProviders(<CreateUserButton {...defaultProps} />);
    await waitFor(() => {
      expect(screen.getByRole("button", { name: /\+ invite user/i })).toBeInTheDocument();
    });
    await user.click(screen.getByRole("button", { name: /\+ invite user/i }));
    const dialog = screen.getByRole("dialog", { name: /invite user/i });
    expect(dialog).toBeInTheDocument();
    expect(within(dialog).getByRole("button", { name: /invite user/i })).toBeInTheDocument();
  });

  it("should display email invitations info message in embedded mode", () => {
    renderWithProviders(<CreateUserButton {...defaultProps} isEmbedded />);
    expect(screen.getByText("Email invitations")).toBeInTheDocument();
  });

  it("should display user role options when possibleUIRoles is provided", async () => {
    const possibleUIRoles = {
      proxy_admin: { ui_label: "Admin", description: "Full access" },
      proxy_user: { ui_label: "User", description: "Limited access" },
    };
    renderWithProviders(
      <CreateUserButton {...defaultProps} possibleUIRoles={possibleUIRoles} isEmbedded />,
    );
    await userEvent.click(screen.getByRole("combobox", { name: /user role/i }));
    expect(screen.getByText("Admin")).toBeInTheDocument();
    expect(screen.getByText("User")).toBeInTheDocument();
  });

  it("should call userCreateCall when form is submitted in embedded mode", async () => {
    const user = userEvent.setup();
    mockUserCreateCall.mockResolvedValue({ data: { user_id: "new-user-123" } });
    mockInvitationCreateCall.mockResolvedValue({
      id: "inv-1",
      user_id: "new-user-123",
      has_user_setup_sso: false,
    } as any);

    renderWithProviders(
      <CreateUserButton {...defaultProps} possibleUIRoles={{ proxy_user: { ui_label: "User", description: "" } }} isEmbedded />,
    );

    await user.type(screen.getByLabelText(/user email/i), "test@example.com");
    await user.click(screen.getByRole("combobox", { name: /user role/i }));
    await user.click(screen.getByText("User"));
    await user.click(screen.getByRole("button", { name: /create user/i }));

    await waitFor(() => {
      expect(mockUserCreateCall).toHaveBeenCalledWith("token", null, expect.objectContaining({
        user_email: "test@example.com",
        user_role: "proxy_user",
      }));
    });
  });

  it("should call onUserCreated callback when user is created in embedded mode", async () => {
    const user = userEvent.setup();
    const onUserCreated = vi.fn();
    mockUserCreateCall.mockResolvedValue({ data: { user_id: "new-user-456" } });

    renderWithProviders(
      <CreateUserButton {...defaultProps} onUserCreated={onUserCreated} possibleUIRoles={{ proxy_user: { ui_label: "User", description: "" } }} isEmbedded />,
    );

    await user.type(screen.getByLabelText(/user email/i), "embedded@example.com");
    await user.click(screen.getByRole("combobox", { name: /user role/i }));
    await user.click(screen.getByText("User"));
    await user.click(screen.getByRole("button", { name: /create user/i }));

    await waitFor(() => {
      expect(onUserCreated).toHaveBeenCalledWith("new-user-456");
    });
  });

  it("should show success notification when user is created successfully in standalone mode", async () => {
    const user = userEvent.setup();
    mockUserCreateCall.mockResolvedValue({ data: { user_id: "new-user-789" } });
    mockInvitationCreateCall.mockResolvedValue({
      id: "inv-2",
      user_id: "new-user-789",
      has_user_setup_sso: false,
    } as any);

    renderWithProviders(
      <CreateUserButton {...defaultProps} possibleUIRoles={{ proxy_user: { ui_label: "User", description: "" } }} />,
    );

    await waitFor(() => {
      expect(screen.getByRole("button", { name: /\+ invite user/i })).toBeInTheDocument();
    });
    await user.click(screen.getByRole("button", { name: /\+ invite user/i }));

    const dialog = screen.getByRole("dialog", { name: /invite user/i });
    await user.type(within(dialog).getByLabelText(/user email/i), "standalone@example.com");
    await user.click(within(dialog).getByRole("combobox", { name: /global proxy role/i }));
    await user.click(screen.getByText("User"));
    await user.click(within(dialog).getByRole("button", { name: /invite user/i }));

    await waitFor(() => {
      expect(mockNotificationsManager.success).toHaveBeenCalledWith("API user Created");
    });
  });

  it("should show error notification when user creation fails", async () => {
    const user = userEvent.setup();
    mockUserCreateCall.mockRejectedValue({ response: { data: { detail: "Email already exists" } } });

    renderWithProviders(
      <CreateUserButton {...defaultProps} possibleUIRoles={{ proxy_user: { ui_label: "User", description: "" } }} isEmbedded />,
    );

    await user.type(screen.getByLabelText(/user email/i), "duplicate@example.com");
    await user.click(screen.getByRole("combobox", { name: /user role/i }));
    await user.click(screen.getByText("User"));
    await user.click(screen.getByRole("button", { name: /create user/i }));

    await waitFor(() => {
      expect(mockNotificationsManager.fromBackend).toHaveBeenCalledWith("Email already exists");
    });
  });

  it("should show info notification when making API call", async () => {
    const user = userEvent.setup();
    mockUserCreateCall.mockResolvedValue({ data: { user_id: "new-user" } });
    mockInvitationCreateCall.mockResolvedValue({
      id: "inv-3",
      user_id: "new-user",
      has_user_setup_sso: false,
    } as any);

    renderWithProviders(
      <CreateUserButton {...defaultProps} possibleUIRoles={{ proxy_user: { ui_label: "User", description: "" } }} isEmbedded />,
    );

    await user.type(screen.getByLabelText(/user email/i), "info@example.com");
    await user.click(screen.getByRole("combobox", { name: /user role/i }));
    await user.click(screen.getByText("User"));
    await user.click(screen.getByRole("button", { name: /create user/i }));

    await waitFor(() => {
      expect(mockNotificationsManager.info).toHaveBeenCalledWith("Making API Call");
    });
  });

  it("should close modal when cancel is clicked in standalone mode", async () => {
    const user = userEvent.setup();
    renderWithProviders(<CreateUserButton {...defaultProps} />);

    await waitFor(() => {
      expect(screen.getByRole("button", { name: /\+ invite user/i })).toBeInTheDocument();
    });
    await user.click(screen.getByRole("button", { name: /\+ invite user/i }));
    expect(screen.getByRole("dialog", { name: /invite user/i })).toBeInTheDocument();

    const dialog = screen.getByRole("dialog", { name: /invite user/i });
    await user.click(within(dialog).getByRole("button", { name: /close/i }));
    expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
  });

  it("should show onboarding modal when user is created and SSO is disabled", async () => {
    const user = userEvent.setup();
    mockUserCreateCall.mockResolvedValue({ data: { user_id: "sso-user" } });
    mockInvitationCreateCall.mockResolvedValue({
      id: "inv-sso",
      user_id: "sso-user",
      has_user_setup_sso: false,
    } as any);

    renderWithProviders(
      <CreateUserButton {...defaultProps} possibleUIRoles={{ proxy_user: { ui_label: "User", description: "" } }} />,
    );

    await waitFor(() => {
      expect(screen.getByRole("button", { name: /\+ invite user/i })).toBeInTheDocument();
    });
    await user.click(screen.getByRole("button", { name: /\+ invite user/i }));

    const dialog = screen.getByRole("dialog", { name: /invite user/i });
    await user.type(within(dialog).getByLabelText(/user email/i), "sso@example.com");
    await user.click(within(dialog).getByRole("combobox", { name: /global proxy role/i }));
    await user.click(screen.getByText("User"));
    await user.click(within(dialog).getByRole("button", { name: /invite user/i }));

    await waitFor(() => {
      expect(mockInvitationCreateCall).toHaveBeenCalledWith("token", "sso-user");
    });
    await waitFor(() => {
      expect(mockNotificationsManager.success).toHaveBeenCalledWith("API user Created");
    });
  });

  it("should send organizations list in POST body when organizations are selected", async () => {
    const { useOrganizations } = await import("@/app/(dashboard)/hooks/organizations/useOrganizations");
    vi.mocked(useOrganizations).mockReturnValue({
      data: [{ organization_id: "org-1", organization_alias: "My Org" }],
      isLoading: false,
    } as any);

    const user = userEvent.setup();
    mockUserCreateCall.mockResolvedValue({ data: { user_id: "org-user" } });
    mockInvitationCreateCall.mockResolvedValue({
      id: "inv-org",
      user_id: "org-user",
      has_user_setup_sso: false,
    } as any);

    renderWithProviders(
      <CreateUserButton {...defaultProps} possibleUIRoles={{ proxy_user: { ui_label: "User", description: "" } }} />,
    );

    await waitFor(() => {
      expect(screen.getByRole("button", { name: /\+ invite user/i })).toBeInTheDocument();
    });
    await user.click(screen.getByRole("button", { name: /\+ invite user/i }));

    const dialog = screen.getByRole("dialog", { name: /invite user/i });
    await user.type(within(dialog).getByLabelText(/user email/i), "org@example.com");
    await user.click(within(dialog).getByRole("combobox", { name: /global proxy role/i }));
    await user.click(screen.getByText("User"));

    // Select org from the dropdown
    const orgSelect = within(dialog).getByRole("combobox", { name: /organization/i });
    await user.click(orgSelect);
    await user.click(screen.getByText("My Org (org-1)"));

    await user.click(within(dialog).getByRole("button", { name: /invite user/i }));

    await waitFor(() => {
      expect(mockUserCreateCall).toHaveBeenCalledWith("token", null, expect.objectContaining({
        organizations: ["org-1"],
      }));
    });
  });

  it("should not call organizationMemberAddCall after user creation", async () => {
    const { useOrganizations } = await import("@/app/(dashboard)/hooks/organizations/useOrganizations");
    vi.mocked(useOrganizations).mockReturnValue({
      data: [{ organization_id: "org-1", organization_alias: "My Org" }],
      isLoading: false,
    } as any);

    const user = userEvent.setup();
    mockUserCreateCall.mockResolvedValue({ data: { user_id: "no-member-add-user" } });
    mockInvitationCreateCall.mockResolvedValue({
      id: "inv-nma",
      user_id: "no-member-add-user",
      has_user_setup_sso: false,
    } as any);

    renderWithProviders(
      <CreateUserButton {...defaultProps} possibleUIRoles={{ proxy_user: { ui_label: "User", description: "" } }} />,
    );

    await waitFor(() => {
      expect(screen.getByRole("button", { name: /\+ invite user/i })).toBeInTheDocument();
    });
    await user.click(screen.getByRole("button", { name: /\+ invite user/i }));

    const dialog = screen.getByRole("dialog", { name: /invite user/i });
    await user.type(within(dialog).getByLabelText(/user email/i), "nomemberadd@example.com");
    await user.click(within(dialog).getByRole("combobox", { name: /global proxy role/i }));
    await user.click(screen.getByText("User"));
    await user.click(within(dialog).getByRole("button", { name: /invite user/i }));

    await waitFor(() => {
      expect(mockUserCreateCall).toHaveBeenCalled();
    });
    expect(mockOrganizationMemberAddCall).not.toHaveBeenCalled();
  });
});
