import '@testing-library/jest-dom'

import { fireEvent, render } from '@testing-library/react'
import { axe, toHaveNoViolations } from 'jest-axe'

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

expect.extend(toHaveNoViolations)

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

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

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

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

describe('PktCombobox', () => {
  describe('Accessibility (axe)', () => {
    test('basic combobox has no accessibility violations', async () => {
      const { container } = createComboboxTest()

      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('combobox with options has no accessibility violations', async () => {
      const { container } = createComboboxTest({
        options: getDefaultOptions(),
      })

      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('combobox with text input has no accessibility violations', async () => {
      const { container } = createComboboxTest({
        allowUserInput: true,
        options: getDefaultOptions(),
      })

      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('combobox with typeahead has no accessibility violations', async () => {
      const { container } = createComboboxTest({
        typeahead: true,
        options: getDefaultOptions(),
      })

      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('multiple combobox has no accessibility violations', async () => {
      const { container } = createComboboxTest({
        multiple: true,
        options: getDefaultOptions(),
      })

      // Exclude nested-interactive: the decorative checkbox inside li[role="option"]
      // is aria-hidden and non-focusable, but axe flags it anyway.
      // Selection state is conveyed by aria-selected on the option element.
      const results = await axe(container, {
        rules: { 'nested-interactive': { enabled: false } },
      })
      expect(results).toHaveNoViolations()
    })

    test('disabled combobox has no accessibility violations', async () => {
      const { container } = createComboboxTest({ disabled: true })

      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('combobox with error state has no accessibility violations', async () => {
      const { container } = createComboboxTest({
        hasError: true,
        errorMessage: 'Required field',
      })

      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('combobox with selected value has no accessibility violations', async () => {
      const { container } = createComboboxTest({
        defaultValue: 'apple',
        options: getDefaultOptions(),
      })

      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('combobox with multiple selected values has no accessibility violations', async () => {
      const { container } = createComboboxTest({
        multiple: true,
        defaultValue: ['apple', 'banana'],
        options: getDefaultOptions(),
      })

      // Exclude nested-interactive: decorative checkboxes inside options (see above)
      const results = await axe(container, {
        rules: { 'nested-interactive': { enabled: false } },
      })
      expect(results).toHaveNoViolations()
    })
  })

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

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

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

    test('combobox aria-expanded updates when dropdown opens', () => {
      const { container } = createComboboxTest()

      const comboboxInput = container.querySelector('.pkt-combobox__input')
      expect(comboboxInput?.getAttribute('aria-expanded')).toBe('false')

      fireEvent.click(comboboxInput!)

      expect(comboboxInput?.getAttribute('aria-expanded')).toBe('true')
    })

    test('text input has correct ARIA attributes for allowUserInput', () => {
      const { container } = createComboboxTest({ allowUserInput: true })

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

      expect(textInput?.getAttribute('role')).toBe('combobox')
      expect(textInput?.getAttribute('aria-controls')).toBe(`${comboboxId}-listbox`)
      expect(textInput?.getAttribute('aria-label')).toBe(label)
      expect(textInput?.getAttribute('aria-autocomplete')).toBe('list')
    })

    test('text input has correct ARIA attributes for typeahead', () => {
      const { container } = createComboboxTest({ typeahead: true })

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

    test('text input sets aria-activedescendant when value is selected', () => {
      const { container } = createComboboxTest({
        allowUserInput: true,
        defaultValue: 'apple',
        options: getDefaultOptions(),
      })

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

    test('text input aria-expanded reflects dropdown state', () => {
      const { container } = createComboboxTest({ allowUserInput: true })

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

      fireEvent.focus(textInput!)

      expect(textInput?.getAttribute('aria-expanded')).toBe('true')
    })

    test('listbox has correct id for aria-controls reference', () => {
      const { container } = createComboboxTest()

      const listbox = container.querySelector('.pkt-listbox')
      expect(listbox?.getAttribute('id')).toBe(`${comboboxId}-listbox`)
    })

    test('listbox has role=listbox', () => {
      const { container } = createComboboxTest({ options: getDefaultOptions() })

      const listbox = container.querySelector('.pkt-listbox')
      expect(listbox?.getAttribute('role')).toBe('listbox')
    })

    test('listbox has aria-label with component label', () => {
      const { container } = createComboboxTest({ options: getDefaultOptions() })

      const listbox = container.querySelector('.pkt-listbox')
      expect(listbox?.getAttribute('aria-label')).toBe(`Liste: ${label}`)
    })

    test('options have role=option with aria-selected', () => {
      const { container } = createComboboxTest({
        defaultValue: 'apple',
        options: getDefaultOptions(),
      })

      const options = container.querySelectorAll('.pkt-listbox__option')
      expect(options[0].getAttribute('role')).toBe('option')
      expect(options[0].getAttribute('aria-selected')).toBe('true')
      expect(options[1].getAttribute('aria-selected')).toBe('false')
    })
  })

  describe('Keyboard accessibility', () => {
    test('select-only combobox is focusable when not disabled', () => {
      const { container } = createComboboxTest()

      const comboboxInput = container.querySelector('.pkt-combobox__input') as HTMLElement
      expect(comboboxInput.getAttribute('tabindex')).toBe('0')
    })

    test('select-only combobox is not focusable when disabled', () => {
      const { container } = createComboboxTest({ disabled: true })

      const comboboxInput = container.querySelector('.pkt-combobox__input') as HTMLElement
      expect(comboboxInput.getAttribute('tabindex')).toBe('-1')
    })

    test('text input is part of tab order', () => {
      const { container } = createComboboxTest({ allowUserInput: true })

      const textInput = container.querySelector('input[type="text"]') as HTMLElement
      expect(textInput).toBeInTheDocument()
      // Text inputs are naturally tabbable (no tabindex needed)
      expect(textInput.hasAttribute('tabindex')).toBe(false)
    })

    test('text input is disabled when component is disabled', () => {
      const { container } = createComboboxTest({
        allowUserInput: true,
        disabled: true,
      })

      const textInput = container.querySelector('input[type="text"]') as HTMLInputElement
      expect(textInput).toBeDisabled()
    })
  })

  describe('Label association', () => {
    test('input wrapper label targets text input when allowUserInput', () => {
      const { container } = createComboboxTest({ allowUserInput: true })

      const labelEl = container.querySelector('label')
      expect(labelEl).toBeInTheDocument()
      expect(labelEl?.getAttribute('for')).toBe(`${comboboxId}-input`)
    })

    test('input wrapper uses fieldset/legend when no text input (select-only)', () => {
      const { container } = createComboboxTest()

      const fieldset = container.querySelector('fieldset')
      expect(fieldset).toBeInTheDocument()
      const legend = container.querySelector('legend')
      expect(legend).toBeInTheDocument()
    })
  })
})
