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

export interface TextinputTestConfig extends BaseTestConfig {
  // From PktTextinput specific properties
  type?: string
  value?: string
  autocomplete?: string | null
  iconNameRight?: string | null
  prefix?: string | null
  suffix?: string | null
  size?: number | null
  omitSearchIcon?: boolean

  // From PktInputElement base class (commonly used ones)
  id?: string
  label?: string
  name?: string
  disabled?: boolean
  readonly?: boolean
  required?: boolean
  placeholder?: string | null
  maxlength?: number | null
  minlength?: number | null
  hasError?: boolean
  errorMessage?: string
  helptext?: string
  fullwidth?: boolean
  counter?: boolean
  inline?: boolean
  ariaLabelledby?: string | null
  ariaDescribedBy?: string | null
}

// Use shared framework
export const createTextinputTest = async (config: TextinputTestConfig = {}) => {
  const { container, element } = await createElementTest<
    CustomElementFor<'pkt-textinput'>,
    TextinputTestConfig
  >('pkt-textinput', config)

  return {
    container,
    textinput: element,
  }
}

expect.extend(toHaveNoViolations)

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

describe('PktTextinput', () => {
  describe('Basic Rendering', () => {
    test('renders without errors', async () => {
      const { textinput } = await createTextinputTest()
      expect(textinput).toBeInTheDocument()
    })

    test('renders with default properties', async () => {
      const { textinput } = await createTextinputTest()
      expect(textinput.type).toBe('text')
      expect(textinput.value).toBe('')
      expect(textinput.autocomplete).toBe(null) // Property defaults to null, template sets 'off'
    })

    test('renders input element', async () => {
      const { textinput } = await createTextinputTest()
      const inputElement = textinput.querySelector('input')
      expect(inputElement).toBeInTheDocument()
    })
  })

  describe('Input Types', () => {
    test('renders text input by default', async () => {
      const { textinput } = await createTextinputTest()
      const inputElement = textinput.querySelector('input')
      expect(inputElement?.getAttribute('type')).toBe('text')
    })

    test('renders email input type', async () => {
      const { textinput } = await createTextinputTest({ type: 'email' })
      const inputElement = textinput.querySelector('input')
      expect(inputElement?.getAttribute('type')).toBe('email')
    })

    test('renders password input type', async () => {
      const { textinput } = await createTextinputTest({ type: 'password' })
      const inputElement = textinput.querySelector('input')
      expect(inputElement?.getAttribute('type')).toBe('password')
    })

    test('renders tel input type', async () => {
      const { textinput } = await createTextinputTest({ type: 'tel' })
      const inputElement = textinput.querySelector('input')
      expect(inputElement?.getAttribute('type')).toBe('tel')
    })

    test('renders url input type', async () => {
      const { textinput } = await createTextinputTest({ type: 'url' })
      const inputElement = textinput.querySelector('input')
      expect(inputElement?.getAttribute('type')).toBe('url')
    })

    test('renders search input type', async () => {
      const { textinput } = await createTextinputTest({ type: 'search' })
      const inputElement = textinput.querySelector('input')
      expect(inputElement?.getAttribute('type')).toBe('search')
    })
  })

  describe('Properties and Attributes', () => {
    test('sets value correctly', async () => {
      const value = 'Test input value'
      const { textinput } = await createTextinputTest({ value })

      expect(textinput.value).toBe(value)
      const inputElement = textinput.querySelector('input') as HTMLInputElement
      expect(inputElement.value).toBe(value)
    })

    test('sets autocomplete correctly', async () => {
      const { textinput } = await createTextinputTest({ autocomplete: 'email' })

      expect(textinput.autocomplete).toBe('email')
      const inputElement = textinput.querySelector('input')
      expect(inputElement?.getAttribute('autocomplete')).toBe('email')
    })

    test('handles disabled state', async () => {
      const { textinput } = await createTextinputTest({ disabled: true })

      const inputElement = textinput.querySelector('input')
      expect(inputElement?.hasAttribute('disabled')).toBe(true)
    })

    test('handles readonly state', async () => {
      const { textinput } = await createTextinputTest({ readonly: true })

      const inputElement = textinput.querySelector('input')
      expect(inputElement?.hasAttribute('readonly')).toBe(true)
    })

    test('handles required state', async () => {
      const { textinput } = await createTextinputTest({ required: true })

      const inputElement = textinput.querySelector('input')
      expect(inputElement?.hasAttribute('required')).toBe(false) // Not set as attribute on input

      const inputWrapper = textinput.querySelector('pkt-input-wrapper')
      expect(inputWrapper?.hasAttribute('required')).toBe(true) // But passed to wrapper
    })
  })

  describe('Icons', () => {
    test('renders right icon', async () => {
      const { textinput } = await createTextinputTest({
        iconNameRight: 'search',
      })

      expect(textinput.iconNameRight).toBe('search')

      const icon = textinput.querySelector('pkt-icon')
      expect(icon).toBeInTheDocument()
      expect(icon?.getAttribute('name')).toBe('search')
    })

    test('renders search icon for search type by default', async () => {
      const { textinput } = await createTextinputTest({ type: 'search' })

      const icon = textinput.querySelector('pkt-icon')
      expect(icon).toBeInTheDocument()
      expect(icon?.getAttribute('name')).toBe('magnifying-glass-big')
    })

    test('can omit search icon for search type', async () => {
      const { textinput } = await createTextinputTest({
        type: 'search',
        omitSearchIcon: true,
      })

      const icon = textinput.querySelector('pkt-icon')
      expect(icon).not.toBeInTheDocument()
    })
  })

  describe('Prefix and Suffix', () => {
    test('renders prefix text', async () => {
      const { textinput } = await createTextinputTest({ prefix: 'https://' })

      expect(textinput.prefix).toBe('https://')
      const prefixElement = textinput.querySelector('.pkt-input-prefix')
      expect(prefixElement).toBeInTheDocument()
      expect(prefixElement?.textContent).toBe('https://')
    })

    test('renders suffix text', async () => {
      const { textinput } = await createTextinputTest({ suffix: '.com' })

      expect(textinput.suffix).toBe('.com')
      const suffixElement = textinput.querySelector('.pkt-input-suffix')
      expect(suffixElement).toBeInTheDocument()
      expect(suffixElement?.textContent?.trim()).toBe('.com')
    })

    test('renders both prefix and suffix', async () => {
      const { textinput } = await createTextinputTest({
        prefix: '$',
        suffix: 'USD',
      })

      const prefixElement = textinput.querySelector('.pkt-input-prefix')
      const suffixElement = textinput.querySelector('.pkt-input-suffix')

      expect(prefixElement?.textContent).toBe('$')
      expect(suffixElement?.textContent?.trim()).toBe('USD')
    })
  })

  describe('Input Wrapper Integration', () => {
    test('displays label correctly', async () => {
      const { textinput } = await createTextinputTest({ label: 'Email Address' })

      const inputWrapper = textinput.querySelector('pkt-input-wrapper')
      expect(inputWrapper?.getAttribute('label')).toBe('Email Address')
    })

    test('displays helptext correctly', async () => {
      const { textinput } = await createTextinputTest({ helptext: 'Enter a valid email' })

      // helptext is passed as a property, not attribute to input-wrapper
      expect(textinput.helptext).toBe('Enter a valid email')
    })

    test('handles error state', async () => {
      const { textinput } = await createTextinputTest({
        hasError: true,
        errorMessage: 'Email is required',
      })

      expect(textinput.hasError).toBe(true)
      expect(textinput.errorMessage).toBe('Email is required')

      const inputWrapper = textinput.querySelector('pkt-input-wrapper')
      expect(inputWrapper?.hasAttribute('hasError')).toBe(true)

      const inputElement = textinput.querySelector('input')
      expect(inputElement?.getAttribute('aria-invalid')).toBe('true')
    })

    test('handles fullwidth styling', async () => {
      const { textinput } = await createTextinputTest({ fullwidth: true })

      const inputElement = textinput.querySelector('input')
      expect(inputElement?.className).toContain('pkt-input--fullwidth')
    })
  })

  describe('Character Counter', () => {
    test('shows counter when enabled', async () => {
      const { textinput } = await createTextinputTest({
        counter: true,
        maxlength: 50,
      })

      const inputWrapper = textinput.querySelector('pkt-input-wrapper')
      expect(inputWrapper?.hasAttribute('counter')).toBe(true)
    })

    test('updates counter on value change', async () => {
      const { textinput } = await createTextinputTest({
        counter: true,
        maxlength: 50,
        value: 'Hello',
      })

      expect(textinput.counterCurrent).toBe(5)
    })
  })

  describe('User Interaction', () => {
    test('updates value on user input', async () => {
      const { textinput } = await createTextinputTest()
      const inputElement = textinput.querySelector('input') as HTMLInputElement

      fireEvent.input(inputElement, { target: { value: 'new value' } })
      await textinput.updateComplete

      expect(textinput.value).toBe('new value')
      expect(textinput.touched).toBe(true)
    })

    test('handles focus and blur events', async () => {
      const { textinput } = await createTextinputTest()
      const inputElement = textinput.querySelector('input') as HTMLInputElement

      // Focus and input to trigger touched state
      fireEvent.focus(inputElement)
      fireEvent.input(inputElement, { target: { value: 'test input' } })
      await textinput.updateComplete
      fireEvent.blur(inputElement)
      await textinput.updateComplete

      // Test that input with value change sets touched state
      expect(textinput.touched).toBe(true)
    })
  })

  describe('Validation', () => {
    test('respects maxlength constraint', async () => {
      const { textinput } = await createTextinputTest({ maxlength: 20 })

      const inputElement = textinput.querySelector('input')
      expect(inputElement?.getAttribute('maxlength')).toBe('20')
    })

    test('respects minlength constraint', async () => {
      const { textinput } = await createTextinputTest({ minlength: 3 })

      const inputElement = textinput.querySelector('input')
      expect(inputElement?.getAttribute('minlength')).toBe('3')
    })
  })

  describe('Accessibility', () => {
    test('passes through accessibility attributes', async () => {
      const { textinput } = await createTextinputTest({
        ariaLabelledby: 'external-label',
        ariaDescribedBy: 'external-description',
      })

      const inputElement = textinput.querySelector('input')
      expect(inputElement?.getAttribute('aria-labelledby')).toBe('external-label')

      // ariaDescribedBy is passed as property to input-wrapper
      expect(textinput.ariaDescribedBy).toBe('external-description')
    })

    test('textinput is accessible', async () => {
      const { textinput } = await createTextinputTest({
        label: 'Email',
        type: 'email',
        helptext: 'Enter your email address',
        required: true,
      })

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

  describe('Complex Configuration', () => {
    test('renders email input with all features', async () => {
      const config: TextinputTestConfig = {
        type: 'email',
        label: 'Email Address',
        value: 'user@example.com',
        placeholder: 'Enter your email...',
        iconNameRight: 'mail',
        maxlength: 100,
        counter: true,
        required: true,
        autocomplete: 'email',
        helptext: 'We will never share your email',
      }

      const { textinput } = await createTextinputTest(config)

      expect(textinput.type).toBe(config.type)
      expect(textinput.value).toBe(config.value)
      expect(textinput.iconNameRight).toBe(config.iconNameRight)
      expect(textinput.maxlength).toBe(config.maxlength)
      expect(textinput.required).toBe(config.required)
      expect(textinput.autocomplete).toBe(config.autocomplete)

      const inputWrapper = textinput.querySelector('pkt-input-wrapper')
      expect(inputWrapper?.getAttribute('label')).toBe(config.label)
      expect(textinput.helptext).toBe(config.helptext) // Property, not attribute
      expect(inputWrapper?.hasAttribute('counter')).toBe(true)

      const inputElement = textinput.querySelector('input')
      expect(inputElement?.getAttribute('type')).toBe(config.type)
      expect(inputElement?.getAttribute('placeholder')).toBe(config.placeholder)
      expect(inputElement?.getAttribute('maxlength')).toBe(String(config.maxlength))
      expect(inputElement?.getAttribute('autocomplete')).toBe(config.autocomplete)

      const icon = textinput.querySelector('pkt-icon')
      expect(icon?.getAttribute('name')).toBe(config.iconNameRight)
    })

    test('renders URL input with prefix and suffix', async () => {
      const config: TextinputTestConfig = {
        type: 'url',
        label: 'Website URL',
        prefix: 'https://',
        suffix: '.com',
        placeholder: 'example',
        fullwidth: true,
      }

      const { textinput } = await createTextinputTest(config)

      expect(textinput.prefix).toBe(config.prefix)
      expect(textinput.suffix).toBe(config.suffix)

      const prefixElement = textinput.querySelector('.pkt-input-prefix')
      const suffixElement = textinput.querySelector('.pkt-input-suffix')

      expect(prefixElement?.textContent).toBe(config.prefix)
      expect(suffixElement?.textContent?.trim()).toBe(config.suffix)

      const inputElement = textinput.querySelector('input')
      expect(inputElement?.className).toContain('pkt-input--fullwidth')
    })
  })
})
