import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import userEvent from '@testing-library/user-event'
import { RouterProvider, createRouter, createRootRoute, createMemoryHistory } from '@tanstack/react-router'
import ChatInput from '../ChatInput'
import { usePrompt } from '@/hooks/usePrompt'
import { useThreads } from '@/hooks/useThreads'
import { useAppState } from '@/hooks/useAppState'
import { useGeneralSetting } from '@/hooks/useGeneralSetting'
import { useModelProvider } from '@/hooks/useModelProvider'
import { useChat } from '@/hooks/useChat'

// Mock dependencies
vi.mock('@/hooks/usePrompt', () => ({
  usePrompt: vi.fn(() => ({
    prompt: '',
    setPrompt: vi.fn(),
  })),
}))

vi.mock('@/hooks/useThreads', () => ({
  useThreads: vi.fn(() => ({
    currentThreadId: null,
    getCurrentThread: vi.fn(),
  })),
}))

vi.mock('@/hooks/useAppState', () => ({
  useAppState: vi.fn(() => ({
    streamingContent: '',
    abortController: null,
  })),
}))

vi.mock('@/hooks/useGeneralSetting', () => ({
  useGeneralSetting: vi.fn(() => ({
    allowSendWhenUnloaded: false,
  })),
}))

vi.mock('@/hooks/useModelProvider', () => ({
  useModelProvider: vi.fn(() => ({
    selectedModel: null,
    providers: [],
    getModelBy: vi.fn(),
    selectModelProvider: vi.fn(),
    selectedProvider: 'llamacpp',
    setProviders: vi.fn(),
    getProviderByName: vi.fn(),
    updateProvider: vi.fn(),
    addProvider: vi.fn(),
    deleteProvider: vi.fn(),
    deleteModel: vi.fn(),
    deletedModels: [],
  })),
}))

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

vi.mock('@/i18n/react-i18next-compat', () => ({
  useTranslation: () => ({
    t: (key: string) => key,
  }),
}))

vi.mock('@/services/mcp', () => ({
  getConnectedServers: vi.fn(() => Promise.resolve([])),
}))

vi.mock('@/services/models', () => ({
  stopAllModels: vi.fn(),
}))

