import { Formik } from 'formik'
import React from 'react'
import { render, fireEvent, waitFor, screen } from '../../tests/with-app-context.js'
import { InferComponentProps } from '../types.js'
import { ZipCodeInput } from './zip-code-input.js'

describe('Base Components: ZipCodeInput', () => {
  const defaultRequiredErrorMessage = 'Required'
  const defaultMinLengthErrorMessage = 'We need your 5-digit zip code'
  const defaultNineDigitLengthErrorMessage = 'We need your 5-digit or 9-digit zip code'

  const cases: [Partial<InferComponentProps<typeof ZipCodeInput>>, string][] = [
    [{}, ''],
    [{ value: 'abc' }, ''],
    [{ value: '12' }, '12'],
    [{ value: '12abc2134234234324' }, '12'],
    [{ value: '12345' }, '12345'],
    [{ value: '1234567' }, '12345-67'],
    [{ value: '123456789' }, '12345-6789'],
    [{ value: '12345-6789' }, '12345-6789'],
    [{ value: '12345 6789' }, '12345-6789'],
    [{ value: '123456789888' }, '12345-6789'],
    [{ value: '123456789888', restrictToFiveDigits: true }, '12345'],
  ]

  it.each(cases)("<ZipCodeInput {...%s} /> should have the value '%s'", async (props, expected) => {
    const testId = 'zip-code-input-test-id'
    const { value, ...rest } = props

    render(
      <Formik
        initialValues={{
          test: value,
        }}
        onSubmit={() => {}}
      >
        <ZipCodeInput name="test" data-testid={testId} {...rest} />
      </Formik>
    )

    const input = screen.getByTestId(testId) as HTMLInputElement

    await waitFor(() => expect(input.value).toBe(expected))
  })

  it('renders default errors', async () => {
    const testId = 'zip-code-input-test-id'

    render(
      <Formik
        initialValues={{
          test: '',
        }}
        onSubmit={() => {}}
      >
        <ZipCodeInput name="test" data-testid={testId} />
      </Formik>
    )

    const input = screen.getByTestId(testId) as HTMLInputElement

    expect(screen.queryByText(defaultRequiredErrorMessage)).toBeNull()
    expect(screen.queryByText(defaultMinLengthErrorMessage)).toBeNull()
    expect(screen.queryByText(defaultNineDigitLengthErrorMessage)).toBeNull()

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

    await waitFor(() => expect(screen.getByText(defaultMinLengthErrorMessage)).toBeTruthy())

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

    await waitFor(() => {
      expect(screen.queryByText(defaultRequiredErrorMessage)).toBeNull()
      expect(screen.queryByText(defaultMinLengthErrorMessage)).toBeNull()
      expect(screen.queryByText(defaultNineDigitLengthErrorMessage)).toBeNull()
    })

    fireEvent.change(input, { target: { value: '12345-67' } })
    fireEvent.blur(input)

    await waitFor(() => {
      expect(screen.queryByText(defaultRequiredErrorMessage)).toBeNull()
      expect(screen.queryByText(defaultMinLengthErrorMessage)).toBeNull()
      expect(screen.queryByText(defaultNineDigitLengthErrorMessage)).toBeTruthy()
    })

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

    await waitFor(() => {
      expect(screen.queryByText(defaultRequiredErrorMessage)).toBeNull()
      expect(screen.queryByText(defaultMinLengthErrorMessage)).toBeNull()
      expect(screen.queryByText(defaultNineDigitLengthErrorMessage)).toBeNull()
    })
  })

  it('renders required errors', async () => {
    const testId = 'zip-code-input-test-id'

    render(
      <Formik
        initialValues={{
          test: '',
        }}
        onSubmit={() => {}}
      >
        <ZipCodeInput name="test" data-testid={testId} isRequired />
      </Formik>
    )

    const input = screen.getByTestId(testId) as HTMLInputElement

    expect(screen.queryByText(defaultRequiredErrorMessage)).toBeNull()
    expect(screen.queryByText(defaultMinLengthErrorMessage)).toBeNull()
    expect(screen.queryByText(defaultNineDigitLengthErrorMessage)).toBeNull()

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

    await waitFor(() => expect(screen.getByText(defaultMinLengthErrorMessage)).toBeTruthy())

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

    await waitFor(() => {
      expect(screen.queryByText(defaultRequiredErrorMessage)).toBeNull()
      expect(screen.queryByText(defaultMinLengthErrorMessage)).toBeNull()
      expect(screen.queryByText(defaultNineDigitLengthErrorMessage)).toBeNull()
    })

    fireEvent.change(input, { target: { value: '12345-67' } })
    fireEvent.blur(input)

    await waitFor(() => {
      expect(screen.queryByText(defaultRequiredErrorMessage)).toBeNull()
      expect(screen.queryByText(defaultMinLengthErrorMessage)).toBeNull()
      expect(screen.queryByText(defaultNineDigitLengthErrorMessage)).toBeTruthy()
    })

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

    await waitFor(() => {
      expect(screen.queryByText(defaultRequiredErrorMessage)).toBeTruthy()
      expect(screen.queryByText(defaultMinLengthErrorMessage)).toBeNull()
      expect(screen.queryByText(defaultNineDigitLengthErrorMessage)).toBeNull()
    })
  })

  it('renders custom error messages', async () => {
    const testId = 'zip-code-input-test-id'
    const testRequiredErrorMessage = 'test-is-required'
    const testMinLengthErrorMessage = 'test-is-invalid-min-length'
    const testNineDigitLengthErrorMessage = 'test-invalid-nine-digit-length'

    render(
      <Formik
        initialValues={{
          test: '',
        }}
        onSubmit={() => {}}
      >
        <ZipCodeInput
          name="test"
          data-testid={testId}
          isRequired
          requiredErrorMessage={testRequiredErrorMessage}
          minLengthErrorMessage={testMinLengthErrorMessage}
          nineDigitLengthErrorMessage={testNineDigitLengthErrorMessage}
        />
      </Formik>
    )

    const input = screen.getByTestId(testId) as HTMLInputElement

    expect(screen.queryByText(testRequiredErrorMessage)).toBeNull()
    expect(screen.queryByText(testMinLengthErrorMessage)).toBeNull()
    expect(screen.queryByText(testNineDigitLengthErrorMessage)).toBeNull()

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

    await waitFor(() => expect(screen.getByText(testMinLengthErrorMessage)).toBeTruthy())

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

    await waitFor(() => {
      expect(screen.queryByText(testRequiredErrorMessage)).toBeNull()
      expect(screen.queryByText(testMinLengthErrorMessage)).toBeNull()
      expect(screen.queryByText(testNineDigitLengthErrorMessage)).toBeNull()
    })

    fireEvent.change(input, { target: { value: '12345-67' } })
    fireEvent.blur(input)

    await waitFor(() => {
      expect(screen.queryByText(testRequiredErrorMessage)).toBeNull()
      expect(screen.queryByText(testMinLengthErrorMessage)).toBeNull()
      expect(screen.queryByText(testNineDigitLengthErrorMessage)).toBeTruthy()
    })

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

    await waitFor(() => {
      expect(screen.queryByText(testRequiredErrorMessage)).toBeTruthy()
      expect(screen.queryByText(testMinLengthErrorMessage)).toBeNull()
      expect(screen.queryByText(testNineDigitLengthErrorMessage)).toBeNull()
    })
  })
})
