import { E2eTestId } from "@shared";
import { fireEvent, render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";

const {
  mockUseOrganization,
  mockUseChatPlaceholder,
  mockTextInputSetInput,
  mockTextInputClear,
  mockControllerState,
} = vi.hoisted(() => ({
  mockUseOrganization: vi.fn(),
  mockUseChatPlaceholder: vi.fn(),
  mockTextInputSetInput: vi.fn(),
  mockTextInputClear: vi.fn(),
  mockControllerState: { value: "" },
}));

// Mock ResizeObserver which is used by Radix UI components
global.ResizeObserver = vi.fn().mockImplementation(() => ({
  observe: vi.fn(),
  unobserve: vi.fn(),
  disconnect: vi.fn(),
}));

// Mock window.matchMedia for useIsMobile hook
Object.defineProperty(window, "matchMedia", {
  writable: true,
  value: vi.fn().mockImplementation((query: string) => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: vi.fn(),
    removeListener: vi.fn(),
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
    dispatchEvent: vi.fn(),
  })),
});

// Mock all the complex dependencies
vi.mock("@/components/ai-elements/prompt-input", () => ({
  PromptInput: ({
    children,
    onSubmit,
  }: {
    children: React.ReactNode;
    onSubmit?: (
      message: { text: string; files: [] },
      event: React.FormEvent<HTMLFormElement>,
    ) => void;
  }) => (
    <form
      data-testid="prompt-input"
      onSubmit={(event) => {
        event.preventDefault();
        onSubmit?.({ text: mockControllerState.value, files: [] }, event);
      }}
    >
      {children}
    </form>
  ),
  PromptInputActionAddAttachments: ({ label }: { label: string }) => (
    <span>{label}</span>
  ),
  PromptInputActionMenu: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="action-menu">{children}</div>
  ),
  PromptInputActionMenuContent: ({
    children,
  }: {
    children: React.ReactNode;
  }) => <div>{children}</div>,
  PromptInputActionMenuTrigger: ({
    children,
    "data-testid": testId,
  }: {
    children: React.ReactNode;
    "data-testid"?: string;
  }) => <span data-testid={testId}>{children}</span>,
  PromptInputAttachment: () => <div />,
  PromptInputAttachments: () => <div />,
  PromptInputBody: ({ children }: { children: React.ReactNode }) => (
    <div>{children}</div>
  ),
  PromptInputButton: ({
    children,
    disabled,
  }: {
    children: React.ReactNode;
    disabled?: boolean;
  }) => (
    <button type="button" disabled={disabled}>
      {children}
    </button>
  ),
  PromptInputCommand: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="prompt-command">{children}</div>
  ),
  PromptInputCommandEmpty: ({ children }: { children: React.ReactNode }) => (
    <div>{children}</div>
  ),
  PromptInputCommandGroup: ({ children }: { children: React.ReactNode }) => (
    <div>{children}</div>
  ),
  PromptInputCommandItem: ({
    children,
    onSelect,
  }: {
    children: React.ReactNode;
    onSelect?: () => void;
  }) => (
    <button type="button" onClick={onSelect}>
      {children}
    </button>
  ),
  PromptInputCommandList: ({ children }: { children: React.ReactNode }) => (
    <div>{children}</div>
  ),
  PromptInputFooter: ({ children }: { children: React.ReactNode }) => (
    <div>{children}</div>
  ),
  PromptInputHeader: ({ children }: { children: React.ReactNode }) => (
    <div>{children}</div>
  ),
  PromptInputProvider: ({ children }: { children: React.ReactNode }) => (
    <div>{children}</div>
  ),
  PromptInputSpeechButton: () => <button type="button">Speech</button>,
  PromptInputSubmit: ({ status }: { status?: string }) => (
    <button data-testid="prompt-submit" type="submit">
      Submit {status ?? "unset"}
    </button>
  ),
  PromptInputTextarea: ({
    placeholder,
    onKeyDown,
    disabled,
    "data-testid": testId,
  }: {
    placeholder?: string;
    onKeyDown?: React.KeyboardEventHandler<HTMLTextAreaElement>;
    disabled?: boolean;
    "data-testid"?: string;
  }) => (
    <textarea
      data-testid={testId}
      disabled={disabled}
      onKeyDown={onKeyDown}
      placeholder={placeholder}
    />
  ),
  PromptInputTools: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="prompt-tools">{children}</div>
  ),
  usePromptInputController: () => ({
    textInput: {
      value: mockControllerState.value,
      setInput: mockTextInputSetInput,
      clear: mockTextInputClear,
    },
    attachments: { files: [] },
  }),
  usePromptInputAttachments: () => ({
    openFileDialog: vi.fn(),
  }),
}));