describe('ChatInput', () => {
  const mockSendMessage = vi.fn()
  const mockSetPrompt = vi.fn()

  const createTestRouter = () => {
    const MockComponent = () => <ChatInput />
    const rootRoute = createRootRoute({
      component: MockComponent,
    })

    return createRouter({ 
      routeTree: rootRoute,
      history: createMemoryHistory({
        initialEntries: ['/'],
      }),
    })
  }

  const renderWithRouter = (component = <ChatInput />) => {
    const router = createTestRouter()
    return render(<RouterProvider router={router} />)
  }

  beforeEach(() => {
    vi.clearAllMocks()
    
    // Set up default mock returns
    vi.mocked(usePrompt).mockReturnValue({
      prompt: '',
      setPrompt: mockSetPrompt,
    })
    
    vi.mocked(useThreads).mockReturnValue({
      currentThreadId: 'test-thread-id',
      getCurrentThread: vi.fn(),
      setCurrentThreadId: vi.fn(),
    })
    
    vi.mocked(useAppState).mockReturnValue({
      streamingContent: null,
      abortControllers: {},
      loadingModel: false,
      tools: [],
    })
    
    vi.mocked(useGeneralSetting).mockReturnValue({
      spellCheckChatInput: true,
      allowSendWhenUnloaded: false,
      experimentalFeatures: true,
    })
    
    vi.mocked(useModelProvider).mockReturnValue({
      selectedModel: {
        id: 'test-model',
        capabilities: ['tools', 'vision'],
      },
      providers: [
        {
          provider: 'llamacpp',
          models: [
            {
              id: 'test-model',
              capabilities: ['tools', 'vision'],
            }
          ]
        }
      ],
      getModelBy: vi.fn(() => ({
        id: 'test-model',
        capabilities: ['tools', 'vision'],
      })),
      selectModelProvider: vi.fn(),
      selectedProvider: 'llamacpp',
      setProviders: vi.fn(),
      getProviderByName: vi.fn(),
      updateProvider: vi.fn(),
      addProvider: vi.fn(),
      deleteProvider: vi.fn(),
      deleteModel: vi.fn(),
      deletedModels: [],
    })
    
    vi.mocked(useChat).mockReturnValue({
      sendMessage: mockSendMessage,
    })
  })

  it('renders chat input textarea', () => {
    act(() => {
      renderWithRouter()
    })
    
    const textarea = screen.getByRole('textbox')
    expect(textarea).toBeInTheDocument()
    expect(textarea).toHaveAttribute('placeholder', 'common:placeholder.chatInput')
  })

  it('renders send button', () => {
    act(() => {
      renderWithRouter()
    })
    
    const sendButton = document.querySelector('[data-test-id="send-message-button"]')
    expect(sendButton).toBeInTheDocument()
  })

  it('disables send button when prompt is empty', () => {
    act(() => {
      renderWithRouter()
    })
    
    const sendButton = document.querySelector('[data-test-id="send-message-button"]')
    expect(sendButton).toBeDisabled()
  })

  it('enables send button when prompt has content', () => {
    // Mock prompt with content
    vi.mocked(usePrompt).mockReturnValue({
      prompt: 'Hello world',
      setPrompt: mockSetPrompt,
    })
    
    act(() => {
      renderWithRouter()
    })
    
    const sendButton = document.querySelector('[data-test-id="send-message-button"]')
    expect(sendButton).not.toBeDisabled()
  })

  it('calls setPrompt when typing in textarea', async () => {
    const user = userEvent.setup()
    renderWithRouter()
    
    const textarea = screen.getByRole('textbox')
    await user.type(textarea, 'Hello')
    
    // setPrompt is called for each character typed
    expect(mockSetPrompt).toHaveBeenCalledTimes(5)
    expect(mockSetPrompt).toHaveBeenLastCalledWith('o')
  })

  it('calls sendMessage when send button is clicked', async () => {
    const user = userEvent.setup()
    
    // Mock prompt with content
    vi.mocked(usePrompt).mockReturnValue({
      prompt: 'Hello world',
      setPrompt: mockSetPrompt,
    })
    
    renderWithRouter()
    
    const sendButton = document.querySelector('[data-test-id="send-message-button"]')
    await user.click(sendButton)
    
    expect(mockSendMessage).toHaveBeenCalledWith('Hello world')
  })

  it('sends message when Enter key is pressed', async () => {
    const user = userEvent.setup()
    
    // Mock prompt with content
    vi.mocked(usePrompt).mockReturnValue({
      prompt: 'Hello world',
      setPrompt: mockSetPrompt,
    })
    
    renderWithRouter()
    
    const textarea = screen.getByRole('textbox')
    await user.type(textarea, '{Enter}')
    
    expect(mockSendMessage).toHaveBeenCalledWith('Hello world')
  })

  it('does not send message when Shift+Enter is pressed', async () => {
    const user = userEvent.setup()
    
    // Mock prompt with content
    vi.mocked(usePrompt).mockReturnValue({
      prompt: 'Hello world',
      setPrompt: mockSetPrompt,
    })
    
    renderWithRouter()
    
    const textarea = screen.getByRole('textbox')
    await user.type(textarea, '{Shift>}{Enter}{/Shift}')
    
    expect(mockSendMessage).not.toHaveBeenCalled()
  })

  it('shows stop button when streaming', () => {
    // Mock streaming state
    vi.mocked(useAppState).mockReturnValue({
      streamingContent: { thread_id: 'test-thread' },
      abortControllers: {},
      loadingModel: false,
      tools: [],
    })
    
    act(() => {
      renderWithRouter()
    })
    
    // Stop button should be rendered (as SVG with tabler-icon-player-stop-filled class)
    const stopButton = document.querySelector('.tabler-icon-player-stop-filled')
    expect(stopButton).toBeInTheDocument()
  })

  it('shows capability icons when model supports them', () => {
    act(() => {
      renderWithRouter()
    })
    
    // Should show vision icon (rendered as SVG with tabler-icon-eye class)
    const visionIcon = document.querySelector('.tabler-icon-eye')
    expect(visionIcon).toBeInTheDocument()
  })

  it('shows model selection dropdown', () => {
    act(() => {
      renderWithRouter()
    })
    
    // Model selection dropdown should be rendered (look for popover trigger)
    const modelDropdown = document.querySelector('[data-slot="popover-trigger"]')
    expect(modelDropdown).toBeInTheDocument()
  })

  it('shows error message when no model is selected', async () => {
    const user = userEvent.setup()
    
    // Mock no selected model and prompt with content
    vi.mocked(useModelProvider).mockReturnValue({
      selectedModel: null,
      providers: [],
      getModelBy: vi.fn(),
      selectModelProvider: vi.fn(),
      selectedProvider: 'llamacpp',
      setProviders: vi.fn(),
      getProviderByName: vi.fn(),
      updateProvider: vi.fn(),
      addProvider: vi.fn(),
      deleteProvider: vi.fn(),
      deleteModel: vi.fn(),
      deletedModels: [],
    })
    
    vi.mocked(usePrompt).mockReturnValue({
      prompt: 'Hello world',
      setPrompt: mockSetPrompt,
    })
    
    renderWithRouter()
    
    const sendButton = document.querySelector('[data-test-id="send-message-button"]')
    await user.click(sendButton)
    
    // The component should still render without crashing when no model is selected
    expect(sendButton).toBeInTheDocument()
  })

  it('handles file upload', async () => {
    const user = userEvent.setup()
    renderWithRouter()
    
    // File upload is rendered as hidden input element
    const fileInput = document.querySelector('input[type="file"]')
    expect(fileInput).toBeInTheDocument()
  })

  it('disables input when streaming', () => {
    // Mock streaming state
    vi.mocked(useAppState).mockReturnValue({
      streamingContent: { thread_id: 'test-thread' },
      abortControllers: {},
      loadingModel: false,
      tools: [],
    })
    
    act(() => {
      renderWithRouter()
    })
    
    const textarea = screen.getByRole('textbox')
    expect(textarea).toBeDisabled()
  })

  it('shows tools dropdown when model supports tools and MCP servers are connected', async () => {
    // Mock connected servers
    const { getConnectedServers } = await import('@/services/mcp')
    vi.mocked(getConnectedServers).mockResolvedValue(['server1'])
    
    renderWithRouter()
    
    await waitFor(() => {
      // Tools dropdown should be rendered (as SVG icon with tabler-icon-tool class)
      const toolsIcon = document.querySelector('.tabler-icon-tool')
      expect(toolsIcon).toBeInTheDocument()
    })
  })
})