import ExtraAttemptSettings from '..'
import { mount, expect, findWithLabel, findWithText, accessible,spy, stub }
  from '@instructure/ui-test-utils'
import NumberInputLocator from '@instructure/ui-number-input/lib/components/NumberInput/locator'
import React from 'react'

const clear = async (input) => {
  await input.input({ target: { value: '' } })
  return
}

describe('ExtraAttempts', () => {
  const labelText = 'Additional attempts'

  it('should render an input with a label', async() => {
    await mount(
      <ExtraAttemptSettings
        extraAttempts={0}
        onChange={stub()}
      />
    )
    const input = await findWithLabel(labelText)
    expect(input).to.exist()
  })

  it('should accept a valid input', async() => {
    await mount(
      <ExtraAttemptSettings
        extraAttempts={0}
        onChange={stub()}
      />
    )
    const input = await findWithLabel(labelText)
    await clear(input)
    await input.typeIn('8')
    await input.blur()

    expect(Number(input.value())).to.equal(8)
  })

  it('should accept a value above max and reset on blur with an error message', async() => {
    await mount(
      <ExtraAttemptSettings
        extraAttempts={5}
        maxExtraAttempts={20}
        onChange={stub()}
      />
    )
    const input = await findWithLabel(labelText)
    const outOfRange = '26'
    await clear(input)
    await input.typeIn(outOfRange)
    await input.blur()

    expect(Number(input.value())).to.equal(20)
    expect(await findWithText(`${outOfRange} is above the maximum of 20.`)).to.exist()
  })

  it('should accept a value below 0 and reset on blur with an error message', async() => {
    await mount(
      <ExtraAttemptSettings
        extraAttempts={5}
        maxExtraAttempts={20}
        onChange={stub()}
      />
    )
    const input = await findWithLabel(labelText)
    const outOfRange = '-5'
    await clear(input)
    await input.typeIn(outOfRange)
    await input.blur()

    expect(Number(input.value())).to.equal(0)
    expect(await findWithText(`${outOfRange} is below the minimum of 0.`)).to.exist()
  })

  it('should display a value above max when passed as a prop, and clamp on blur', async() => {
    const extraAttempts = 12
    const max = 10
    await mount(
      <ExtraAttemptSettings
        extraAttempts={extraAttempts}
        maxExtraAttempts={max}
        onChange={stub()}
      />
    )
    const input = await findWithLabel(labelText, { exact: false })

    expect(Number(input.value())).to.equal(extraAttempts)
    expect(await findWithText(`${extraAttempts} is above the maximum of ${max}.`)).to.exist()

    await input.blur()
    expect(Number(input.value())).to.equal(max)
  })

  it('should display a value below 0 when passed as a prop, and clamp on blur', async() => {
    const extraAttempts = -12
    await mount(
      <ExtraAttemptSettings
        extraAttempts={extraAttempts}
        maxExtraAttempts={10}
        onChange={stub()}
      />
    )
    const input = await findWithLabel(labelText, { exact: false })

    expect(Number(input.value())).to.equal(extraAttempts)
    expect(await findWithText(`${extraAttempts} is below the minimum of 0.`)).to.exist()

    await input.blur()
    expect(Number(input.value())).to.equal(0)
  })

  it('should accept a non-number input and reset on blur with an error message', async() => {
    await mount(
      <ExtraAttemptSettings
        extraAttempts={5}
        maxExtraAttempts={20}
        onChange={stub()}
      />
    )
    const input = await findWithLabel(labelText)
    const invalidValue = 'apple'

    await clear(input)
    await input.typeIn('6')
    await clear(input)
    await input.typeIn(invalidValue)
    await input.blur()
    const errMsg = `${invalidValue} is not a valid number.`

    expect(Number(input.value())).to.equal(6)
    expect(await findWithText(errMsg)).to.exist()
  })

  it('should not show an error message when the input is blank', async() => {
    await mount(
      <ExtraAttemptSettings
        extraAttempts={5}
        maxExtraAttempts={20}
        onChange={stub()}
      />
    )
    const input = await findWithLabel(labelText)
    await clear(input)
    const errElement = await findWithText('is not a valid number.', { expectEmpty: true })
    expect(errElement).not.to.exist()
  })

  it('should remove error message after valid input', async() => {
    await mount(
      <ExtraAttemptSettings
        extraAttempts={5}
        maxExtraAttempts={20}
        onChange={stub()}
      />
    )
    const input = await findWithLabel(labelText)
    const invalidValue = 'apple'

    await clear(input)
    await input.typeIn(invalidValue)
    await clear(input)
    await input.typeIn('6')
    await input.blur()
    const errorMsg = `${invalidValue} is not a valid number.`
    expect(await findWithText(errorMsg , { expectEmpty: true })).not.to.exist()
  })

  it('should callback a valid input', async() => {
    const onChange = spy()
    await mount(
      <ExtraAttemptSettings
        extraAttempts={0}
        onChange={onChange}
      />
    )
    const input = await findWithLabel(labelText)
    await clear(input)
    await input.typeIn('4')
    await input.blur()

    expect(onChange).to.have.been.calledWith(4)
  })

  it('should callback the most recent valid input on invalid input', async() => {
    const onChange = spy()
    await mount(
      <ExtraAttemptSettings
        extraAttempts={0}
        onChange={onChange}
      />
    )
    const input = await findWithLabel(labelText)
    const invalidValue = '!392afs09'

    await clear(input)
    await input.typeIn('7')
    await input.blur()
    expect(onChange).to.have.been.calledWith(7)

    await clear(input)
    await input.typeIn(invalidValue)
    await input.blur()
    expect(onChange).to.have.been.calledWith(7)
  })

  it('should respond correctly to up-and-down buttons', async() => {
    const onChange = spy()
    await mount(
      <ExtraAttemptSettings
        extraAttempts={9}
        onChange={onChange}
      />
    )
    const input = await findWithLabel(labelText)
    const numberInput = await NumberInputLocator.find()
    const arrows = await numberInput.findArrowButtons()
    const upButton = arrows[0]
    const downButton = arrows[1]

    await upButton.mouseDown()
    await input.blur()
    expect(Number(input.value())).to.equal(10)

    await downButton.mouseDown()
    await input.blur()
    expect(Number(input.value())).to.equal(9)
  })

  it('should respond correctly to arrow keys', async() => {
    const onChange = spy()
    await mount(
      <ExtraAttemptSettings
        extraAttempts={2}
        onChange={onChange}
      />
    )
    const input = await findWithLabel(labelText)

    await input.keyDown('up')
    expect(Number(input.value())).to.equal(3)

    await input.keyDown('down')
    expect(Number(input.value())).to.equal(2)
  })

  it('should ignore up-and-down button events when current value invalid', async() => {
    const onChange = spy()
    await mount(
      <ExtraAttemptSettings
        extraAttempts={5}
        onChange={onChange}
      />
    )
    const input = await findWithLabel(labelText)
    const numberInput = await NumberInputLocator.find()
    const arrows = await numberInput.findArrowButtons()
    const upButton = arrows[0]
    const downButton = arrows[1]
    const invalidValue = 'ksf0sf90afs'

    await clear(input)
    await input.typeIn(invalidValue)

    await upButton.click()
    await downButton.click()
    expect(input.value()).to.equal(invalidValue)
  })

  it('should ignore arrow keys when current value invalid', async() => {
    const onChange = spy()
    await mount(
      <ExtraAttemptSettings
        extraAttempts={5}
        onChange={onChange}
      />
    )
    const input = await findWithLabel(labelText)
    await clear(input)
    const invalidValue = 'klj3jlk4j3kl2j'

    await input.typeIn(invalidValue)
    await input.keyDown('up')
    await input.keyDown('down')
    expect(input.value()).to.equal(invalidValue)
  })

  it('should reset to max on up-and-down button events when input above max', async() => {
    const onChange = stub()
    await mount(
      <ExtraAttemptSettings
        extraAttempts={5}
        maxExtraAttempts={10}
        onChange={onChange}
      />
    )

    const input = await findWithLabel(labelText)
    const numberInput = await NumberInputLocator.find()
    const arrows = await numberInput.findArrowButtons()
    const upButton = arrows[0]
    const downButton = arrows[1]

    await clear(input)
    await input.typeIn('15')
    await upButton.mouseDown()
    expect(Number(input.value())).to.equal(10)

    await clear(input)
    await input.typeIn('15')
    await downButton.mouseDown()
    expect(Number(input.value())).to.equal(10)
  })

  it('should reset to min on up-and-down button events when input below 0', async() => {
    const onChange = stub()
    await mount(
      <ExtraAttemptSettings
        extraAttempts={5}
        maxExtraAttempts={10}
        onChange={onChange}
      />
    )

    const input = await findWithLabel(labelText)
    const numberInput = await NumberInputLocator.find()
    const arrows = await numberInput.findArrowButtons()
    const upButton = arrows[0]
    const downButton = arrows[1]

    await clear(input)
    await input.typeIn('-15')
    await upButton.mouseDown()
    expect(Number(input.value())).to.equal(0)

    await clear(input)
    await input.typeIn('-15')
    await downButton.mouseDown()
    expect(Number(input.value())).to.equal(0)
  })

  it('should reset to max on arrow key events when input above max', async() => {
    const onChange = stub()
    await mount(
      <ExtraAttemptSettings
        extraAttempts={5}
        maxExtraAttempts={10}
        onChange={onChange}
      />
    )
    const input = await findWithLabel(labelText)
    await clear(input)
    await input.typeIn('25')
    await input.keyDown('up')

    expect(Number(input.value())).to.equal(10)

    await clear(input)
    await input.typeIn('25')
    await input.keyDown('down')

    expect(Number(input.value())).to.equal(10)
  })

  it('should reset to min on arrow key events when input below 0', async() => {
    const onChange = stub()
    await mount(
      <ExtraAttemptSettings
        extraAttempts={5}
        maxExtraAttempts={10}
        onChange={onChange}
      />
    )
    const input = await findWithLabel(labelText)

    await clear(input)
    await input.typeIn('-15')
    await input.keyDown('up')
    expect(Number(input.value())).to.equal(0)

    await clear(input)
    await input.typeIn('-15')
    await input.keyDown('down')
    expect(Number(input.value())).to.equal(0)
  })

  it('should be accessible', async () => {
    await mount(
      <ExtraAttemptSettings
        extraAttempts={5}
        maxExtraAttempts={10}
        onChange={stub()}
      />
    )

    expect(await accessible()).to.be.true()
  })
})
