import '@testing-library/jest-dom'
import { axe, toHaveNoViolations } from 'jest-axe'
import { fireEvent } from '@testing-library/dom'
import { vi } from 'vitest'
import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
import { CustomElementFor } from '../../tests/component-registry'
import './timepicker'

export interface TimepickerTestConfig extends BaseTestConfig {
  value?: string
  min?: string
  max?: string
  step?: number
  disabled?: boolean
  required?: boolean
  label?: string
  'hide-picker'?: boolean
  'step-arrows'?: boolean
  fullwidth?: boolean
  hasError?: boolean
  errorMessage?: string
  helptext?: string
  id?: string
  name?: string
}

export const createTimepickerTest = async (config: TimepickerTestConfig = {}) => {
  const { container, element } = await createElementTest<
    CustomElementFor<'pkt-timepicker'>,
    TimepickerTestConfig
  >('pkt-timepicker', config)

  return {
    container,
    timepicker: element,
  }
}

expect.extend(toHaveNoViolations)

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

describe('PktTimepicker', () => {
  describe('Rendering', () => {
    test('renders without errors', async () => {
      const { timepicker } = await createTimepickerTest({ label: 'Tidspunkt', id: 'test' })
      expect(timepicker).toBeInTheDocument()
    })

    test('renders hours and minutes inputs', async () => {
      const { timepicker } = await createTimepickerTest({ label: 'Tidspunkt', id: 'test' })
      const hoursInput = timepicker.querySelector('#test-hours')
      const minutesInput = timepicker.querySelector('#test-minutes')
      expect(hoursInput).toBeInTheDocument()
      expect(minutesInput).toBeInTheDocument()
    })

    test('renders clock button by default', async () => {
      const { timepicker } = await createTimepickerTest({ label: 'Tidspunkt', id: 'test' })
      const button = timepicker.querySelector('.pkt-timepicker__button')
      expect(button).toBeInTheDocument()
    })

    test('does not render clock button when hide-picker', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        'hide-picker': true,
      })
      const button = timepicker.querySelector('.pkt-timepicker__button')
      expect(button).not.toBeInTheDocument()
    })

    test('renders decorative icon when hide-picker', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        'hide-picker': true,
      })
      const icon = timepicker.querySelector('.pkt-timepicker__icon')
      expect(icon).toBeInTheDocument()
    })

    test('renders prev/next buttons when step-arrows', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        'step-arrows': true,
      })
      const prev = timepicker.querySelector('.pkt-timepicker__button--prev')
      const next = timepicker.querySelector('.pkt-timepicker__button--next')
      expect(prev).toBeInTheDocument()
      expect(next).toBeInTheDocument()
    })

    test('does not render popup when hide-picker', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        'hide-picker': true,
      })
      const popup = timepicker.querySelector('.pkt-timepicker-popup')
      expect(popup).not.toBeInTheDocument()
    })

    test('does not render popup when step-arrows', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        'step-arrows': true,
      })
      const popup = timepicker.querySelector('.pkt-timepicker-popup')
      expect(popup).not.toBeInTheDocument()
    })

    test('renders hidden popup by default (not open)', async () => {
      const { timepicker } = await createTimepickerTest({ label: 'Tidspunkt', id: 'test' })
      const popup = timepicker.querySelector('.pkt-timepicker-popup')
      expect(popup).toBeInTheDocument()
      expect(popup).toHaveAttribute('hidden')
    })
  })

  describe('Properties', () => {
    test('value sets display inputs correctly', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        value: '09:30',
      })
      const hoursInput = timepicker.querySelector<HTMLInputElement>('#test-hours')
      const minutesInput = timepicker.querySelector<HTMLInputElement>('#test-minutes')
      expect(hoursInput?.value).toBe('09')
      expect(minutesInput?.value).toBe('30')
    })

    test('empty value renders empty display inputs', async () => {
      const { timepicker } = await createTimepickerTest({ label: 'Tidspunkt', id: 'test' })
      const hoursInput = timepicker.querySelector<HTMLInputElement>('#test-hours')
      const minutesInput = timepicker.querySelector<HTMLInputElement>('#test-minutes')
      expect(hoursInput?.value).toBe('')
      expect(minutesInput?.value).toBe('')
    })

    test('disabled disables all inputs', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        disabled: true,
      })
      const hoursInput = timepicker.querySelector<HTMLInputElement>('#test-hours')
      const minutesInput = timepicker.querySelector<HTMLInputElement>('#test-minutes')
      expect(hoursInput).toBeDisabled()
      expect(minutesInput).toBeDisabled()
    })

    test('disabled disables the clock button', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        disabled: true,
      })
      const button = timepicker.querySelector<HTMLButtonElement>('.pkt-timepicker__button')
      expect(button).toBeDisabled()
    })

    test('step-arrows adds pkt-timepicker--stepper class to host', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        'step-arrows': true,
      })
      expect(timepicker.classList.contains('pkt-timepicker--stepper')).toBe(true)
    })

    test('fullwidth adds pkt-timepicker--fullwidth class to host', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        fullwidth: true,
      })
      expect(timepicker.classList.contains('pkt-timepicker--fullwidth')).toBe(true)
    })

    test('min/max/step forwarded to hidden input', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        min: '08:00',
        max: '17:00',
        step: 300,
      })
      const hiddenInput = timepicker.querySelector<HTMLInputElement>('#test-input')
      expect(hiddenInput?.getAttribute('min')).toBe('08:00')
      expect(hiddenInput?.getAttribute('max')).toBe('17:00')
      expect(hiddenInput?.getAttribute('step')).toBe('300')
    })
  })

  describe('Value management', () => {
    test('external value change updates display', async () => {
      const { timepicker } = await createTimepickerTest({ label: 'Tidspunkt', id: 'test' })
      timepicker.value = '15:45'
      await timepicker.updateComplete
      const hoursInput = timepicker.querySelector<HTMLInputElement>('#test-hours')
      const minutesInput = timepicker.querySelector<HTMLInputElement>('#test-minutes')
      expect(hoursInput?.value).toBe('15')
      expect(minutesInput?.value).toBe('45')
    })

    test('invalid value format clears display', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        value: '09:30',
      })
      timepicker.value = 'invalid'
      await timepicker.updateComplete
      const hoursInput = timepicker.querySelector<HTMLInputElement>('#test-hours')
      expect(hoursInput?.value).toBe('')
    })

    test('hidden input reflects value', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        value: '09:30',
      })
      const hiddenInput = timepicker.querySelector<HTMLInputElement>('#test-input')
      expect(hiddenInput?.value).toBe('09:30')
    })
  })

  describe('Events', () => {
    test('change fires when value changes via ArrowUp', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        value: '09:30',
      })
      const changeHandler = vi.fn()
      timepicker.addEventListener('change', changeHandler)
      timepicker.touched = true

      const hoursInput = timepicker.querySelector<HTMLInputElement>('#test-hours')!
      fireEvent.keyDown(hoursInput, { key: 'ArrowUp' })
      await timepicker.updateComplete

      expect(changeHandler).toHaveBeenCalled()
    })

    test('value-change fires with HH:MM detail', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        value: '09:30',
      })
      const valueChangeHandler = vi.fn()
      timepicker.addEventListener('value-change', valueChangeHandler)
      timepicker.touched = true

      const hoursInput = timepicker.querySelector<HTMLInputElement>('#test-hours')!
      fireEvent.keyDown(hoursInput, { key: 'ArrowUp' })
      await timepicker.updateComplete

      expect(valueChangeHandler).toHaveBeenCalledWith(
        expect.objectContaining({ detail: '10:30' }),
      )
    })
  })

  describe('Popup', () => {
    test('clock button opens popup', async () => {
      const { timepicker } = await createTimepickerTest({ label: 'Tidspunkt', id: 'test' })
      const button = timepicker.querySelector<HTMLButtonElement>('.pkt-timepicker__button')!
      fireEvent.click(button)
      await timepicker.updateComplete
      const popup = timepicker.querySelector('.pkt-timepicker-popup')
      expect(popup).not.toHaveAttribute('hidden')
    })

    test('clock button toggles popup closed', async () => {
      const { timepicker } = await createTimepickerTest({ label: 'Tidspunkt', id: 'test' })
      const button = timepicker.querySelector<HTMLButtonElement>('.pkt-timepicker__button')!
      fireEvent.click(button)
      await timepicker.updateComplete
      fireEvent.click(button)
      await timepicker.updateComplete
      const popup = timepicker.querySelector('.pkt-timepicker-popup')
      expect(popup).toHaveAttribute('hidden')
    })

    test('popup renders hour and minute columns', async () => {
      const { timepicker } = await createTimepickerTest({ label: 'Tidspunkt', id: 'test' })
      const button = timepicker.querySelector<HTMLButtonElement>('.pkt-timepicker__button')!
      fireEvent.click(button)
      await timepicker.updateComplete
      const cols = timepicker.querySelectorAll('.pkt-timepicker-popup__col')
      expect(cols).toHaveLength(2)
    })

    test('selecting hour option updates hours display', async () => {
      const { timepicker } = await createTimepickerTest({ label: 'Tidspunkt', id: 'test' })
      timepicker.touched = true
      const button = timepicker.querySelector<HTMLButtonElement>('.pkt-timepicker__button')!
      fireEvent.click(button)
      await timepicker.updateComplete

      const option = timepicker.querySelector<HTMLButtonElement>(
        '[data-type="hour"][data-value="9"]',
      )!
      fireEvent.click(option)
      await timepicker.updateComplete

      const hoursInput = timepicker.querySelector<HTMLInputElement>('#test-hours')
      expect(hoursInput?.value).toBe('09')
    })
  })

  describe('Stepper', () => {
    test('next button increments by step', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        'step-arrows': true,
        value: '09:00',
      })
      timepicker.touched = true
      const next = timepicker.querySelector<HTMLButtonElement>('.pkt-timepicker__button--next')!
      fireEvent.click(next)
      await timepicker.updateComplete
      expect(timepicker.value).toBe('09:01')
    })

    test('prev button decrements by step', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        'step-arrows': true,
        value: '09:05',
        step: 300,
      })
      timepicker.touched = true
      const prev = timepicker.querySelector<HTMLButtonElement>('.pkt-timepicker__button--prev')!
      fireEvent.click(prev)
      await timepicker.updateComplete
      expect(timepicker.value).toBe('09:00')
    })

    test('next button at 59 minutes rolls over to next hour', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        'step-arrows': true,
        value: '09:59',
      })
      timepicker.touched = true
      const next = timepicker.querySelector<HTMLButtonElement>('.pkt-timepicker__button--next')!
      fireEvent.click(next)
      await timepicker.updateComplete
      expect(timepicker.value).toBe('10:00')
    })
  })

  describe('Form integration', () => {
    test('required with empty value fails validation', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        name: 'tidspunkt',
        required: true,
      })
      expect(timepicker.internals.validity.valid).toBe(false)
    })

    test('form reset clears value', async () => {
      const container = document.createElement('div')
      container.innerHTML = `
        <form>
          <pkt-timepicker id="test" label="Tidspunkt" name="tidspunkt" value="09:30"></pkt-timepicker>
        </form>
      `
      document.body.appendChild(container)
      await customElements.whenDefined('pkt-timepicker')
      const timepicker = container.querySelector('pkt-timepicker') as CustomElementFor<'pkt-timepicker'>
      await timepicker.updateComplete

      const form = container.querySelector('form')!
      form.reset()
      await timepicker.updateComplete

      const hoursInput = timepicker.querySelector<HTMLInputElement>('#test-hours')
      expect(hoursInput?.value).toBe('')
    })
  })

  describe('Accessibility', () => {
    test('passes axe in default state', async () => {
      const { container } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test-axe',
      })
      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('passes axe with step-arrows', async () => {
      const { container } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test-axe-stepper',
        'step-arrows': true,
      })
      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('passes axe when disabled', async () => {
      const { container } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test-axe-disabled',
        disabled: true,
      })
      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('passes axe with hasError and errorMessage', async () => {
      const { container } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test-axe-error',
        hasError: true,
        errorMessage: 'Ugyldig tidspunkt',
      })
      const results = await axe(container)
      expect(results).toHaveNoViolations()
    })

    test('hours input has correct spinbutton ARIA', async () => {
      const { timepicker } = await createTimepickerTest({
        label: 'Tidspunkt',
        id: 'test',
        value: '09:30',
      })
      const hoursInput = timepicker.querySelector<HTMLInputElement>('#test-hours')!
      expect(hoursInput.getAttribute('role')).toBe('spinbutton')
      expect(hoursInput.getAttribute('aria-valuemin')).toBe('0')
      expect(hoursInput.getAttribute('aria-valuemax')).toBe('23')
      expect(hoursInput.getAttribute('aria-valuenow')).toBe('09')
    })
  })
})
