import '@testing-library/jest-dom'

import { fireEvent, render, screen } from '@testing-library/react'

import type { IPktComboboxOption } from 'shared-types/combobox'
import { PktCombobox } from './Combobox'
import type { IPktCombobox } from './types'

const comboboxId = 'test-combobox'
const label = 'Test Combobox'

const getDefaultOptions = (): IPktComboboxOption[] => [
  { value: 'apple', label: 'Apple' },
  { value: 'banana', label: 'Banana' },
  { value: 'cherry', label: 'Cherry', disabled: true },
  { value: 'date', label: 'Date' },
]

const createComboboxTest = (props: Partial<IPktCombobox> = {}) => {
  const defaultProps: IPktCombobox = {
    label,
    id: comboboxId,
    ...props,
  }

  return render(<PktCombobox {...defaultProps} />)
}

describe('PktCombobox', () => {
  describe('Rendering and basic functionality', () => {
    test('renders without errors', () => {
      const { container } = createComboboxTest()

      const combobox = container.querySelector('.pkt-combobox')
      expect(combobox).toBeInTheDocument()
    })

    test('renders with correct structure', () => {
      const { container } = createComboboxTest()

      const wrapper = container.querySelector('.pkt-inputwrapper')
      const inputDiv = container.querySelector('.pkt-combobox__input')
      const arrowButton = container.querySelector('.pkt-combobox__input')
      const listbox = container.querySelector('.pkt-listbox')

      expect(wrapper).toBeInTheDocument()
      expect(inputDiv).toBeInTheDocument()
      expect(arrowButton).toBeInTheDocument()
      expect(listbox).toBeInTheDocument()
    })

    test('renders select-only combobox with correct ARIA attributes', () => {
      const { container } = createComboboxTest()

      const comboboxInput = container.querySelector('.pkt-combobox__input')

      expect(comboboxInput?.getAttribute('id')).toBe(`${comboboxId}-combobox`)
      expect(comboboxInput?.getAttribute('aria-expanded')).toBe('false')
      expect(comboboxInput?.getAttribute('aria-controls')).toBe(`${comboboxId}-listbox`)
      expect(comboboxInput?.getAttribute('aria-haspopup')).toBe('listbox')
      expect(comboboxInput?.getAttribute('aria-labelledby')).toBe(`${comboboxId}-combobox-label`)
      expect(comboboxInput?.getAttribute('role')).toBe('combobox')
    })
  })

  describe('Properties and attributes', () => {
    test('handles multiple property correctly', () => {
      const { container } = createComboboxTest({
        multiple: true,
        defaultValue: ['apple'],
        options: getDefaultOptions(),
      })

      const tags = container.querySelectorAll('.pkt-tag')
      expect(tags.length).toBeGreaterThan(0)
    })

    test('handles allowUserInput property correctly', () => {
      const { container } = createComboboxTest({ allowUserInput: true })

      const textInput = container.querySelector('input[type="text"][role="combobox"]')
      expect(textInput).toBeInTheDocument()
      expect(textInput?.getAttribute('role')).toBe('combobox')
    })

    test('handles typeahead property correctly', () => {
      const { container } = createComboboxTest({ typeahead: true })

      const textInput = container.querySelector('input[type="text"]')
      expect(textInput).toBeInTheDocument()
      expect(textInput?.getAttribute('aria-autocomplete')).toBe('both')
    })

    test('handles disabled property correctly', () => {
      const { container } = createComboboxTest({ disabled: true })

      const comboboxInput = container.querySelector('.pkt-combobox__input')

      expect(comboboxInput).toHaveClass('pkt-combobox__input--disabled')
      expect(comboboxInput?.getAttribute('tabindex')).toBe('-1')
    })

    test('handles fullwidth property correctly', () => {
      const { container } = createComboboxTest({ fullwidth: true })

      const inputDiv = container.querySelector('.pkt-combobox__input')
      expect(inputDiv).toHaveClass('pkt-combobox__input--fullwidth')
    })

    test('renders with custom className', () => {
      const { container } = createComboboxTest({ className: 'my-custom-class' })

      const outerDiv = container.querySelector('.pkt-combobox-component')
      expect(outerDiv).toHaveClass('my-custom-class')
    })
  })

  describe('Options handling', () => {
    test('handles options provided via options prop', () => {
      const { container } = createComboboxTest({
        options: getDefaultOptions(),
      })

      const optionElements = container.querySelectorAll('.pkt-listbox__option')
      expect(optionElements.length).toBe(4)
      expect(optionElements[0].getAttribute('data-value')).toBe('apple')
      expect(optionElements[1].getAttribute('data-value')).toBe('banana')
    })

    test('handles options provided via children', () => {
      const { container } = render(
        <PktCombobox id="test" label="Test">
          <option value="value1">Label 1</option>
          <option value="value2">Label 2</option>
        </PktCombobox>,
      )

      const optionElements = container.querySelectorAll('.pkt-listbox__option')
      expect(optionElements.length).toBe(2)
      expect(optionElements[0].getAttribute('data-value')).toBe('value1')
      expect(optionElements[1].getAttribute('data-value')).toBe('value2')
    })

    test('handles defaultOptions property', () => {
      const defaultOptions: IPktComboboxOption[] = [
        { value: 'default1', label: 'Default 1' },
        { value: 'default2', label: 'Default 2' },
      ]

      const { container } = createComboboxTest({ defaultOptions })

      const optionElements = container.querySelectorAll('.pkt-listbox__option')
      expect(optionElements.length).toBe(2)
      expect(optionElements[0].getAttribute('data-value')).toBe('default1')
    })

    test('handles disabled options', () => {
      const { container } = createComboboxTest({
        options: getDefaultOptions(),
      })

      const disabledOption = container.querySelector('[data-value="cherry"]')
      expect(disabledOption?.getAttribute('data-disabled')).toBe('true')
    })
  })

  describe('Value handling', () => {
    test('handles single value correctly', () => {
      const { container } = createComboboxTest({
        defaultValue: 'apple',
        options: getDefaultOptions(),
      })

      const formInput = container.querySelector('input.pkt-visually-hidden') as HTMLInputElement
      expect(formInput.value).toBe('apple')
    })

    test('handles multiple values correctly', () => {
      const { container } = createComboboxTest({
        defaultValue: ['apple', 'banana'],
        multiple: true,
        options: getDefaultOptions(),
      })

      const formInput = container.querySelector('input.pkt-visually-hidden') as HTMLInputElement
      expect(formInput.value).toBe('apple,banana')
    })

    test('handles controlled value correctly', () => {
      const { container } = createComboboxTest({
        value: 'apple',
        options: getDefaultOptions(),
      })

      const formInput = container.querySelector('input.pkt-visually-hidden') as HTMLInputElement
      expect(formInput.value).toBe('apple')
    })
  })

  describe('Placeholder functionality', () => {
    test('shows placeholder when no value selected', () => {
      const { container } = createComboboxTest({ placeholder: 'Select an option' })

      const placeholder = container.querySelector('.pkt-combobox__placeholder')
      expect(placeholder).toBeInTheDocument()
      expect(placeholder?.textContent).toBe('Select an option')
    })

    test('hides placeholder when value is selected', () => {
      const { container } = createComboboxTest({
        placeholder: 'Select an option',
        defaultValue: 'apple',
        options: getDefaultOptions(),
      })

      const placeholder = container.querySelector('.pkt-combobox__placeholder')
      expect(placeholder).not.toBeInTheDocument()
    })

    test('shows placeholder in multiple mode with outside tag placement', () => {
      const { container } = createComboboxTest({
        placeholder: 'Select options',
        multiple: true,
        tagPlacement: 'outside',
      })

      const placeholder = container.querySelector('.pkt-combobox__placeholder')
      expect(placeholder).toBeInTheDocument()
      expect(placeholder?.textContent).toBe('Select options')
    })
  })

  describe('Tag placement functionality', () => {
    test('renders tags inside input by default in multiple mode', () => {
      const { container } = createComboboxTest({
        multiple: true,
        defaultValue: ['apple'],
        options: getDefaultOptions(),
      })

      const outsideTags = container.querySelector('.pkt-combobox__tags-outside')
      expect(outsideTags).not.toBeInTheDocument()

      const inlineTags = container.querySelectorAll('.pkt-combobox__input .pkt-tag')
      expect(inlineTags.length).toBeGreaterThan(0)
    })

    test('renders tags outside input when tagPlacement is outside', () => {
      const { container } = createComboboxTest({
        multiple: true,
        tagPlacement: 'outside',
        defaultValue: ['apple', 'banana'],
        options: getDefaultOptions(),
      })

      const outsideTags = container.querySelector('.pkt-combobox__tags-outside')
      expect(outsideTags).toBeInTheDocument()

      const tags = outsideTags?.querySelectorAll('.pkt-tag')
      expect(tags?.length).toBe(2)
    })
  })

  describe('Input field functionality', () => {
    test('renders hidden input when not allowUserInput or typeahead', () => {
      const { container } = createComboboxTest({ name: 'test-name' })

      const hiddenInput = container.querySelector('input[type="hidden"]')
      const textInput = container.querySelector('.pkt-combobox__input input[type="text"]')

      expect(hiddenInput).toBeInTheDocument()
      expect(textInput).not.toBeInTheDocument()
      expect(hiddenInput?.getAttribute('id')).toBe(`${comboboxId}-input`)
      expect(hiddenInput?.getAttribute('name')).toBe('test-name-input')
    })

    test('renders text input when allowUserInput is true', () => {
      const { container } = createComboboxTest({
        allowUserInput: true,
        name: 'test-name',
      })

      const textInput = container.querySelector('input[type="text"][role="combobox"]')
      const hiddenInput = container.querySelector('input[type="hidden"]')

      expect(textInput).toBeInTheDocument()
      expect(hiddenInput).not.toBeInTheDocument()
      expect(textInput?.getAttribute('id')).toBe(`${comboboxId}-input`)
      expect(textInput?.getAttribute('name')).toBe('test-name-input')
      expect(textInput?.getAttribute('aria-controls')).toBe(`${comboboxId}-listbox`)
    })

    test('renders text input when typeahead is true', () => {
      const { container } = createComboboxTest({ typeahead: true })

      const textInput = container.querySelector('input[type="text"]')
      expect(textInput).toBeInTheDocument()
      expect(textInput?.getAttribute('aria-autocomplete')).toBe('both')
    })

    test('sets correct aria-autocomplete for allowUserInput', () => {
      const { container } = createComboboxTest({ allowUserInput: true })

      const textInput = container.querySelector('input[type="text"]')
      expect(textInput?.getAttribute('aria-autocomplete')).toBe('list')
    })
  })

  describe('Dropdown functionality', () => {
    test('opens dropdown when arrow button is clicked', () => {
      const { container } = createComboboxTest()

      const arrowButton = container.querySelector('.pkt-combobox__input')
      const inputDiv = container.querySelector('.pkt-combobox__input')

      expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
      expect(inputDiv).not.toHaveClass('pkt-combobox__input--open')

      fireEvent.click(arrowButton!)

      expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')
      expect(inputDiv).toHaveClass('pkt-combobox__input--open')
    })

    test('toggles dropdown state with multiple clicks', () => {
      const { container } = createComboboxTest()

      const arrowButton = container.querySelector('.pkt-combobox__input')

      fireEvent.click(arrowButton!)
      expect(arrowButton?.getAttribute('aria-expanded')).toBe('true')

      fireEvent.click(arrowButton!)
      expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
    })

    test('does not open when disabled', () => {
      const { container } = createComboboxTest({ disabled: true })

      const arrowButton = container.querySelector('.pkt-combobox__input')

      fireEvent.click(arrowButton!)

      expect(arrowButton?.getAttribute('aria-expanded')).toBe('false')
    })
  })

  describe('Search functionality', () => {
    test('renders search input when includeSearch is true', () => {
      const { container } = createComboboxTest({ includeSearch: true })

      const searchInput = container.querySelector('.pkt-listbox__search input')
      expect(searchInput).toBeInTheDocument()
      expect(searchInput?.getAttribute('role')).toBe('searchbox')
    })

    test('sets search placeholder correctly', () => {
      const { container } = createComboboxTest({
        includeSearch: true,
        searchPlaceholder: 'Search items...',
      })

      const searchInput = container.querySelector('.pkt-listbox__search input') as HTMLInputElement
      expect(searchInput.placeholder).toBe('Search items...')
    })
  })

  describe('Max length functionality', () => {
    test('shows counter when maxlength is set in multiple mode', () => {
      const { container } = createComboboxTest({
        multiple: true,
        maxlength: 5,
        defaultValue: ['apple', 'banana'],
        options: getDefaultOptions(),
      })

      const counter = container.querySelector('.pkt-input__counter')
      expect(counter).toBeInTheDocument()
    })
  })

  describe('Error handling', () => {
    test('applies error styling when hasError is true', () => {
      const { container } = createComboboxTest({ hasError: true })

      const inputDiv = container.querySelector('.pkt-combobox__input')
      expect(inputDiv).toHaveClass('pkt-combobox__input--error')
    })

    test('renders error message text', () => {
      createComboboxTest({
        hasError: true,
        errorMessage: 'Test error',
      })

      expect(screen.getByText('Test error')).toBeInTheDocument()
    })
  })

  describe('Form integration', () => {
    test('uses id for form input name when no name specified', () => {
      const { container } = createComboboxTest()

      const formInput = container.querySelector('input.pkt-visually-hidden') as HTMLInputElement
      expect(formInput).toHaveAttribute('name', comboboxId)
    })

    test('uses custom name for form input when specified', () => {
      const { container } = createComboboxTest({
        name: 'myField',
        defaultValue: 'apple',
        options: getDefaultOptions(),
      })

      const formInput = container.querySelector('input.pkt-visually-hidden') as HTMLInputElement
      expect(formInput).toHaveAttribute('name', 'myField')
      expect(formInput.value).toBe('apple')
    })

    test('renders hidden form input', () => {
      const { container } = createComboboxTest()

      const formInput = container.querySelector('input.pkt-visually-hidden')
      expect(formInput).toBeInTheDocument()
      expect(formInput).toHaveClass('pkt-visually-hidden')
      expect(formInput?.getAttribute('tabindex')).toBe('-1')
    })
  })

  describe('InputWrapper integration', () => {
    test('renders label text', () => {
      createComboboxTest({ label: 'Custom Label' })

      expect(screen.getByText('Custom Label')).toBeInTheDocument()
    })

    test('renders helptext', () => {
      createComboboxTest({ helptext: 'Help text' })

      expect(screen.getByText('Help text')).toBeInTheDocument()
    })

    test('renders optional tag', () => {
      const { container } = createComboboxTest({
        optionalTag: true,
        optionalText: 'Valgfritt',
      })

      expect(container.textContent).toContain('Valgfritt')
    })

    test('renders required tag', () => {
      const { container } = createComboboxTest({
        requiredTag: true,
        requiredText: 'Må fylles ut',
      })

      expect(container.textContent).toContain('Må fylles ut')
    })

    test('renders label as screen-reader-only when useWrapper is false', () => {
      const { container } = createComboboxTest({ useWrapper: false })

      const labelEl = container.querySelector('label')
      expect(labelEl).toBeInTheDocument()
      expect(labelEl).toHaveClass('pkt-sr-only')
    })
  })
})
