import type { DocumentItem } from '@/models/datasets'
import { fireEvent, render, screen } from '@testing-library/react'
import * as React from 'react'
import PreviewDocumentPicker from '../preview-document-picker'

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

// Factory function to create mock DocumentItem
const createMockDocumentItem = (overrides: Partial<DocumentItem> = {}): DocumentItem => ({
  id: `doc-${Math.random().toString(36).substr(2, 9)}`,
  name: 'Test Document',
  extension: 'txt',
  ...overrides,
})

// Factory function to create multiple document items
const createMockDocumentList = (count: number): DocumentItem[] => {
  return Array.from({ length: count }, (_, index) =>
    createMockDocumentItem({
      id: `doc-${index + 1}`,
      name: `Document ${index + 1}`,
      extension: index % 2 === 0 ? 'pdf' : 'txt',
    }))
}

// Factory function to create default props
const createDefaultProps = (overrides: Partial<React.ComponentProps<typeof PreviewDocumentPicker>> = {}) => ({
  value: createMockDocumentItem({ id: 'selected-doc', name: 'Selected Document' }),
  files: createMockDocumentList(3),
  onChange: vi.fn(),
  ...overrides,
})

// Helper to render component with default props
const renderComponent = (props: Partial<React.ComponentProps<typeof PreviewDocumentPicker>> = {}) => {
  const defaultProps = createDefaultProps(props)
  return {
    ...render(<PreviewDocumentPicker {...defaultProps} />),
    props: defaultProps,
  }
}

