import { Formik } from 'formik'
import { renderWithContext } from '../tests/with-app-context.js'
import { waitFor, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { DateSegmentInput } from './date-segment-input.js'
import { InferComponentProps } from '../types.js'

describe('Base Components: DateSegmentInput', () => {
  const defaultRequiredErrorMessage = 'Required'
  const defaultInvalidErrorMessage = 'Please enter a valid date'
  const cases: [Partial<InferComponentProps<typeof DateSegmentInput>>, string, string, string, string][] = [
    [{}, '', '', '', ''],
    [{ value: 'abc' }, '0000-00-00', '', '', ''],
    [{ value: '12' }, '0000-00-00', '', '', ''],
    [{ value: '12abc2134234234324' }, '0000-00-00', '', '', ''],
    [{ value: '10/25/2010' }, '2010-10-25', '10', '25', '2010'],
    [{ value: '10/25/201' }, '0201-10-25', '10', '25', '0201'],
    [{ value: '10/25/2010555' }, '0000-00-00', '', '', ''],
    [{ value: '2010-10-25' }, '2010-10-25', '10', '25', '2010'],
    [{ value: '2010-10-' }, '0000-00-00', '', '', ''],
    [{ value: '2010-10-25555' }, '0000-00-00', '', '', ''],
  ]

  it.each(cases)(
    "<DateSegmentInput {...%s} /> should have the value '%s' and display month '%s', day '%s', and year '%s'",
    async (props, expected, monthDisplayValue, dayDisplayValue, yearDisplayValue) => {
      const valueTestId = 'value-test-id'
      const { value, ...rest } = props

      renderWithContext(
        <Formik
          initialValues={{
            test: value,
          }}
          onSubmit={() => {}}
        >
          {({ values }) => (
            <>
              <DateSegmentInput name="test" {...rest} />
              <div data-testid={valueTestId}>{values.test}</div>
            </>
          )}
        </Formik>
      )

      const monthInput = screen.getByTestId('date-segment-component:month') as HTMLInputElement
      const dayInput = screen.getByTestId('date-segment-component:day') as HTMLInputElement
      const yearInput = screen.getByTestId('date-segment-component:year') as HTMLInputElement
      const valueEl = screen.getByTestId(valueTestId) as HTMLDivElement

      await waitFor(() => {
        expect(valueEl).toHaveTextContent(expected)
      })

      expect(monthInput.value).toBe(monthDisplayValue)
      expect(dayInput.value).toBe(dayDisplayValue)
      expect(yearInput.value).toBe(yearDisplayValue)
    }
  )

  it('renders default errors', async () => {
    const user = userEvent.setup()

    renderWithContext(
      <Formik
        initialValues={{
          test: '',
        }}
        onSubmit={() => {}}
      >
        <DateSegmentInput name="test" />
      </Formik>
    )

    const monthInput = screen.getByTestId('date-segment-component:month') as HTMLInputElement
    const dayInput = screen.getByTestId('date-segment-component:day') as HTMLInputElement
    const yearInput = screen.getByTestId('date-segment-component:year') as HTMLInputElement

    expect(screen.queryByText(defaultRequiredErrorMessage)).not.toBeInTheDocument()
    expect(screen.queryByText(defaultInvalidErrorMessage)).not.toBeInTheDocument()

    await user.type(monthInput, '10')
    await user.click(document.body)

    await screen.findByText(defaultInvalidErrorMessage)

    await user.type(monthInput, '10')
    await user.type(dayInput, '25')
    await user.type(yearInput, '2015')
    await user.click(document.body)

    await waitFor(() => {
      expect(screen.queryByText(defaultRequiredErrorMessage)).not.toBeInTheDocument()
    })
    expect(screen.queryByText(defaultInvalidErrorMessage)).not.toBeInTheDocument()

    await user.click(yearInput)
    await user.keyboard('{Backspace>10/}')
    // Feb 29th 1991 did not exist
    await user.keyboard('02291991')
    await user.click(document.body)

    await screen.findByText(defaultInvalidErrorMessage)

    await user.clear(yearInput)
    await user.clear(monthInput)
    await user.clear(dayInput)
    await user.click(document.body)

    await waitFor(() => {
      expect(screen.queryByText(defaultRequiredErrorMessage)).not.toBeInTheDocument()
    })
    expect(screen.queryByText(defaultInvalidErrorMessage)).not.toBeInTheDocument()
  })

  it('renders required errors', async () => {
    const user = userEvent.setup()

    renderWithContext(
      <Formik
        initialValues={{
          test: '',
        }}
        onSubmit={() => {}}
      >
        <DateSegmentInput name="test" isRequired />
      </Formik>
    )

    const monthInput = screen.getByTestId('date-segment-component:month') as HTMLInputElement
    const dayInput = screen.getByTestId('date-segment-component:day') as HTMLInputElement
    const yearInput = screen.getByTestId('date-segment-component:year') as HTMLInputElement

    expect(screen.queryByText(defaultRequiredErrorMessage)).not.toBeInTheDocument()
    expect(screen.queryByText(defaultInvalidErrorMessage)).not.toBeInTheDocument()

    await user.type(monthInput, '10')
    await user.click(document.body)

    await screen.findByText(defaultInvalidErrorMessage)

    await user.type(monthInput, '10')
    await user.type(dayInput, '25')
    await user.type(yearInput, '2015')
    await user.click(document.body)

    await waitFor(() => {
      expect(screen.queryByText(defaultRequiredErrorMessage)).not.toBeInTheDocument()
    })
    expect(screen.queryByText(defaultInvalidErrorMessage)).not.toBeInTheDocument()

    await user.clear(yearInput)
    await user.clear(monthInput)
    await user.clear(dayInput)
    await user.click(document.body)

    await waitFor(() => {
      expect(screen.getByText(defaultRequiredErrorMessage)).toBeInTheDocument()
    })
    expect(screen.queryByText(defaultInvalidErrorMessage)).not.toBeInTheDocument()
  })

  it('renders custom error messages', async () => {
    const user = userEvent.setup()

    const testRequiredErrorMessage = 'test-is-required'
    const testInvalidErrorMessage = 'test-is-invalid'

    renderWithContext(
      <Formik
        initialValues={{
          test: '',
        }}
        onSubmit={() => {}}
      >
        <DateSegmentInput
          name="test"
          isRequired
          requiredErrorMessage={testRequiredErrorMessage}
          invalidErrorMessage={testInvalidErrorMessage}
        />
      </Formik>
    )

    const monthInput = screen.getByTestId('date-segment-component:month') as HTMLInputElement
    const dayInput = screen.getByTestId('date-segment-component:day') as HTMLInputElement
    const yearInput = screen.getByTestId('date-segment-component:year') as HTMLInputElement

    expect(screen.queryByText(testRequiredErrorMessage)).not.toBeInTheDocument()
    expect(screen.queryByText(testInvalidErrorMessage)).not.toBeInTheDocument()

    await user.type(monthInput, '10')
    await user.click(document.body)

    await screen.findByText(testInvalidErrorMessage)

    await user.type(monthInput, '10')
    await user.type(dayInput, '25')
    await user.type(yearInput, '2015')
    await user.click(document.body)

    await waitFor(() => {
      expect(screen.queryByText(testRequiredErrorMessage)).not.toBeInTheDocument()
    })
    expect(screen.queryByText(testInvalidErrorMessage)).not.toBeInTheDocument()

    await user.clear(yearInput)
    await user.clear(monthInput)
    await user.clear(dayInput)
    await user.click(document.body)

    await waitFor(() => {
      expect(screen.getByText(testRequiredErrorMessage)).toBeInTheDocument()
    })
    expect(screen.queryByText(testInvalidErrorMessage)).not.toBeInTheDocument()
  })

  it('moves the cursor between inputs', async () => {
    const user = userEvent.setup()

    renderWithContext(
      <Formik
        initialValues={{
          test: '',
        }}
        onSubmit={() => {}}
      >
        <DateSegmentInput name="test" />
      </Formik>
    )

    const monthInput = screen.getByTestId('date-segment-component:month') as HTMLInputElement
    const dayInput = screen.getByTestId('date-segment-component:day') as HTMLInputElement
    const yearInput = screen.getByTestId('date-segment-component:year') as HTMLInputElement

    // Double digit month and day
    await user.click(monthInput)
    await user.keyboard('10')
    await waitFor(() => expect(dayInput).toHaveFocus())
    await user.keyboard('25')
    await waitFor(() => expect(yearInput).toHaveFocus())
    await user.keyboard('2015123')
    expect(yearInput).toHaveValue('2015')
    await user.keyboard('{Backspace>4/}')
    expect(yearInput).toHaveValue('')
    expect(dayInput).toHaveValue('25')
    await user.keyboard('{Backspace}')
    await waitFor(() => expect(dayInput).toHaveFocus())
    // This does not match expected output in the browser - backspace on empty year will remove the `5` from day
    // unable to get testing-library match browser
    expect(dayInput).toHaveValue('25')
    await user.keyboard('{Backspace}')
    expect(dayInput).toHaveValue('2')
    await user.keyboard('{Backspace>2/}')
    await waitFor(() => expect(monthInput).toHaveFocus())
    // This does not match expected output in the browser - backspace on empty day will remove the `0` from month
    // unable to get testing-library match browser
    expect(monthInput).toHaveValue('10')
    await user.keyboard('{Backspace>2/}')
    expect(monthInput).toHaveValue('')

    // single digit month and day
    await user.click(monthInput)
    await user.keyboard('2')
    await waitFor(() => expect(dayInput).toHaveFocus())
    await user.keyboard('4')
    await waitFor(() => expect(yearInput).toHaveFocus())
  })
})
