import '@testing-library/jest-dom'
import { fireEvent } from '@testing-library/dom'
import { axe, toHaveNoViolations } from 'jest-axe'
import { vi } from 'vitest'

import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
import { CustomElementFor } from '../../tests/component-registry'
import './searchinput'

expect.extend(toHaveNoViolations)

interface SearchInputTestConfig extends BaseTestConfig {
  id?: string
  name?: string
  appearance?: 'local' | 'local-with-button' | 'global'
  label?: string
  placeholder?: string
  disabled?: boolean
  fullwidth?: boolean
  action?: string
  method?: 'get' | 'post' | 'dialog'
  value?: string
}

const createSearchInputTest = async (config: SearchInputTestConfig = {}) => {
  const { container, element } = await createElementTest<
    CustomElementFor<'pkt-searchinput'>,
    SearchInputTestConfig
  >('pkt-searchinput', {
    id: 'test-searchinput',
    ...config,
  })

  return {
    container,
    searchinput: element,
  }
}

afterEach(() => {
  document.body.innerHTML = ''
})

describe('PktSearchInput', () => {
  describe('wrapper', () => {
    test('renders form when action exists', async () => {
      const { searchinput } = await createSearchInputTest({ action: '/search', method: 'get' })
      const wrapper = searchinput.querySelector('.pkt-searchinput')
      expect(wrapper?.tagName.toLowerCase()).toBe('form')
      expect(wrapper).toHaveAttribute('role', 'search')
      expect(wrapper).toHaveAttribute('action', '/search')
      expect(wrapper).toHaveAttribute('method', 'get')
    })

    test('renders div when action does not exist', async () => {
      const { searchinput } = await createSearchInputTest()
      const wrapper = searchinput.querySelector('.pkt-searchinput')
      expect(wrapper?.tagName.toLowerCase()).toBe('div')
      expect(wrapper).toHaveAttribute('role', 'search')
      expect(searchinput.querySelector('form')).toBeNull()
    })
  })

  describe('label', () => {
    test('renders label when label exists', async () => {
      const { searchinput } = await createSearchInputTest({ label: 'Test Label' })
      await searchinput.updateComplete
      const label = searchinput.querySelector('label.pkt-inputwrapper__label')
      expect(label).toBeInTheDocument()
      expect(label?.textContent).toContain('Test Label')
      expect(label).toHaveAttribute('for', 'test-searchinput')
    })

    test('does not render label when label is absent', async () => {
      const { searchinput } = await createSearchInputTest()
      await searchinput.updateComplete
      expect(searchinput.querySelector('label')).toBeNull()
    })
  })

  describe('input attributes', () => {
    test('input has expected type, name, id, placeholder, disabled', async () => {
      const { searchinput } = await createSearchInputTest({
        id: 'my-search',
        name: 'q',
        placeholder: 'Finn…',
        disabled: true,
      })
      await searchinput.updateComplete

      const input = searchinput.querySelector('input') as HTMLInputElement
      expect(input.type).toBe('search')
      expect(input.id).toBe('my-search')
      expect(input.name).toBe('q')
      expect(input.placeholder).toBe('Finn…')
      expect(input.disabled).toBe(true)
    })

    test('name defaults to id when name is not set', async () => {
      const { searchinput } = await createSearchInputTest({ id: 'search-field' })
      await searchinput.updateComplete
      expect((searchinput.querySelector('input') as HTMLInputElement).name).toBe('search-field')
    })

    test('placeholder defaults to Søk…', async () => {
      const { searchinput } = await createSearchInputTest()
      await searchinput.updateComplete
      expect((searchinput.querySelector('input') as HTMLInputElement).placeholder).toBe('Søk…')
    })
  })

  describe('appearance and fullwidth classes', () => {
    test('applies appearance modifier and fullwidth on wrapper', async () => {
      const { searchinput } = await createSearchInputTest({
        appearance: 'global',
        fullwidth: true,
      })
      await searchinput.updateComplete
      const wrapper = searchinput.querySelector('.pkt-searchinput')
      expect(wrapper?.classList.contains('pkt-searchinput--global')).toBe(true)
      expect(wrapper?.classList.contains('pkt-searchinput--fullwidth')).toBe(true)
    })

    test('local uses pkt-input__container; local-with-button uses pkt-searchinput__field', async () => {
      const { searchinput: localEl } = await createSearchInputTest({ appearance: 'local' })
      await localEl.updateComplete
      expect(localEl.querySelector('.pkt-input__container')).toBeTruthy()
      expect(localEl.querySelector('.pkt-searchinput__field')).toBeNull()

      const { searchinput: lwb } = await createSearchInputTest({
        appearance: 'local-with-button',
      })
      await lwb.updateComplete
      expect(lwb.querySelector('.pkt-searchinput__field')).toBeTruthy()
      expect(lwb.querySelector('.pkt-input__container')).toBeNull()
    })
  })

  describe('pkt-search', () => {
    test('fires on Enter with detail { value }', async () => {
      const { searchinput } = await createSearchInputTest()
      const searchSpy = vi.fn()
      searchinput.addEventListener('pkt-search', searchSpy)

      const input = searchinput.querySelector('input') as HTMLInputElement
      fireEvent.input(input, { target: { value: 'oslo' } })
      fireEvent.keyDown(input, { key: 'Enter' })

      expect(searchSpy).toHaveBeenCalledTimes(1)
      expect(searchSpy.mock.calls[0][0].detail).toEqual({ value: 'oslo' })
    })

    test('fires on search button click with detail { value }', async () => {
      const { searchinput } = await createSearchInputTest({ value: 'kommune' })
      await searchinput.updateComplete
      const searchSpy = vi.fn()
      searchinput.addEventListener('pkt-search', searchSpy)

      const button = searchinput.querySelector('.pkt-searchinput__button') as HTMLButtonElement
      fireEvent.click(button)

      expect(searchSpy).toHaveBeenCalledTimes(1)
      expect(searchSpy.mock.calls[0][0].detail).toEqual({ value: 'kommune' })
    })
  })

  describe('search submit button classes', () => {
    test('local: tertiary, icon-only, pkt-input-icon', async () => {
      const { searchinput } = await createSearchInputTest({ appearance: 'local' })
      await searchinput.updateComplete
      const button = searchinput.querySelector('.pkt-searchinput__button') as HTMLButtonElement
      expect(button.tagName.toLowerCase()).toBe('button')
      expect(button.classList.contains('pkt-input-icon')).toBe(true)
      expect(button.classList.contains('pkt-btn--tertiary')).toBe(true)
      expect(button.classList.contains('pkt-btn--icon-only')).toBe(true)
      expect(button.classList.contains('pkt-btn--medium')).toBe(true)
    })

    test('global: primary, yellow', async () => {
      const { searchinput } = await createSearchInputTest({ appearance: 'global' })
      await searchinput.updateComplete
      const button = searchinput.querySelector('.pkt-searchinput__button') as HTMLButtonElement
      expect(button.classList.contains('pkt-btn--primary')).toBe(true)
      expect(button.classList.contains('pkt-btn--yellow')).toBe(true)
      expect(button.classList.contains('pkt-input-icon')).toBe(false)
    })
  })

  describe('suggestions', () => {
    test('renders from suggestions property without mutating the array', async () => {
      const suggestions = [
        { title: 'A', text: 'tekst', href: 'https://oslo.kommune.no' },
        { title: 'B', text: 'tekst 2' },
      ]
      const snapshot = JSON.stringify(suggestions)

      const { searchinput } = await createSearchInputTest()
      searchinput.suggestions = suggestions
      await searchinput.updateComplete

      expect(searchinput.querySelectorAll('.pkt-searchinput__suggestion')).toHaveLength(2)
      expect(JSON.stringify(suggestions)).toBe(snapshot)
    })

    test('suggestion with href renders as link', async () => {
      const { searchinput } = await createSearchInputTest()
      searchinput.suggestions = [{ title: 'Lenke', href: 'https://oslo.kommune.no' }]
      await searchinput.updateComplete

      const link = searchinput.querySelector('a.pkt-searchinput__suggestion')
      expect(link).toBeTruthy()
      expect(link?.getAttribute('href')).toBe('https://oslo.kommune.no')
    })

    test('suggestion without href renders as button type="button"', async () => {
      const { searchinput } = await createSearchInputTest()
      searchinput.suggestions = [{ title: 'Knapp', text: 'beskrivelse' }]
      await searchinput.updateComplete

      const btn = searchinput.querySelector('button.pkt-searchinput__suggestion')
      expect(btn).toBeTruthy()
      expect(btn?.getAttribute('type')).toBe('button')
    })

    test('nonInteractive suggestion renders as div and does not emit pkt-suggestion-click', async () => {
      const suggestion = { text: 'Ingen resultater', nonInteractive: true }
      const { searchinput } = await createSearchInputTest()
      searchinput.suggestions = [suggestion]
      await searchinput.updateComplete

      expect(searchinput.querySelector('div.pkt-searchinput__suggestion')).toBeTruthy()
      expect(searchinput.querySelector('button.pkt-searchinput__suggestion')).toBeNull()

      const spy = vi.fn()
      searchinput.addEventListener('pkt-suggestion-click', spy)
      const div = searchinput.querySelector('div.pkt-searchinput__suggestion') as HTMLDivElement
      fireEvent.click(div)

      expect(spy).not.toHaveBeenCalled()
    })

    test('pkt-suggestion-click fires with detail { index, suggestion } on link click', async () => {
      const suggestion = { title: 'T', href: 'https://oslo.kommune.no' }
      const { searchinput } = await createSearchInputTest()
      searchinput.suggestions = [suggestion]
      await searchinput.updateComplete

      const spy = vi.fn()
      searchinput.addEventListener('pkt-suggestion-click', spy)
      searchinput.addEventListener('pkt-suggestion-click', (e: Event) => e.preventDefault())

      const link = searchinput.querySelector('a.pkt-searchinput__suggestion') as HTMLAnchorElement
      fireEvent.click(link)

      expect(spy).toHaveBeenCalledTimes(1)
      const evt = spy.mock.calls[0][0] as CustomEvent
      expect(evt.detail).toEqual({ index: 0, suggestion })
      expect(evt.cancelable).toBe(true)
    })

    test('pkt-suggestion-click fires with detail { index, suggestion } on button click', async () => {
      const suggestion = { title: 'X', text: 'y' }
      const { searchinput } = await createSearchInputTest()
      searchinput.suggestions = [suggestion]
      await searchinput.updateComplete

      const spy = vi.fn()
      searchinput.addEventListener('pkt-suggestion-click', spy)

      const btn = searchinput.querySelector('button.pkt-searchinput__suggestion') as HTMLButtonElement
      fireEvent.click(btn)

      expect(spy).toHaveBeenCalledTimes(1)
      expect((spy.mock.calls[0][0] as CustomEvent).detail).toEqual({
        index: 0,
        suggestion,
      })
    })

    test('canceling pkt-suggestion-click causes preventDefault on the anchor MouseEvent', async () => {
      const { searchinput } = await createSearchInputTest()
      searchinput.suggestions = [{ title: 'Resultat', href: 'https://oslo.kommune.no' }]
      await searchinput.updateComplete

      searchinput.addEventListener('pkt-suggestion-click', (e: Event) => e.preventDefault())

      const link = searchinput.querySelector('a.pkt-searchinput__suggestion') as HTMLAnchorElement
      let seen: MouseEvent | undefined
      link.addEventListener('click', (e: MouseEvent) => {
        seen = e
      })

      fireEvent.click(link)

      expect(seen).toBeDefined()
      expect(seen!.defaultPrevented).toBe(true)
    })
  })

  describe('accessibility', () => {
    test('has no axe violations with label and suggestions', async () => {
      const { searchinput } = await createSearchInputTest({
        label: 'Søk etter noe',
      })
      searchinput.suggestions = [{ title: 'Resultat', text: 'Eksempeltekst' }]
      await searchinput.updateComplete

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