import type { ChatWithHistoryContextValue } from '../../context'
import type { AppData, ConversationItem } from '@/models/share'
import { act, render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useChatWithHistoryContext } from '../../context'
import Header from '../index'

// Mock context module
vi.mock('../../context', () => ({
  useChatWithHistoryContext: vi.fn(),
}))

// Mock InputsFormContent
vi.mock('@/app/components/base/chat/chat-with-history/inputs-form/content', () => ({
  default: () => <div data-testid="inputs-form-content">InputsFormContent</div>,
}))

vi.mock('@langgenius/dify-ui/dropdown-menu', () => import('@/__mocks__/base-ui-dropdown-menu'))
vi.mock('@langgenius/dify-ui/tooltip', () => import('@/__mocks__/base-ui-tooltip'))

// Mock Dialog to avoid Base UI focus/portal behavior in tests
vi.mock('@langgenius/dify-ui/dialog', () => ({
  Dialog: ({ children, open }: { children: React.ReactNode; open?: boolean }) => {
    if (!open) return null
    return <div data-testid="modal">{children}</div>
  },
  DialogContent: ({ children }: { children: React.ReactNode }) => (
    <div role="dialog" data-testid="modal-content">
      {children}
    </div>
  ),
  DialogTitle: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
}))

const mockAppData: AppData = {
  app_id: 'app-1',
  site: {
    title: 'Test App',
    icon_type: 'emoji',
    icon: '🤖',
    icon_background: '#fff',
    icon_url: '',
  },
  end_user_id: 'user-1',
  custom_config: null,
  can_replace_logo: false,
}

const mockContextDefaults: ChatWithHistoryContextValue = {
  appData: mockAppData,
  currentConversationId: '',
  currentConversationItem: undefined,
  inputsForms: [],
  pinnedConversationList: [],
  handlePinConversation: vi.fn(),
  handleUnpinConversation: vi.fn(),
  handleRenameConversation: vi.fn(),
  handleDeleteConversation: vi.fn(),
  handleNewConversation: vi.fn(),
  sidebarCollapseState: true,
  handleSidebarCollapse: vi.fn(),
  isResponding: false,
  conversationRenaming: false,
  showConfig: false,
} as unknown as ChatWithHistoryContextValue

const setup = (overrides: Partial<ChatWithHistoryContextValue> = {}) => {
  vi.mocked(useChatWithHistoryContext).mockReturnValue({
    ...mockContextDefaults,
    ...overrides,
  })
  return render(<Header />)
}

