import { screen, waitFor, within } from "@testing-library/react";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import userEvent from "@testing-library/user-event";
import { createRoutesStub } from "react-router";
import React from "react";
import { renderWithProviders } from "test-utils";
import { ConversationPanel } from "#/components/features/conversation-panel/conversation-panel";
import ConversationService from "#/api/conversation-service/conversation-service.api";
import { Conversation } from "#/api/open-hands.types";

// Mock the unified stop conversation hook
const mockStopConversationMutate = vi.fn();
vi.mock("#/hooks/mutation/use-unified-stop-conversation", () => ({
  useUnifiedPauseConversationSandbox: () => ({
    mutate: mockStopConversationMutate,
  }),
}));

describe("ConversationPanel", () => {
  const onCloseMock = vi.fn();
  const RouterStub = createRoutesStub([
    {
      Component: () => <ConversationPanel onClose={onCloseMock} />,
      path: "/",
    },
    {
      // Add route to prevent "No routes matched location" warning
      Component: () => null,
      path: "/conversations/:conversationId",
    },
  ]);

  const renderConversationPanel = () => renderWithProviders(<RouterStub />);

  beforeAll(() => {
    vi.mock("react-router", async (importOriginal) => ({
      ...(await importOriginal<typeof import("react-router")>()),
      Link: ({ children }: React.PropsWithChildren) => children,
      useNavigate: vi.fn(() => vi.fn()),
      useLocation: vi.fn(() => ({ pathname: "/conversation" })),
      useParams: vi.fn(() => ({ conversationId: "2" })),
    }));
  });

  const mockConversations: Conversation[] = [
    {
      conversation_id: "1",
      title: "Conversation 1",
      selected_repository: null,
      git_provider: null,
      selected_branch: null,
      last_updated_at: "2021-10-01T12:00:00Z",
      created_at: "2021-10-01T12:00:00Z",
      status: "STOPPED" as const,
      runtime_status: null,
      url: null,
      session_api_key: null,
    },
    {
      conversation_id: "2",
      title: "Conversation 2",
      selected_repository: null,
      git_provider: null,
      selected_branch: null,
      last_updated_at: "2021-10-02T12:00:00Z",
      created_at: "2021-10-02T12:00:00Z",
      status: "STOPPED" as const,
      runtime_status: null,
      url: null,
      session_api_key: null,
    },
    {
      conversation_id: "3",
      title: "Conversation 3",
      selected_repository: null,
      git_provider: null,
      selected_branch: null,
      last_updated_at: "2021-10-03T12:00:00Z",
      created_at: "2021-10-03T12:00:00Z",
      status: "STOPPED" as const,
      runtime_status: null,
      url: null,
      session_api_key: null,
    },
  ];

  beforeEach(() => {
    vi.clearAllMocks();
    mockStopConversationMutate.mockClear();
    // Setup default mock for getUserConversations
    vi.spyOn(ConversationService, "getUserConversations").mockResolvedValue({
      results: [...mockConversations],
      next_page_id: null,
    });
  });

  it("should render the conversations", async () => {
    renderConversationPanel();
    const cards = await screen.findAllByTestId("conversation-card");

    // NOTE that we filter out conversations that don't have a created_at property
    // (mock data has 4 conversations, but only 3 have a created_at property)
    expect(cards).toHaveLength(3);
  });

  it("should display an empty state when there are no conversations", async () => {
    const getUserConversationsSpy = vi.spyOn(
      ConversationService,
      "getUserConversations",
    );
    getUserConversationsSpy.mockResolvedValue({
      results: [],
      next_page_id: null,
    });

    renderConversationPanel();

    const emptyState = await screen.findByText("CONVERSATION$NO_CONVERSATIONS");
    expect(emptyState).toBeInTheDocument();
  });

  it("should handle an error when fetching conversations", async () => {
    const getUserConversationsSpy = vi.spyOn(
      ConversationService,
      "getUserConversations",
    );
    getUserConversationsSpy.mockRejectedValue(
      new Error("Failed to fetch conversations"),
    );

    renderConversationPanel();

    const error = await screen.findByText("Failed to fetch conversations");
    expect(error).toBeInTheDocument();
  });

  it("should cancel deleting a conversation", async () => {
    const user = userEvent.setup();
    renderConversationPanel();

    let cards = await screen.findAllByTestId("conversation-card");
    // Delete button should not be visible initially (context menu is closed)
    // The context menu is always in the DOM but hidden by CSS classes on the parent div
    const contextMenuParent = within(cards[0]).queryByTestId(
      "context-menu",
    )?.parentElement;
    if (contextMenuParent) {
      expect(contextMenuParent).toHaveClass("opacity-0", "invisible");
    }

    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);
    const deleteButton = within(cards[0]).getByTestId("delete-button");

    // Click the first delete button
    await user.click(deleteButton);

    // Cancel the deletion
    const cancelButton = screen.getByRole("button", { name: /cancel/i });
    await user.click(cancelButton);

    expect(
      screen.queryByRole("button", { name: /cancel/i }),
    ).not.toBeInTheDocument();

    // Ensure the conversation is not deleted
    cards = await screen.findAllByTestId("conversation-card");
    expect(cards).toHaveLength(3);
  });

  it("should delete a conversation", async () => {
    const user = userEvent.setup();
    const mockData: Conversation[] = [
      {
        conversation_id: "1",
        title: "Conversation 1",
        selected_repository: null,
        git_provider: null,
        selected_branch: null,
        last_updated_at: "2021-10-01T12:00:00Z",
        created_at: "2021-10-01T12:00:00Z",
        status: "STOPPED" as const,
        runtime_status: null,
        url: null,
        session_api_key: null,
      },
      {
        conversation_id: "2",
        title: "Conversation 2",
        selected_repository: null,
        git_provider: null,
        selected_branch: null,
        last_updated_at: "2021-10-02T12:00:00Z",
        created_at: "2021-10-02T12:00:00Z",
        status: "STOPPED" as const,
        runtime_status: null,
        url: null,
        session_api_key: null,
      },
      {
        conversation_id: "3",
        title: "Conversation 3",
        selected_repository: null,
        git_provider: null,
        selected_branch: null,
        last_updated_at: "2021-10-03T12:00:00Z",
        created_at: "2021-10-03T12:00:00Z",
        status: "STOPPED" as const,
        runtime_status: null,
        url: null,
        session_api_key: null,
      },
    ];

    const getUserConversationsSpy = vi.spyOn(
      ConversationService,
      "getUserConversations",
    );
    getUserConversationsSpy.mockImplementation(async () => ({
      results: mockData,
      next_page_id: null,
    }));

    const deleteUserConversationSpy = vi.spyOn(
      ConversationService,
      "deleteUserConversation",
    );
    deleteUserConversationSpy.mockImplementation(async (id: string) => {
      const index = mockData.findIndex((conv) => conv.conversation_id === id);
      if (index !== -1) {
        mockData.splice(index, 1);
      }
    });

    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");
    expect(cards).toHaveLength(3);

    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);
    const deleteButton = within(cards[0]).getByTestId("delete-button");

    // Click the first delete button
    await user.click(deleteButton);

    // Confirm the deletion
    const confirmButton = screen.getByRole("button", { name: /confirm/i });
    await user.click(confirmButton);

    expect(
      screen.queryByRole("button", { name: /confirm/i }),
    ).not.toBeInTheDocument();

    // Wait for the cards to update
    await waitFor(() => {
      const updatedCards = screen.getAllByTestId("conversation-card");
      expect(updatedCards).toHaveLength(2);
    });
  });

  it("should call onClose after clicking a card", async () => {
    const user = userEvent.setup();
    renderConversationPanel();
    const cards = await screen.findAllByTestId("conversation-card");
    const firstCard = cards[1];

    await user.click(firstCard);

    expect(onCloseMock).toHaveBeenCalledOnce();
  });

  it("should refetch data on rerenders", async () => {
    const user = userEvent.setup();
    const getUserConversationsSpy = vi.spyOn(
      ConversationService,
      "getUserConversations",
    );
    getUserConversationsSpy.mockResolvedValue({
      results: [...mockConversations],
      next_page_id: null,
    });

    function PanelWithToggle() {
      const [isOpen, setIsOpen] = React.useState(true);
      return (
        <>
          <button type="button" onClick={() => setIsOpen((prev) => !prev)}>
            Toggle
          </button>
          {isOpen && <ConversationPanel onClose={onCloseMock} />}
        </>
      );
    }

    const MyRouterStub = createRoutesStub([
      {
        Component: PanelWithToggle,
        path: "/",
      },
    ]);

    renderWithProviders(<MyRouterStub />);

    const toggleButton = screen.getByText("Toggle");

    // Initial render
    const cards = await screen.findAllByTestId("conversation-card");
    expect(cards).toHaveLength(3);

    // Toggle off
    await user.click(toggleButton);
    expect(screen.queryByTestId("conversation-card")).not.toBeInTheDocument();

    // Toggle on
    await user.click(toggleButton);
    const newCards = await screen.findAllByTestId("conversation-card");
    expect(newCards).toHaveLength(3);
  });

  it("should cancel stopping a conversation", async () => {
    const user = userEvent.setup();

    // Create mock data with a RUNNING conversation
    const mockRunningConversations: Conversation[] = [
      {
        conversation_id: "1",
        title: "Running Conversation",
        selected_repository: null,
        git_provider: null,
        selected_branch: null,
        last_updated_at: "2021-10-01T12:00:00Z",
        created_at: "2021-10-01T12:00:00Z",
        status: "RUNNING" as const,
        runtime_status: null,
        url: null,
        session_api_key: null,
      },
      {
        conversation_id: "2",
        title: "Starting Conversation",
        selected_repository: null,
        git_provider: null,
        selected_branch: null,
        last_updated_at: "2021-10-02T12:00:00Z",
        created_at: "2021-10-02T12:00:00Z",
        status: "STARTING" as const,
        runtime_status: null,
        url: null,
        session_api_key: null,
      },
      {
        conversation_id: "3",
        title: "Stopped Conversation",
        selected_repository: null,
        git_provider: null,
        selected_branch: null,
        last_updated_at: "2021-10-03T12:00:00Z",
        created_at: "2021-10-03T12:00:00Z",
        status: "STOPPED" as const,
        runtime_status: null,
        url: null,
        session_api_key: null,
      },
    ];

    const getUserConversationsSpy = vi.spyOn(
      ConversationService,
      "getUserConversations",
    );
    getUserConversationsSpy.mockResolvedValue({
      results: mockRunningConversations,
      next_page_id: null,
    });

    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");
    expect(cards).toHaveLength(3);

    // Click ellipsis on the first card (RUNNING status)
    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);

    // Stop button should be available for RUNNING conversation
    const stopButton = within(cards[0]).getByTestId("stop-button");
    expect(stopButton).toBeInTheDocument();

    // Click the stop button
    await user.click(stopButton);

    // Cancel the stopping action
    const cancelButton = screen.getByRole("button", { name: /cancel/i });
    await user.click(cancelButton);

    expect(
      screen.queryByRole("button", { name: /cancel/i }),
    ).not.toBeInTheDocument();

    // Ensure the conversation status hasn't changed
    const updatedCards = await screen.findAllByTestId("conversation-card");
    expect(updatedCards).toHaveLength(3);
  });

  it("should stop a conversation", async () => {
    const user = userEvent.setup();

    const mockData: Conversation[] = [
      {
        conversation_id: "1",
        title: "Running Conversation",
        selected_repository: null,
        git_provider: null,
        selected_branch: null,
        last_updated_at: "2021-10-01T12:00:00Z",
        created_at: "2021-10-01T12:00:00Z",
        status: "RUNNING" as const,
        runtime_status: null,
        url: null,
        session_api_key: null,
      },
      {
        conversation_id: "2",
        title: "Starting Conversation",
        selected_repository: null,
        git_provider: null,
        selected_branch: null,
        last_updated_at: "2021-10-02T12:00:00Z",
        created_at: "2021-10-02T12:00:00Z",
        status: "STARTING" as const,
        runtime_status: null,
        url: null,
        session_api_key: null,
      },
    ];

    const getUserConversationsSpy = vi.spyOn(
      ConversationService,
      "getUserConversations",
    );
    getUserConversationsSpy.mockImplementation(async () => ({
      results: mockData,
      next_page_id: null,
    }));

    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");
    expect(cards).toHaveLength(2);

    // Click ellipsis on the first card (RUNNING status)
    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);

    const stopButton = within(cards[0]).getByTestId("stop-button");

    // Click the stop button
    await user.click(stopButton);

    // Confirm the stopping action
    const confirmButton = screen.getByRole("button", { name: /confirm/i });
    await user.click(confirmButton);

    expect(
      screen.queryByRole("button", { name: /confirm/i }),
    ).not.toBeInTheDocument();

    // Verify the mutation was called
    expect(mockStopConversationMutate).toHaveBeenCalledWith({
      conversationId: "1",
      version: undefined,
    });
    expect(mockStopConversationMutate).toHaveBeenCalledTimes(1);
  });

  it("should only show stop button for STARTING or RUNNING conversations", async () => {
    const user = userEvent.setup();

    const mockMixedStatusConversations: Conversation[] = [
      {
        conversation_id: "1",
        title: "Running Conversation",
        selected_repository: null,
        git_provider: null,
        selected_branch: null,
        last_updated_at: "2021-10-01T12:00:00Z",
        created_at: "2021-10-01T12:00:00Z",
        status: "RUNNING" as const,
        runtime_status: null,
        url: null,
        session_api_key: null,
      },
      {
        conversation_id: "2",
        title: "Starting Conversation",
        selected_repository: null,
        git_provider: null,
        selected_branch: null,
        last_updated_at: "2021-10-02T12:00:00Z",
        created_at: "2021-10-02T12:00:00Z",
        status: "STARTING" as const,
        runtime_status: null,
        url: null,
        session_api_key: null,
      },
      {
        conversation_id: "3",
        title: "Stopped Conversation",
        selected_repository: null,
        git_provider: null,
        selected_branch: null,
        last_updated_at: "2021-10-03T12:00:00Z",
        created_at: "2021-10-03T12:00:00Z",
        status: "STOPPED" as const,
        runtime_status: null,
        url: null,
        session_api_key: null,
      },
    ];

    const getUserConversationsSpy = vi.spyOn(
      ConversationService,
      "getUserConversations",
    );
    getUserConversationsSpy.mockResolvedValue({
      results: mockMixedStatusConversations,
      next_page_id: null,
    });

    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");
    expect(cards).toHaveLength(3);

    // Test RUNNING conversation - should show stop button
    const runningEllipsisButton = within(cards[0]).getByTestId(
      "ellipsis-button",
    );
    await user.click(runningEllipsisButton);

    expect(within(cards[0]).getByTestId("stop-button")).toBeInTheDocument();

    // Click outside to close the menu
    await user.click(document.body);

    // Wait for context menu to close (check CSS classes on parent div)
    await waitFor(() => {
      const contextMenuParent = within(cards[0]).queryByTestId(
        "context-menu",
      )?.parentElement;
      if (contextMenuParent) {
        expect(contextMenuParent).toHaveClass("opacity-0", "invisible");
      }
    });

    // Test STARTING conversation - should show stop button
    const startingEllipsisButton = within(cards[1]).getByTestId(
      "ellipsis-button",
    );
    await user.click(startingEllipsisButton);

    expect(within(cards[1]).getByTestId("stop-button")).toBeInTheDocument();

    // Click outside to close the menu
    await user.click(document.body);

    // Wait for context menu to close (check CSS classes on parent div)
    await waitFor(() => {
      const contextMenuParent = within(cards[1]).queryByTestId(
        "context-menu",
      )?.parentElement;
      if (contextMenuParent) {
        expect(contextMenuParent).toHaveClass("opacity-0", "invisible");
      }
    });

    // Test STOPPED conversation - should NOT show stop button
    const stoppedEllipsisButton = within(cards[2]).getByTestId(
      "ellipsis-button",
    );
    await user.click(stoppedEllipsisButton);

    expect(
      within(cards[2]).queryByTestId("stop-button"),
    ).not.toBeInTheDocument();
  });

  it("should show edit button in context menu", async () => {
    const user = userEvent.setup();
    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");
    expect(cards).toHaveLength(3);

    // Click ellipsis to open context menu
    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);

    // Edit button should be visible within the first card's context menu
    const editButton = within(cards[0]).getByTestId("edit-button");
    expect(editButton).toBeInTheDocument();
    expect(editButton).toHaveTextContent("BUTTON$RENAME");
  });

  it("should enter edit mode when edit button is clicked", async () => {
    const user = userEvent.setup();
    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");

    // Click ellipsis to open context menu
    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);

    // Click edit button within the first card's context menu
    const editButton = within(cards[0]).getByTestId("edit-button");
    await user.click(editButton);

    // Should find input field instead of title text
    const titleInput = within(cards[0]).getByTestId("conversation-card-title");
    expect(titleInput).toBeInTheDocument();
    expect(titleInput.tagName).toBe("INPUT");
    expect(titleInput).toHaveValue("Conversation 1");
    expect(titleInput).toHaveFocus();
  });

  it("should successfully update conversation title", async () => {
    const user = userEvent.setup();

    // Mock the updateConversation API call
    const updateConversationSpy = vi.spyOn(
      ConversationService,
      "updateConversation",
    );
    updateConversationSpy.mockResolvedValue(true);

    // Mock the toast function
    const mockToast = vi.fn();
    vi.mock("#/utils/custom-toast-handlers", () => ({
      displaySuccessToast: mockToast,
    }));

    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");

    // Enter edit mode
    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);

    const editButton = within(cards[0]).getByTestId("edit-button");
    await user.click(editButton);

    // Edit the title
    const titleInput = within(cards[0]).getByTestId("conversation-card-title");
    await user.clear(titleInput);
    await user.type(titleInput, "Updated Title");

    // Blur the input to save
    await user.tab();

    // Verify API call was made with correct parameters
    expect(updateConversationSpy).toHaveBeenCalledWith("1", {
      title: "Updated Title",
    });
  });

  it("should save title when Enter key is pressed", async () => {
    const user = userEvent.setup();

    const updateConversationSpy = vi.spyOn(
      ConversationService,
      "updateConversation",
    );
    updateConversationSpy.mockResolvedValue(true);

    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");

    // Enter edit mode
    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);

    const editButton = within(cards[0]).getByTestId("edit-button");
    await user.click(editButton);

    // Edit the title and press Enter
    const titleInput = within(cards[0]).getByTestId("conversation-card-title");
    await user.clear(titleInput);
    await user.type(titleInput, "Title Updated via Enter");
    await user.keyboard("{Enter}");

    // Verify API call was made
    expect(updateConversationSpy).toHaveBeenCalledWith("1", {
      title: "Title Updated via Enter",
    });
  });

  it("should trim whitespace from title", async () => {
    const user = userEvent.setup();

    const updateConversationSpy = vi.spyOn(
      ConversationService,
      "updateConversation",
    );
    updateConversationSpy.mockResolvedValue(true);

    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");

    // Enter edit mode
    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);

    const editButton = within(cards[0]).getByTestId("edit-button");
    await user.click(editButton);

    // Edit the title with extra whitespace
    const titleInput = within(cards[0]).getByTestId("conversation-card-title");
    await user.clear(titleInput);
    await user.type(titleInput, "   Trimmed Title   ");
    await user.tab();

    // Verify API call was made with trimmed title
    expect(updateConversationSpy).toHaveBeenCalledWith("1", {
      title: "Trimmed Title",
    });
  });

  it("should revert to original title when empty", async () => {
    const user = userEvent.setup();

    const updateConversationSpy = vi.spyOn(
      ConversationService,
      "updateConversation",
    );
    updateConversationSpy.mockResolvedValue(true);

    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");

    // Enter edit mode
    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);

    const editButton = within(cards[0]).getByTestId("edit-button");
    await user.click(editButton);

    // Clear the title completely
    const titleInput = within(cards[0]).getByTestId("conversation-card-title");
    await user.clear(titleInput);
    await user.tab();

    // Verify API was not called
    expect(updateConversationSpy).not.toHaveBeenCalled();
  });

  it("should handle API error when updating title", async () => {
    const user = userEvent.setup();

    const updateConversationSpy = vi.spyOn(
      ConversationService,
      "updateConversation",
    );
    updateConversationSpy.mockRejectedValue(new Error("API Error"));

    vi.mock("#/utils/custom-toast-handlers", () => ({
      displayErrorToast: vi.fn(),
    }));

    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");

    // Enter edit mode
    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);

    const editButton = within(cards[0]).getByTestId("edit-button");
    await user.click(editButton);

    // Edit the title
    const titleInput = within(cards[0]).getByTestId("conversation-card-title");
    await user.clear(titleInput);
    await user.type(titleInput, "Failed Update");
    await user.tab();

    // Verify API call was made
    expect(updateConversationSpy).toHaveBeenCalledWith("1", {
      title: "Failed Update",
    });

    // Wait for error handling
    await waitFor(() => {
      expect(updateConversationSpy).toHaveBeenCalled();
    });
  });

  it("should close context menu when edit button is clicked", async () => {
    const user = userEvent.setup();
    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");

    // Click ellipsis to open context menu
    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);

    // Verify context menu is open within the first card
    const contextMenu = within(cards[0]).getByTestId("context-menu");
    expect(contextMenu).toBeInTheDocument();

    // Click edit button within the first card's context menu
    const editButton = within(cards[0]).getByTestId("edit-button");
    await user.click(editButton);

    // Wait for context menu to close after edit button click (check CSS classes on parent div)
    await waitFor(() => {
      const contextMenuParent = within(cards[0]).queryByTestId(
        "context-menu",
      )?.parentElement;
      if (contextMenuParent) {
        expect(contextMenuParent).toHaveClass("opacity-0", "invisible");
      }
    });
  });

  it("should not call API when title is unchanged", async () => {
    const user = userEvent.setup();

    const updateConversationSpy = vi.spyOn(
      ConversationService,
      "updateConversation",
    );
    updateConversationSpy.mockResolvedValue(true);

    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");

    // Enter edit mode
    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);

    const editButton = within(cards[0]).getByTestId("edit-button");
    await user.click(editButton);

    // Don't change the title, just blur
    await user.tab();

    // Verify API was NOT called with the same title (since handleConversationTitleChange will always be called)
    expect(updateConversationSpy).not.toHaveBeenCalledWith("1", {
      title: "Conversation 1",
    });
  });

  it("should handle special characters in title", async () => {
    const user = userEvent.setup();

    const updateConversationSpy = vi.spyOn(
      ConversationService,
      "updateConversation",
    );
    updateConversationSpy.mockResolvedValue(true);

    renderConversationPanel();

    const cards = await screen.findAllByTestId("conversation-card");

    // Enter edit mode
    const ellipsisButton = within(cards[0]).getByTestId("ellipsis-button");
    await user.click(ellipsisButton);

    const editButton = within(cards[0]).getByTestId("edit-button");
    await user.click(editButton);

    // Edit the title with special characters
    const titleInput = within(cards[0]).getByTestId("conversation-card-title");
    await user.clear(titleInput);
    await user.type(titleInput, "Special @#$%^&*()_+ Characters");
    await user.tab();

    // Verify API call was made with special characters
    expect(updateConversationSpy).toHaveBeenCalledWith("1", {
      title: "Special @#$%^&*()_+ Characters",
    });
  });
});
