import { fireEvent, render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'

import TextInput from './TextInput'

describe('TextInput Component', () => {
  const defaultProps = {
    value: '',
    callout:
      vi.fn<
        (stateName: string | number | undefined, value: string | number) => void
      >(),
    labelText: 'Test Input',
    stateName: 'test',
  }

  it('renders basic text input with label', () => {
    render(<TextInput {...defaultProps} />)

    const container = screen.getByTestId('text-input-container')
    const label = screen.getByTestId('form-label')
    const input = screen.getByTestId('text-input-field')

    expect(container).toBeInTheDocument()
    expect(label).toBeInTheDocument()
    expect(input).toBeInTheDocument()
    expect(label).toHaveTextContent('Test Input')
  })

  it('handles text input changes', () => {
    render(<TextInput {...defaultProps} />)

    const input = screen.getByTestId('text-input-field')
    fireEvent.change(input, { target: { value: 'test value' } })

    expect(input).toHaveValue('test value')
    expect(defaultProps.callout).toHaveBeenCalledWith('test', 'test value')
  })

  it('renders textarea when type is textarea', () => {
    render(<TextInput {...defaultProps} type='textarea' />)

    const textarea = screen.getByTestId('text-input-textarea')
    expect(textarea).toBeInTheDocument()
  })

  it('handles password visibility toggle', () => {
    render(<TextInput {...defaultProps} type='password' value='password123' />)

    const input = screen.getByTestId('text-input-field')
    expect(input).toHaveAttribute('type', 'password')

    const toggleButton = screen.getByRole('button')
    fireEvent.click(toggleButton)

    expect(input).toHaveAttribute('type', 'text')
  })

  it('shows error state and message', () => {
    const errorText = 'This field is required'
    render(
      <TextInput
        {...defaultProps}
        hasError={true}
        errorText={errorText}
        classType='error'
      />,
    )

    const error = screen.getByTestId('text-input-error')
    expect(error).toBeInTheDocument()
    expect(error).toHaveTextContent(errorText)
  })

  it('handles currency input format', () => {
    render(
      <TextInput
        {...defaultProps}
        type='number'
        numberFormat={{ type: 'currency', currencySymbol: '$' }}
      />,
    )

    const currencySymbol = screen.getByTestId('text-input-currency')
    expect(currencySymbol).toBeInTheDocument()
    expect(currencySymbol).toHaveTextContent('$')
  })

  it('handles disabled state', () => {
    render(<TextInput {...defaultProps} disabled={true} />)

    const input = screen.getByTestId('text-input-field')
    expect(input).toBeDisabled()
  })

  it('handles required state', () => {
    render(<TextInput {...defaultProps} required={true} />)

    const input = screen.getByTestId('text-input-field')
    expect(input).toBeRequired()
  })

  it('handles clear button functionality', () => {
    const clearCallout = vi.fn<() => void>()
    render(
      <TextInput
        {...defaultProps}
        value='test value'
        clear={true}
        clearCallout={clearCallout}
      />,
    )

    const xIcon = screen.getByTestId('icon-x')

    expect(xIcon).toBeInTheDocument()

    fireEvent.click(xIcon)

    expect(clearCallout).toHaveBeenCalled()
  })

  it('handles input with end text', () => {
    render(<TextInput {...defaultProps} endText='%' />)

    const endText = screen.getByTestId('text-input-end-text')
    expect(endText).toBeInTheDocument()
    expect(endText).toHaveValue('%')
  })

  it('handles number input with min/max constraints', () => {
    render(
      <TextInput
        {...defaultProps}
        type='number'
        min={0}
        max={100}
        value='50'
      />,
    )

    const input = screen.getByTestId('text-input-field')
    fireEvent.change(input, { target: { value: '150' } })

    expect(input).toHaveValue(100)
  })

  it('handles debounced input', async () => {
    vi.useFakeTimers()

    render(<TextInput {...defaultProps} value='initial' debounce={500} />)

    defaultProps.callout.mockClear() // this is needed to avoid the initial callout when the input is rendered

    const input = screen.getByTestId('text-input-field')

    fireEvent.change(input, { target: { value: 'test' } })

    expect(defaultProps.callout).not.toHaveBeenCalled()

    vi.advanceTimersByTime(500)

    expect(defaultProps.callout).toHaveBeenCalledTimes(1)
    expect(defaultProps.callout).toHaveBeenCalledWith('test', 'test')

    vi.useRealTimers()
  })

  it('handles auto-resize textarea', () => {
    render(<TextInput {...defaultProps} type='textarea' autoResize={true} />)

    const textarea = screen.getByTestId('text-input-textarea')
    expect(textarea).toHaveClass('textareaAutoResize')
  })

  it('handles input with suffix label', () => {
    render(<TextInput {...defaultProps} inputLabel='kg' />)

    const suffix = screen.getByTestId('text-input-suffix')
    expect(suffix).toBeInTheDocument()
    expect(suffix).toHaveTextContent('kg')
  })

  it('handles onBlur callback', () => {
    const onBlurCallout =
      vi.fn<
        (stateName: string | number | undefined, value: string | number) => void
      >()
    render(<TextInput {...defaultProps} onBlurCallout={onBlurCallout} />)

    const input = screen.getByTestId('text-input-field')
    fireEvent.blur(input)

    expect(onBlurCallout).toHaveBeenCalledWith('test', '')
  })

  it('handles onReturn callback', () => {
    const onReturnCallout = vi.fn<() => void>()
    render(<TextInput {...defaultProps} onReturnCallout={onReturnCallout} />)

    const input = screen.getByTestId('text-input-field')
    fireEvent.keyDown(input, { key: 'Enter' })

    expect(onReturnCallout).toHaveBeenCalled()
  })
})