describe('Header Component', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  describe('Rendering', () => {
    it('should render conversation name when conversation is selected', () => {
      const mockConv = { id: 'conv-1', name: 'My Chat' } as ConversationItem
      setup({
        currentConversationId: 'conv-1',
        currentConversationItem: mockConv,
        sidebarCollapseState: true,
      })
      expect(screen.getByText('My Chat'))!.toBeInTheDocument()
    })

    it('should render ViewFormDropdown trigger when inputsForms are present', () => {
      const mockConv = { id: 'conv-1', name: 'My Chat' } as ConversationItem
      setup({
        currentConversationId: 'conv-1',
        currentConversationItem: mockConv,
        inputsForms: [{ id: 'form-1' }],
      })

      const buttons = screen.getAllByRole('button')
      // Sidebar(1) + Conversation operation(1) + NewChat(1) + ResetChat(1) + ViewForm(1) = 5 buttons
      expect(buttons).toHaveLength(5)
    })
  })

  describe('Interactions', () => {
    it('should handle new conversation', async () => {
      const handleNewConversation = vi.fn()
      setup({ handleNewConversation, sidebarCollapseState: true, currentConversationId: 'conv-1' })

      const buttons = screen.getAllByRole('button')
      // Sidebar, NewChat, ResetChat (3)
      const resetChatBtn = buttons[buttons.length - 1]
      await userEvent.click(resetChatBtn!)

      expect(handleNewConversation).toHaveBeenCalled()
    })

    it('should handle sidebar toggle', async () => {
      const handleSidebarCollapse = vi.fn()
      setup({ handleSidebarCollapse, sidebarCollapseState: true })

      const buttons = screen.getAllByRole('button')
      const sidebarBtn = buttons[0]
      await userEvent.click(sidebarBtn!)

      expect(handleSidebarCollapse).toHaveBeenCalledWith(false)
    })

    it('should render operation menu and handle pin', async () => {
      const handlePinConversation = vi.fn()
      const mockConv = { id: 'conv-1', name: 'My Chat' } as ConversationItem
      setup({
        currentConversationId: 'conv-1',
        currentConversationItem: mockConv,
        handlePinConversation,
        sidebarCollapseState: true,
      })

      const trigger = screen.getByText('My Chat')
      await userEvent.click(trigger)

      const pinBtn = await screen.findByText('explore.sidebar.action.pin')
      expect(pinBtn)!.toBeInTheDocument()

      await userEvent.click(pinBtn)

      expect(handlePinConversation).toHaveBeenCalledWith('conv-1')
    })

    it('should handle unpin', async () => {
      const handleUnpinConversation = vi.fn()
      const mockConv = { id: 'conv-1', name: 'My Chat' } as ConversationItem
      setup({
        currentConversationId: 'conv-1',
        currentConversationItem: mockConv,
        handleUnpinConversation,
        pinnedConversationList: [{ id: 'conv-1' } as ConversationItem],
        sidebarCollapseState: true,
      })

      await userEvent.click(screen.getByText('My Chat'))

      const unpinBtn = await screen.findByText('explore.sidebar.action.unpin')
      await userEvent.click(unpinBtn)

      expect(handleUnpinConversation).toHaveBeenCalledWith('conv-1')
    })

    it('should handle rename cancellation', async () => {
      const mockConv = { id: 'conv-1', name: 'My Chat' } as ConversationItem
      setup({
        currentConversationId: 'conv-1',
        currentConversationItem: mockConv,
        sidebarCollapseState: true,
      })

      await userEvent.click(screen.getByText('My Chat'))

      const renameMenuBtn = await screen.findByText('explore.sidebar.action.rename')
      await userEvent.click(renameMenuBtn)

      const cancelBtn = await screen.findByText('common.operation.cancel')
      await userEvent.click(cancelBtn)

      await waitFor(() => {
        expect(screen.queryByText('common.chat.renameConversation')).not.toBeInTheDocument()
      })
    })

    it('should handle rename success flow', async () => {
      const handleRenameConversation = vi.fn()
      const mockConv = { id: 'conv-1', name: 'My Chat' } as ConversationItem
      setup({
        currentConversationId: 'conv-1',
        currentConversationItem: mockConv,
        handleRenameConversation,
        sidebarCollapseState: true,
      })

      await userEvent.click(screen.getByText('My Chat'))

      const renameMenuBtn = await screen.findByText('explore.sidebar.action.rename')
      await userEvent.click(renameMenuBtn)

      expect(await screen.findByText('common.chat.renameConversation'))!.toBeInTheDocument()

      const input = screen.getByDisplayValue('My Chat')
      await userEvent.clear(input)
      await userEvent.type(input, 'New Name')

      const saveBtn = await screen.findByText('common.operation.save')
      await userEvent.click(saveBtn)

      expect(handleRenameConversation).toHaveBeenCalledWith(
        'conv-1',
        'New Name',
        expect.any(Object),
      )

      const successCallback = handleRenameConversation.mock.calls[0]![2].onSuccess
      await act(async () => {
        successCallback()
      })

      await waitFor(() => {
        expect(screen.queryByText('common.chat.renameConversation')).not.toBeInTheDocument()
      })
    })

    it('should handle delete flow', async () => {
      const handleDeleteConversation = vi.fn()
      const mockConv = { id: 'conv-1', name: 'My Chat' } as ConversationItem
      setup({
        currentConversationId: 'conv-1',
        currentConversationItem: mockConv,
        handleDeleteConversation,
        sidebarCollapseState: true,
      })

      await userEvent.click(screen.getByText('My Chat'))

      const deleteMenuBtn = await screen.findByText('explore.sidebar.action.delete')
      await userEvent.click(deleteMenuBtn)

      expect(handleDeleteConversation).not.toHaveBeenCalled()
      expect(await screen.findByText('share.chat.deleteConversation.title'))!.toBeInTheDocument()

      const confirmBtn = await screen.findByText('common.operation.confirm')
      await userEvent.click(confirmBtn)

      expect(handleDeleteConversation).toHaveBeenCalledWith('conv-1', expect.any(Object))

      const successCallback = handleDeleteConversation.mock.calls[0]![1].onSuccess
      await act(async () => {
        successCallback()
      })

      await waitFor(() => {
        expect(screen.queryByText('share.chat.deleteConversation.title')).not.toBeInTheDocument()
      })
    })

    it('should handle delete cancellation', async () => {
      const mockConv = { id: 'conv-1', name: 'My Chat' } as ConversationItem
      setup({
        currentConversationId: 'conv-1',
        currentConversationItem: mockConv,
        sidebarCollapseState: true,
      })

      await userEvent.click(screen.getByText('My Chat'))

      const deleteMenuBtn = await screen.findByText('explore.sidebar.action.delete')
      await userEvent.click(deleteMenuBtn)

      const cancelBtn = await screen.findByText('common.operation.cancel')
      await userEvent.click(cancelBtn)

      await waitFor(() => {
        expect(screen.queryByText('share.chat.deleteConversation.title')).not.toBeInTheDocument()
      })
    })

    it('should handle empty translated delete content via fallback', async () => {
      const mockConv = { id: 'conv-1', name: 'My Chat' } as ConversationItem
      setup({
        currentConversationId: 'conv-1',
        currentConversationItem: mockConv,
        sidebarCollapseState: true,
      })

      await userEvent.click(screen.getByText('My Chat'))
      await userEvent.click(await screen.findByText('explore.sidebar.action.delete'))

      expect(await screen.findByText('share.chat.deleteConversation.title'))!.toBeInTheDocument()
    })
  })

  describe('Edge Cases', () => {
    it('should not render inputs form dropdown if inputsForms is empty', () => {
      const mockConv = { id: 'conv-1', name: 'My Chat' } as ConversationItem
      setup({
        currentConversationId: 'conv-1',
        currentConversationItem: mockConv,
        inputsForms: [],
      })

      const buttons = screen.getAllByRole('button')
      // Sidebar(1) + Conversation operation(1) + NewChat(1) + ResetChat(1) = 4 buttons
      expect(buttons).toHaveLength(4)
    })

    it('should render system title if conversation id is missing', () => {
      setup({ currentConversationId: '', sidebarCollapseState: true })
      const titleEl = screen.getByText('Test App')
      expect(titleEl)!.toHaveClass('system-md-semibold')
    })

    it('should render app icon from URL when icon_url is provided', () => {
      setup({
        appData: {
          ...mockAppData,
          site: {
            ...mockAppData.site,
            icon_type: 'image',
            icon_url: 'https://example.com/icon.png',
          },
        },
      })
      const img = screen.getByAltText('app icon')
      expect(img)!.toHaveAttribute('src', 'https://example.com/icon.png')
    })

    it('should handle undefined appData gracefully (optional chaining)', () => {
      setup({ appData: null as unknown as AppData })
      // Just verify it doesn't crash and renders the basic structure
      expect(screen.getAllByRole('button').length).toBeGreaterThan(0)
    })

    it('should handle missing name in conversation item', () => {
      const mockConv = { id: 'conv-1', name: '' } as ConversationItem
      setup({
        currentConversationId: 'conv-1',
        currentConversationItem: mockConv,
        sidebarCollapseState: true,
      })
      // The separator is just a div with text content '/'
      // The separator is just a div with text content '/'
      expect(screen.getByText('/'))!.toBeInTheDocument()
    })

    it('should handle New Chat button state when currentConversationId is present but isResponding is true', () => {
      setup({
        isResponding: true,
        sidebarCollapseState: true,
        currentConversationId: 'conv-1',
      })

      const buttons = screen.getAllByRole('button')
      // Sidebar, NewChat, ResetChat (3)
      const newChatBtn = buttons[1]
      expect(newChatBtn)!.toBeDisabled()
    })

    it('should handle New Chat button state when currentConversationId is missing and isResponding is false', () => {
      setup({
        isResponding: false,
        sidebarCollapseState: true,
        currentConversationId: '',
      })

      const buttons = screen.getAllByRole('button')
      // Sidebar, NewChat (2)
      const newChatBtn = buttons[1]
      expect(newChatBtn)!.toBeDisabled()
    })

    it('should not render operation menu if conversation id is missing', () => {
      setup({ currentConversationId: '', sidebarCollapseState: true })
      expect(screen.queryByText('My Chat')).not.toBeInTheDocument()
    })

    it('should not render operation menu if sidebar is NOT collapsed', () => {
      const mockConv = { id: 'conv-1', name: 'My Chat' } as ConversationItem
      setup({
        currentConversationId: 'conv-1',
        currentConversationItem: mockConv,
        sidebarCollapseState: false,
      })
      expect(screen.queryByText('My Chat')).not.toBeInTheDocument()
    })

    it('should pass empty rename value when conversation name is undefined', async () => {
      const mockConv = { id: 'conv-1' } as ConversationItem
      const { container } = setup({
        currentConversationId: 'conv-1',
        currentConversationItem: mockConv,
        sidebarCollapseState: true,
      })

      const operationTrigger = container.querySelector(
        '.flex.cursor-pointer.items-center.rounded-lg.p-1\\.5.pl-2.text-text-secondary.hover\\:bg-state-base-hover',
      ) as HTMLElement
      await userEvent.click(operationTrigger)
      await userEvent.click(await screen.findByText('explore.sidebar.action.rename'))

      const input = screen.getByRole('textbox') as HTMLInputElement
      expect(input.value).toBe('')
    })
  })
})