vi.mock("@/components/chat/agent-tools-display", () => ({
  AgentToolsDisplay: () => <div data-testid="agent-tools-display" />,
}));

vi.mock("@/components/chat/llm-provider-api-key-selector", () => ({
  LlmProviderApiKeySelector: () => <div data-testid="chat-api-key-selector" />,
}));

vi.mock("@/components/chat/chat-tools-display", () => ({
  ChatToolsDisplay: () => <div data-testid="chat-tools-display" />,
}));

vi.mock("@/components/chat/knowledge-base-upload-indicator", () => ({
  KnowledgeBaseUploadIndicator: () => (
    <div data-testid="knowledge-base-indicator" />
  ),
}));

vi.mock("@/components/chat/model-selector", () => ({
  ModelSelector: () => <div data-testid="model-selector" />,
}));

// Mock the Tooltip components to avoid Radix UI complexity
vi.mock("@/components/ui/tooltip", () => ({
  Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}</>,
  TooltipContent: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="tooltip-content" role="tooltip">
      {children}
    </div>
  ),
  TooltipTrigger: ({ children }: { children: React.ReactNode }) => (
    <>{children}</>
  ),
}));

// Mock agent query hooks
vi.mock("@/lib/agent.query", () => ({
  useProfile: () => ({
    data: null,
    isLoading: false,
    error: null,
  }),
}));

// Mock the React Query hooks that the component uses
vi.mock("@/lib/agent-tools.query", () => ({
  useAgentDelegations: () => ({
    data: [],
    isLoading: false,
    error: null,
  }),
}));

vi.mock("@/lib/chat/chat.query", () => ({
  useProfileToolsWithIds: () => ({
    data: [],
    isLoading: false,
    error: null,
  }),
}));

vi.mock("@/lib/organization.query", () => ({
  useOrganization: () => mockUseOrganization(),
}));

vi.mock("@/lib/chat/chat-placeholder.hook", () => ({
  useChatPlaceholder: (...args: unknown[]) => mockUseChatPlaceholder(...args),
}));

// Mock for useHasPermissions - default to non-admin
const mockUseHasPermissions = vi.fn().mockReturnValue({
  data: false,
  isPending: false,
  isLoading: false,
});

vi.mock("@/lib/auth/auth.query", () => ({
  useHasPermissions: () => mockUseHasPermissions(),
}));

// Import the component after mocks are set up
import ArchestraPromptInput from "./prompt-input";

