/**
 * @file Chat.test.tsx
 * @description Tests for Chat component error handling in imperative handle methods
 */

import { createRef } from "react";
import { render } from "@testing-library/react";
import { describe, it, expect, beforeEach, vi, afterEach, type MockedFunction } from "vitest";
import { Chat, type ChatRef } from "./Chat";
import * as useChatModule from "@/hooks/useChat";
import * as useWebSocketModule from "@/hooks/useWebSocket";
import * as authModule from "@/lib/auth/auth-context";

// Mock scrollIntoView (not available in JSDOM)
Element.prototype.scrollIntoView = vi.fn();

// Mock dependencies
vi.mock("@/lib/auth/auth-context", () => ({
  useAuth: vi.fn(),
}));

vi.mock("@/hooks/useChat", () => ({
  useChat: vi.fn(),
}));

vi.mock("@/hooks/useWebSocket", () => ({
  useWebSocket: vi.fn(),
}));

vi.mock("./MessageList", () => ({
  MessageList: (): React.ReactElement => <div data-testid="message-list" />,
}));

vi.mock("./ChatInput", () => ({
  ChatInput: ({
    onSend,
  }: {
    onSend: (content: string) => Promise<void>;
    disabled: boolean;
    inputRef: React.RefObject<HTMLTextAreaElement | null>;
  }): React.ReactElement => (
    <button data-testid="chat-input" onClick={(): void => void onSend("test message")}>
      Send
    </button>
  ),
}));

const mockUseAuth = authModule.useAuth as MockedFunction<typeof authModule.useAuth>;
const mockUseChat = useChatModule.useChat as MockedFunction<typeof useChatModule.useChat>;
const mockUseWebSocket = useWebSocketModule.useWebSocket as MockedFunction<
  typeof useWebSocketModule.useWebSocket
>;

function createMockUseChatReturn(
  overrides: Partial<useChatModule.UseChatReturn> = {}
): useChatModule.UseChatReturn {
  return {
    messages: [
      {
        id: "welcome",
        role: "assistant",
        content: "Hello!",
        createdAt: new Date().toISOString(),
      },
    ],
    isLoading: false,
    error: null,
    conversationId: null,
    conversationTitle: null,
    sendMessage: vi.fn().mockResolvedValue(undefined),
    loadConversation: vi.fn().mockResolvedValue(undefined),
    startNewConversation: vi.fn(),
    setMessages: vi.fn(),
    clearError: vi.fn(),
    ...overrides,
  };
}

describe("Chat", () => {
  let consoleSpy: ReturnType<typeof vi.spyOn>;

  beforeEach(() => {
    vi.clearAllMocks();
    consoleSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);

    mockUseAuth.mockReturnValue({
      user: { id: "user-1", name: "Test User", email: "test@test.com" },
      isAuthenticated: true,
      isLoading: false,
      login: vi.fn(),
      logout: vi.fn(),
    } as unknown as ReturnType<typeof authModule.useAuth>);

    mockUseWebSocket.mockReturnValue({
      isConnected: true,
      socket: null,
      connectionError: null,
    });
  });

  afterEach(() => {
    consoleSpy.mockRestore();
  });

  describe("loadConversation via ref", () => {
    it("should successfully load a conversation", async () => {
      const mockLoadConversation = vi.fn().mockResolvedValue(undefined);
      mockUseChat.mockReturnValue(
        createMockUseChatReturn({ loadConversation: mockLoadConversation })
      );

      const ref = createRef<ChatRef>();
      render(<Chat ref={ref} />);

      await ref.current?.loadConversation("conv-123");

      expect(mockLoadConversation).toHaveBeenCalledWith("conv-123");
    });

    it("should log error context and re-throw on network failure", async () => {
      const networkError = new Error("Network request failed");
      const mockLoadConversation = vi.fn().mockRejectedValue(networkError);
      mockUseChat.mockReturnValue(
        createMockUseChatReturn({ loadConversation: mockLoadConversation })
      );

      const ref = createRef<ChatRef>();
      render(<Chat ref={ref} />);

      await expect(ref.current?.loadConversation("conv-123")).rejects.toThrow(
        "Network request failed"
      );

      expect(consoleSpy).toHaveBeenCalledWith(
        "Failed to load conversation",
        expect.objectContaining({
          error: networkError,
          conversationId: "conv-123",
        })
      );
    });

    it("should log error context and re-throw on API error (500)", async () => {
      const apiError = new Error("Internal Server Error");
      const mockLoadConversation = vi.fn().mockRejectedValue(apiError);
      mockUseChat.mockReturnValue(
        createMockUseChatReturn({ loadConversation: mockLoadConversation })
      );

      const ref = createRef<ChatRef>();
      render(<Chat ref={ref} />);

      await expect(ref.current?.loadConversation("conv-456")).rejects.toThrow(
        "Internal Server Error"
      );

      expect(consoleSpy).toHaveBeenCalledWith(
        "Failed to load conversation",
        expect.objectContaining({
          conversationId: "conv-456",
        })
      );
    });

    it("should re-throw error for caller to handle", async () => {
      const mockLoadConversation = vi.fn().mockRejectedValue(new Error("Auth failed"));
      mockUseChat.mockReturnValue(
        createMockUseChatReturn({ loadConversation: mockLoadConversation })
      );

      const ref = createRef<ChatRef>();
      render(<Chat ref={ref} />);

      // Verify the error propagates to the caller
      await expect(ref.current?.loadConversation("conv-789")).rejects.toThrow("Auth failed");
    });
  });

  describe("sendMessage delegation", () => {
    it("should delegate to useChat.sendMessage without extra error handling", async () => {
      const mockSendMessage = vi.fn().mockResolvedValue(undefined);
      mockUseChat.mockReturnValue(createMockUseChatReturn({ sendMessage: mockSendMessage }));

      const ref = createRef<ChatRef>();
      const { getByTestId } = render(<Chat ref={ref} />);

      const sendButton = getByTestId("chat-input");
      sendButton.click();

      await vi.waitFor(() => {
        expect(mockSendMessage).toHaveBeenCalledWith("test message");
      });
    });
  });
});