const openPopover = () => {
  fireEvent.click(screen.getByTestId('popover-trigger'))
}

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

  // Tests for basic rendering
  describe('Rendering', () => {
    it('should render without crashing', () => {
      renderComponent()

      expect(screen.getByTestId('popover')).toBeInTheDocument()
    })

    it('should render document name from value prop', () => {
      renderComponent({
        value: createMockDocumentItem({ name: 'My Document' }),
      })

      expect(screen.getByText('My Document')).toBeInTheDocument()
    })

    it('should render placeholder when name is empty', () => {
      renderComponent({
        value: createMockDocumentItem({ name: '' }),
      })

      expect(screen.getByText('--')).toBeInTheDocument()
    })

    it('should render placeholder when name is undefined', () => {
      renderComponent({
        value: { id: 'doc-1', extension: 'txt' } as DocumentItem,
      })

      expect(screen.getByText('--')).toBeInTheDocument()
    })

    it('should render file icon', () => {
      renderComponent({
        value: createMockDocumentItem({ extension: 'txt' }),
        files: [], // Use empty files to avoid duplicate icons
      })

      const trigger = screen.getByTestId('popover-trigger')
      expect(trigger.querySelector('svg')).toBeInTheDocument()
    })

    it('should render pdf icon for pdf extension', () => {
      renderComponent({
        value: createMockDocumentItem({ extension: 'pdf' }),
        files: [], // Use empty files to avoid duplicate icons
      })

      const trigger = screen.getByTestId('popover-trigger')
      expect(trigger.querySelector('svg')).toBeInTheDocument()
    })
  })

  // Tests for props handling
  describe('Props', () => {
    it('should accept required props', () => {
      const props = createDefaultProps()
      render(<PreviewDocumentPicker {...props} />)

      expect(screen.getByTestId('popover')).toBeInTheDocument()
    })

    it('should apply className to trigger element', () => {
      renderComponent({ className: 'custom-class' })

      const trigger = screen.getByTestId('popover-trigger')
      expect(trigger).toHaveClass('custom-class')
    })

    it('should handle empty files array', () => {
      // Component should render without crashing with empty files
      renderComponent({ files: [] })

      expect(screen.getByTestId('popover')).toBeInTheDocument()
    })

    it('should handle single file', () => {
      // Component should accept single file
      renderComponent({
        files: [createMockDocumentItem({ id: 'single-doc', name: 'Single File' })],
      })

      expect(screen.getByTestId('popover')).toBeInTheDocument()
    })

    it('should handle multiple files', () => {
      // Component should accept multiple files
      renderComponent({
        files: createMockDocumentList(5),
      })

      expect(screen.getByTestId('popover')).toBeInTheDocument()
    })

    it('should use value.extension for file icon', () => {
      renderComponent({
        value: createMockDocumentItem({ name: 'test.docx', extension: 'docx' }),
      })

      const trigger = screen.getByTestId('popover-trigger')
      expect(trigger.querySelector('svg')).toBeInTheDocument()
    })
  })

  // Tests for state management
  describe('State Management', () => {
    it('should initialize with popup closed', () => {
      renderComponent()

      expect(screen.getByTestId('popover')).toHaveAttribute('data-open', 'false')
    })

    it('should toggle popup when trigger is clicked', () => {
      renderComponent()

      const trigger = screen.getByTestId('popover-trigger')
      fireEvent.click(trigger)

      expect(trigger).toBeInTheDocument()
    })

    it('should render portal content for document selection', () => {
      renderComponent()
      openPopover()

      // Popover content is rendered after opening the trigger in our mock
      expect(screen.getByTestId('popover-content')).toBeInTheDocument()
    })
  })

  // Tests for callback stability and memoization
  describe('Callback Stability', () => {
    it('should maintain stable onChange callback when value changes', () => {
      const onChange = vi.fn()
      const value1 = createMockDocumentItem({ id: 'doc-1', name: 'Doc 1' })
      const value2 = createMockDocumentItem({ id: 'doc-2', name: 'Doc 2' })

      const { rerender } = render(
        <PreviewDocumentPicker
          value={value1}
          files={createMockDocumentList(3)}
          onChange={onChange}
        />,
      )

      rerender(
        <PreviewDocumentPicker
          value={value2}
          files={createMockDocumentList(3)}
          onChange={onChange}
        />,
      )

      expect(screen.getByText('Doc 2')).toBeInTheDocument()
    })

    it('should use updated onChange callback after rerender', () => {
      const onChange1 = vi.fn()
      const onChange2 = vi.fn()
      const value = createMockDocumentItem()
      const files = createMockDocumentList(3)

      const { rerender } = render(
        <PreviewDocumentPicker value={value} files={files} onChange={onChange1} />,
      )

      rerender(
        <PreviewDocumentPicker value={value} files={files} onChange={onChange2} />,
      )

      expect(screen.getByTestId('popover')).toBeInTheDocument()
    })
  })

  // Tests for component memoization
  describe('Component Memoization', () => {
    it('should be wrapped with React.memo', () => {
      expect((PreviewDocumentPicker as unknown as { $$typeof: symbol }).$$typeof).toBeDefined()
    })

    it('should not re-render when props are the same', () => {
      const onChange = vi.fn()
      const value = createMockDocumentItem()
      const files = createMockDocumentList(3)

      const { rerender } = render(
        <PreviewDocumentPicker value={value} files={files} onChange={onChange} />,
      )

      rerender(
        <PreviewDocumentPicker value={value} files={files} onChange={onChange} />,
      )

      expect(screen.getByTestId('popover')).toBeInTheDocument()
    })
  })

  // Tests for user interactions
  describe('User Interactions', () => {
    it('should toggle popup when trigger is clicked', () => {
      renderComponent()

      const trigger = screen.getByTestId('popover-trigger')
      fireEvent.click(trigger)

      expect(trigger).toBeInTheDocument()
    })

    it('should render document list with files', () => {
      const files = createMockDocumentList(3)
      renderComponent({ files })
      openPopover()

      // Documents should be visible in the list
      expect(screen.getByText('Document 1')).toBeInTheDocument()
      expect(screen.getByText('Document 2')).toBeInTheDocument()
      expect(screen.getByText('Document 3')).toBeInTheDocument()
    })

    it('should call onChange when document is selected', () => {
      const onChange = vi.fn()
      const files = createMockDocumentList(3)

      renderComponent({ files, onChange })
      openPopover()

      fireEvent.click(screen.getByText('Document 2'))

      // handleChange should call onChange with the selected item
      expect(onChange).toHaveBeenCalledTimes(1)
      expect(onChange).toHaveBeenCalledWith(files[1])
    })

    it('should handle rapid toggle clicks', () => {
      renderComponent()

      const trigger = screen.getByTestId('popover-trigger')

      // Rapid clicks
      fireEvent.click(trigger)
      fireEvent.click(trigger)
      fireEvent.click(trigger)
      fireEvent.click(trigger)

      expect(trigger).toBeInTheDocument()
    })
  })

  // Tests for edge cases
  describe('Edge Cases', () => {
    it('should handle null value properties gracefully', () => {
      renderComponent({
        value: { id: 'doc-1', name: '', extension: '' },
      })

      expect(screen.getByText('--')).toBeInTheDocument()
    })

    it('should render when value prop is omitted (optional)', () => {
      const files = createMockDocumentList(2)
      const onChange = vi.fn()
      // Do not pass `value` at all to verify optional behavior
      render(<PreviewDocumentPicker files={files} onChange={onChange} />)

      // Renders placeholder for missing name
      expect(screen.getByText('--')).toBeInTheDocument()
      // Portal wrapper renders
      expect(screen.getByTestId('popover')).toBeInTheDocument()
    })

    it('should handle empty files array', () => {
      renderComponent({ files: [] })

      // Component should render without crashing
      expect(screen.getByTestId('popover')).toBeInTheDocument()
    })

    it('should handle very long document names', () => {
      const longName = 'A'.repeat(500)
      renderComponent({
        value: createMockDocumentItem({ name: longName }),
      })

      expect(screen.getByText(longName)).toBeInTheDocument()
    })

    it('should handle special characters in document name', () => {
      const specialName = '<script>alert("xss")</script>'
      renderComponent({
        value: createMockDocumentItem({ name: specialName }),
      })

      expect(screen.getByText(specialName)).toBeInTheDocument()
    })

    it('should handle undefined files prop', () => {
      // Test edge case where files might be undefined at runtime
      const props = createDefaultProps()
      // @ts-expect-error - Testing runtime edge case
      props.files = undefined

      render(<PreviewDocumentPicker {...props} />)

      // Component should render without crashing
      expect(screen.getByTestId('popover')).toBeInTheDocument()
    })

    it('should handle large number of files', () => {
      const manyFiles = createMockDocumentList(100)
      renderComponent({ files: manyFiles })

      // Component should accept large files array
      expect(screen.getByTestId('popover')).toBeInTheDocument()
    })

    it('should handle files with same name but different extensions', () => {
      const files = [
        createMockDocumentItem({ id: 'doc-1', name: 'document', extension: 'pdf' }),
        createMockDocumentItem({ id: 'doc-2', name: 'document', extension: 'txt' }),
      ]
      renderComponent({ files })

      // Component should handle duplicate names
      expect(screen.getByTestId('popover')).toBeInTheDocument()
    })
  })

  // Tests for prop variations
  describe('Prop Variations', () => {
    describe('value variations', () => {
      it('should handle value with all fields', () => {
        renderComponent({
          value: {
            id: 'full-doc',
            name: 'Full Document',
            extension: 'pdf',
          },
        })

        expect(screen.getByText('Full Document')).toBeInTheDocument()
      })

      it('should handle value with minimal fields', () => {
        renderComponent({
          value: { id: 'minimal', name: '', extension: '' },
        })

        expect(screen.getByText('--')).toBeInTheDocument()
      })
    })

    describe('files variations', () => {
      it('should handle single file', () => {
        renderComponent({
          files: [createMockDocumentItem({ name: 'Single' })],
        })

        expect(screen.getByTestId('popover')).toBeInTheDocument()
      })

      it('should handle two files', () => {
        renderComponent({
          files: createMockDocumentList(2),
        })

        expect(screen.getByTestId('popover')).toBeInTheDocument()
      })

      it('should handle many files', () => {
        renderComponent({
          files: createMockDocumentList(50),
        })

        expect(screen.getByTestId('popover')).toBeInTheDocument()
      })
    })

    describe('className variations', () => {
      it('should apply custom className', () => {
        renderComponent({ className: 'my-custom-class' })

        const trigger = screen.getByTestId('popover-trigger')
        expect(trigger).toHaveClass('my-custom-class')
      })

      it('should work without className', () => {
        renderComponent({ className: undefined })

        expect(screen.getByTestId('popover-trigger')).toBeInTheDocument()
      })

      it('should handle multiple class names', () => {
        renderComponent({ className: 'class-one class-two' })

        const trigger = screen.getByTestId('popover-trigger')
        expect(trigger).toHaveClass('class-one')
        expect(trigger).toHaveClass('class-two')
      })
    })

    describe('extension variations', () => {
      const extensions = ['txt', 'pdf', 'docx', 'xlsx', 'md']

      it.each(extensions)('should render icon for %s extension', (ext) => {
        renderComponent({
          value: createMockDocumentItem({ extension: ext }),
          files: [], // Use empty files to avoid duplicate icons
        })

        const trigger = screen.getByTestId('popover-trigger')
        expect(trigger.querySelector('svg')).toBeInTheDocument()
      })
    })
  })

  // Tests for document list rendering
  describe('Document List Rendering', () => {
    it('should render all documents in the list', () => {
      const files = createMockDocumentList(5)
      renderComponent({ files })
      openPopover()

      // All documents should be visible
      files.forEach((file) => {
        expect(screen.getByText(file.name)).toBeInTheDocument()
      })
    })

    it('should pass onChange handler to DocumentList', () => {
      const onChange = vi.fn()
      const files = createMockDocumentList(3)

      renderComponent({ files, onChange })
      openPopover()

      fireEvent.click(screen.getByText('Document 1'))

      expect(onChange).toHaveBeenCalledWith(files[0])
    })

    it('should show count header only for multiple files', () => {
      // Single file - no header
      const { rerender } = render(
        <PreviewDocumentPicker
          value={createMockDocumentItem()}
          files={[createMockDocumentItem({ name: 'Single File' })]}
          onChange={vi.fn()}
        />,
      )
      expect(screen.queryByText(/files/)).not.toBeInTheDocument()

      // Multiple files - show header
      rerender(
        <PreviewDocumentPicker
          value={createMockDocumentItem()}
          files={createMockDocumentList(3)}
          onChange={vi.fn()}
        />,
      )
      openPopover()
      expect(screen.getByText(/dataset\.preprocessDocument/)).toBeInTheDocument()
    })
  })

  // Tests for visual states
  describe('Visual States', () => {
    it('should apply hover styles on trigger', () => {
      renderComponent()

      const trigger = screen.getByTestId('popover-trigger')
      expect(trigger).toHaveClass('hover:bg-state-base-hover')
    })

    it('should have truncate class for long names', () => {
      renderComponent({
        value: createMockDocumentItem({ name: 'Very Long Document Name' }),
      })

      const nameElement = screen.getByText('Very Long Document Name')
      expect(nameElement).toHaveClass('truncate')
    })

    it('should have max-width on name element', () => {
      renderComponent({
        value: createMockDocumentItem({ name: 'Test' }),
      })

      const nameElement = screen.getByText('Test')
      expect(nameElement).toHaveClass('max-w-[200px]')
    })
  })

  // Tests for handleChange callback
  describe('handleChange Callback', () => {
    it('should call onChange with selected document item', () => {
      const onChange = vi.fn()
      const files = createMockDocumentList(3)

      renderComponent({ files, onChange })
      openPopover()

      fireEvent.click(screen.getByText('Document 1'))

      expect(onChange).toHaveBeenCalledWith(files[0])
    })

    it('should handle different document items in files', () => {
      const onChange = vi.fn()
      const customFiles = [
        { id: 'custom-1', name: 'Custom File 1', extension: 'pdf' },
        { id: 'custom-2', name: 'Custom File 2', extension: 'txt' },
      ]

      renderComponent({ files: customFiles, onChange })
      openPopover()

      fireEvent.click(screen.getByText('Custom File 1'))
      expect(onChange).toHaveBeenCalledWith(customFiles[0])

      openPopover()
      fireEvent.click(screen.getByText('Custom File 2'))
      expect(onChange).toHaveBeenCalledWith(customFiles[1])
    })

    it('should work with multiple sequential selections', () => {
      const onChange = vi.fn()
      const files = createMockDocumentList(3)

      renderComponent({ files, onChange })

      // Select multiple documents sequentially
      openPopover()
      fireEvent.click(screen.getByText('Document 1'))
      openPopover()
      fireEvent.click(screen.getByText('Document 3'))
      openPopover()
      fireEvent.click(screen.getByText('Document 2'))

      expect(onChange).toHaveBeenCalledTimes(3)
      expect(onChange).toHaveBeenNthCalledWith(1, files[0])
      expect(onChange).toHaveBeenNthCalledWith(2, files[2])
      expect(onChange).toHaveBeenNthCalledWith(3, files[1])
    })
  })
})