describe("ArchestraPromptInput", () => {
  const defaultProps = {
    onSubmit: vi.fn(),
    status: "ready" as const,
    selectedModel: "gpt-4",
    onModelChange: vi.fn(),
    agentId: "test-agent-id",
    isPlaywrightSetupVisible: false,
  };

  beforeEach(() => {
    vi.clearAllMocks();
    mockUseOrganization.mockReturnValue({
      data: null,
      isLoading: false,
    });
    mockUseChatPlaceholder.mockReturnValue({
      placeholder: "Animated placeholder",
      isAnimating: true,
    });
    mockControllerState.value = "";
  });

  describe("File Upload Button", () => {
    it("should render enabled file upload button when allowFileUploads is true and model supports files", () => {
      render(
        <ArchestraPromptInput
          {...defaultProps}
          allowFileUploads={true}
          inputModalities={["text", "image"]}
        />,
      );

      // Should find the enabled file upload button
      const enabledButton = screen.getByTestId(E2eTestId.ChatFileUploadButton);
      expect(enabledButton).toBeInTheDocument();

      // Should not find the disabled button
      expect(
        screen.queryByTestId(E2eTestId.ChatDisabledFileUploadButton),
      ).not.toBeInTheDocument();
    });

    it("should render disabled file upload button when allowFileUploads is false", () => {
      render(
        <ArchestraPromptInput
          {...defaultProps}
          allowFileUploads={false}
          inputModalities={["text", "image"]}
        />,
      );

      // Should find the disabled file upload button wrapper
      const disabledButton = screen.getByTestId(
        E2eTestId.ChatDisabledFileUploadButton,
      );
      expect(disabledButton).toBeInTheDocument();

      // Should not find the enabled button
      expect(
        screen.queryByTestId(E2eTestId.ChatFileUploadButton),
      ).not.toBeInTheDocument();
    });

    it("should render disabled file upload button when model does not support files", () => {
      render(
        <ArchestraPromptInput
          {...defaultProps}
          allowFileUploads={true}
          inputModalities={null}
        />,
      );

      // Should find the disabled file upload button wrapper
      const disabledButton = screen.getByTestId(
        E2eTestId.ChatDisabledFileUploadButton,
      );
      expect(disabledButton).toBeInTheDocument();

      // Tooltip should show message about model not supporting files
      const tooltip = screen.getByTestId("tooltip-content");
      expect(tooltip).toHaveTextContent(
        "This model does not support file uploads",
      );
    });

    it("should render enabled file upload button for text-only models", () => {
      render(
        <ArchestraPromptInput
          {...defaultProps}
          allowFileUploads={true}
          inputModalities={["text"]}
        />,
      );

      expect(
        screen.getByTestId(E2eTestId.ChatFileUploadButton),
      ).toBeInTheDocument();
      expect(screen.getByTestId("tooltip-content")).toHaveTextContent(
        "Supports: chat prompts, .txt, .csv, and .md uploads",
      );
    });

    it("should show settings link in tooltip for admins when file uploads disabled", () => {
      // Mock admin user with agentSettings update permission
      mockUseHasPermissions.mockReturnValue({
        data: true,
        isPending: false,
        isLoading: false,
      });

      render(
        <ArchestraPromptInput
          {...defaultProps}
          allowFileUploads={false}
          inputModalities={["text", "image"]}
        />,
      );

      // Tooltip should show "Enable in settings" link for admins
      const tooltip = screen.getByTestId("tooltip-content");
      expect(tooltip).toHaveTextContent("File uploads are disabled.");
      expect(tooltip).toHaveTextContent("Enable in settings");
      expect(screen.getByRole("link")).toHaveAttribute(
        "href",
        "/settings/agents",
      );
      expect(screen.getByRole("link")).toHaveAttribute(
        "aria-label",
        "Enable file uploads in agent settings",
      );
    });

    it("should show admin message in tooltip for non-admins when file uploads disabled", () => {
      // Mock non-admin user without agentSettings update permission
      mockUseHasPermissions.mockReturnValue({
        data: false,
        isPending: false,
        isLoading: false,
      });

      render(
        <ArchestraPromptInput
          {...defaultProps}
          allowFileUploads={false}
          inputModalities={["text", "image"]}
        />,
      );

      // Tooltip should show message about admin for non-admins
      const tooltip = screen.getByTestId("tooltip-content");
      expect(tooltip).toHaveTextContent(
        "File uploads are disabled by your administrator",
      );
      // Should not have a settings link
      expect(screen.queryByRole("link")).not.toBeInTheDocument();
    });
  });

  describe("Component rendering", () => {
    it("should render the prompt input form", () => {
      render(
        <ArchestraPromptInput {...defaultProps} allowFileUploads={true} />,
      );

      expect(screen.getByTestId("prompt-input")).toBeInTheDocument();
    });

    it("should show the submit state as ready after an error", () => {
      render(
        <ArchestraPromptInput
          {...defaultProps}
          status="error"
          allowFileUploads={true}
        />,
      );

      expect(screen.getByTestId("prompt-submit")).toHaveTextContent(
        "Submit ready",
      );
    });

    it("should render model selector when user has provider settings permission", () => {
      mockUseHasPermissions.mockReturnValue({
        data: true,
        isPending: false,
        isLoading: false,
      });

      render(
        <ArchestraPromptInput {...defaultProps} allowFileUploads={true} />,
      );

      expect(screen.getByTestId("model-selector")).toBeInTheDocument();
    });

    it("should keep a single organization placeholder static", () => {
      mockUseOrganization.mockReturnValue({
        data: {
          chatPlaceholders: ["Ask the support agent"],
          animateChatPlaceholders: true,
        },
        isLoading: false,
      });
      mockUseChatPlaceholder.mockReturnValue({
        placeholder: "Ask the support agent",
        isAnimating: false,
      });

      render(
        <ArchestraPromptInput {...defaultProps} allowFileUploads={true} />,
      );

      expect(mockUseChatPlaceholder).toHaveBeenCalledWith({
        animate: true,
        placeholders: ["Ask the support agent"],
      });
      expect(
        screen.getByPlaceholderText("Ask the support agent"),
      ).toBeInTheDocument();
      expect(
        screen.queryByPlaceholderText("Animated placeholder"),
      ).not.toBeInTheDocument();
    });

    it("should keep placeholders static when animation is disabled", () => {
      mockUseOrganization.mockReturnValue({
        data: {
          chatPlaceholders: ["First placeholder", "Second placeholder"],
          animateChatPlaceholders: false,
        },
        isLoading: false,
      });
      mockUseChatPlaceholder.mockReturnValue({
        placeholder: "Second placeholder",
        isAnimating: false,
      });

      render(
        <ArchestraPromptInput {...defaultProps} allowFileUploads={true} />,
      );

      expect(mockUseChatPlaceholder).toHaveBeenCalledWith({
        animate: false,
        placeholders: ["First placeholder", "Second placeholder"],
      });
      expect(
        screen.getByPlaceholderText("Second placeholder"),
      ).toBeInTheDocument();
      expect(
        screen.queryByPlaceholderText("Animated placeholder"),
      ).not.toBeInTheDocument();
    });

    it("should reset slash command selection when the menu reopens", () => {
      const onCompactConversation = vi.fn();
      mockControllerState.value = "/";

      const { rerender } = render(
        <ArchestraPromptInput
          {...defaultProps}
          conversationId="conversation-1"
          onCompactConversation={onCompactConversation}
        />,
      );

      fireEvent.keyDown(screen.getByTestId(E2eTestId.ChatPromptTextarea), {
        key: "ArrowDown",
      });

      mockControllerState.value = "";
      rerender(
        <ArchestraPromptInput
          {...defaultProps}
          conversationId="conversation-1"
          onCompactConversation={onCompactConversation}
        />,
      );

      mockControllerState.value = "/";
      rerender(
        <ArchestraPromptInput
          {...defaultProps}
          conversationId="conversation-1"
          onCompactConversation={onCompactConversation}
        />,
      );

      fireEvent.keyDown(screen.getByTestId(E2eTestId.ChatPromptTextarea), {
        key: "Enter",
      });

      expect(onCompactConversation).toHaveBeenCalledTimes(1);
      expect(mockTextInputClear).toHaveBeenCalled();
    });

    it("queues a follow-up submitted while the current response is streaming", () => {
      const onSubmit = vi.fn();
      mockControllerState.value = "Run this next";

      render(
        <ArchestraPromptInput
          {...defaultProps}
          onSubmit={onSubmit}
          status="streaming"
        />,
      );

      fireEvent.submit(screen.getByTestId("prompt-input"));

      expect(onSubmit).not.toHaveBeenCalled();
      expect(screen.getByText("Run this next")).toBeInTheDocument();
      expect(screen.getByLabelText("Queued prompts")).toBeInTheDocument();
    });

    it("submits the next queued follow-up when the chat is ready again", () => {
      const onSubmit = vi.fn();
      mockControllerState.value = "Run after streaming";

      const { rerender } = render(
        <ArchestraPromptInput
          {...defaultProps}
          onSubmit={onSubmit}
          status="streaming"
        />,
      );

      fireEvent.submit(screen.getByTestId("prompt-input"));

      rerender(
        <ArchestraPromptInput
          {...defaultProps}
          onSubmit={onSubmit}
          status="ready"
        />,
      );

      expect(onSubmit).toHaveBeenCalledWith(
        { text: "Run after streaming", files: [] },
        expect.objectContaining({ preventDefault: expect.any(Function) }),
      );
    });

    it("keeps the queued follow-up in the queue when submitting it fails", () => {
      const onSubmit = vi.fn(() => {
        throw new Error("submit failed");
      });
      mockControllerState.value = "Retain on failure";

      const { rerender } = render(
        <ArchestraPromptInput
          {...defaultProps}
          onSubmit={onSubmit}
          status="streaming"
        />,
      );

      fireEvent.submit(screen.getByTestId("prompt-input"));

      rerender(
        <ArchestraPromptInput
          {...defaultProps}
          onSubmit={onSubmit}
          status="ready"
        />,
      );

      expect(onSubmit).toHaveBeenCalledTimes(1);
      expect(screen.getByText("Retain on failure")).toBeInTheDocument();
    });

    it("does not submit queued follow-ups after switching conversations", () => {
      const onConversationASubmit = vi.fn();
      const onConversationBSubmit = vi.fn();
      mockControllerState.value = "Keep this in conversation A";

      const { rerender } = render(
        <ArchestraPromptInput
          {...defaultProps}
          conversationId="conversation-a"
          onSubmit={onConversationASubmit}
          status="streaming"
        />,
      );

      fireEvent.submit(screen.getByTestId("prompt-input"));

      expect(
        screen.getByText("Keep this in conversation A"),
      ).toBeInTheDocument();

      rerender(
        <ArchestraPromptInput
          {...defaultProps}
          conversationId="conversation-b"
          onSubmit={onConversationBSubmit}
          status="ready"
        />,
      );

      expect(onConversationASubmit).not.toHaveBeenCalled();
      expect(onConversationBSubmit).not.toHaveBeenCalled();
      expect(
        screen.queryByText("Keep this in conversation A"),
      ).not.toBeInTheDocument();
    });

    it("removes queued follow-ups from the prompt queue", () => {
      mockControllerState.value = "Remove this queued prompt";

      render(<ArchestraPromptInput {...defaultProps} status="streaming" />);

      fireEvent.submit(screen.getByTestId("prompt-input"));
      fireEvent.click(screen.getByLabelText("Remove queued prompt"));

      expect(
        screen.queryByText("Remove this queued prompt"),
      ).not.toBeInTheDocument();
    });
  });
});
