import ReactTestUtils from 'react-dom/test-utils'
import { Map, fromJS } from 'immutable'
import ToggleDetails from '@instructure/ui-toggle-details/lib/components/ToggleDetails'

import { TEMPORARY_STIMULUS_ID, SHOULD_CLONE_ITEM } from '@instructure/quiz-common/lib/constants'

import mouseEventOn from '../../../../../../../tests/util/mouseEventOn'
import { withTestFeatures } from '../../../../../../common/util/featureCheck'
import { unsetFakeStore } from '../../../../../../reduxStore'

import QuizEntry from '../../../../../records/QuizEntry'
import Stimulus from '../../../../../records/Stimulus'

import WarningWrapper from '../../../WarningWrapper'
import AddToBankModal from '../../../../../../banks/components/AddToBankModal'
import AddToBankOptions from '../../../../../../building/components/resources/AddToBankOptions'
import ConnectedStimulusEdit from '../index'
import WrappedStimulusEdit from '../presenter'
import StimulusShowInfo from '../../StimulusShowInfo'

import styles from '../styles.css'

describe('StimulusEdit', () => {
  const sandbox = sinon.createSandbox()

  const clearStub = sandbox.stub()
  const clearNextQuizEntryStub = sandbox.stub()
  const createStimulusStub = sandbox.stub().resolves({ id: 55 })
  const createStimulusAndQuizEntryStub = sandbox.stub().resolves({ id: '3' })
  const createTempStimulusStub = sandbox.stub()
  const modifyStub = sandbox.stub()
  const clearModificationsStub = sandbox.stub()
  const switchOnEditingStub = sandbox.stub()
  const switchOffEditingStub = sandbox.stub()

  const showErrorStub = sandbox.stub()
  const hideErrorStub = sandbox.stub()
  const getQuizEntriesStub = sandbox.stub()
  const updateQuizEntryStub = sandbox.stub()
  const updateStimulusStub = sandbox.stub().resolves()
  const setUiStub = sandbox.stub()
  const screenreaderNotificationStub = sandbox.stub()
  const openDeleteStimulusModalStub = sandbox.stub()

  const stimulus = new Stimulus({
    id: 55,
    title: 'Title',
    instructions: 'Instructions',
    body: 'Body',
    sourceUrl: 'Source URL'
  })
  sandbox.stub(stimulus, 'hasModifications').returns(true)

  const tempStimulus = new Stimulus({
    id: TEMPORARY_STIMULUS_ID,
    title: 'Title',
    instructions: 'Instructions',
    body: 'Body',
    sourceUrl: 'Source URL'
  })

  const quizEntry = new QuizEntry({
    id: 3
  })

  const defaultProps = {
    stimulus,
    workingStimulus: stimulus,
    quizEntry,
    workingQuizEntry: quizEntry,
    quizEntries: fromJS([{}, {}]),
    quizId: '100',
    clearModifications: clearModificationsStub,
    clearNextQuizEntry: clearNextQuizEntryStub,
    displayPosition: 1,
    guid: 'g1234',
    fileUpload: sandbox.stub(),
    clearTemporaryStimulus: clearStub,
    createStimulus: createStimulusStub,
    createTempStimulus: createTempStimulusStub,
    getQuizEntries: getQuizEntriesStub,
    modifyStimulus: modifyStub,
    switchOnEditing: switchOnEditingStub,
    switchOffEditing: switchOffEditingStub,
    updateQuizEntry: (...args) => {
      updateQuizEntryStub(...args)
      return {then: (cb) => cb()}
    },
    showError: showErrorStub,
    hideError: hideErrorStub,
    updateStimulus: (...args) => {
      updateStimulusStub(...args)
      return {then: (cb) => cb()}
    },
    createStimulusAndQuizEntry: createStimulusAndQuizEntryStub,
    setUi: setUiStub,
    screenreaderNotification: screenreaderNotificationStub,
    openDeleteStimulusModal: openDeleteStimulusModalStub,
    progressValue: 0,
    setUploadStatus: sandbox.stub(),
    connectDragSource: (C) => C,
    appContainer: '.root',
    onEditToggle: () => { }
  }

  const testbed = createTestbed(withContext(WrappedStimulusEdit), defaultProps)

  const DELETE_STIMULUS_MODAL_BUTTON = 1

  afterEach(() => {
    sandbox.resetHistory()
  })

  const findTestbedCancelButton = (testbed) => {
    const selector = `.${styles.footer} button`
    return testbed.findChildrenBySelector(selector)[0]
  }

  const findTestbedSaveButton = (testbed) => {
    const selector = `.${styles.footer} button`
    return testbed.findChildrenBySelector(selector)[1]
  }

  context('errors', () => {
    const renderWithAttrs = (attrs, errorsShowing = true) => {
      const stim = stimulus.merge(attrs)
      testbed.render({stimulus: stim, errorsShowing})
    }

    it('displays no errors when stimulus is valid', () => {
      renderWithAttrs({})
      expect(testbed.rootNode.innerText.match('Title cannot be blank')).to.not.exist()
    })

    it('focuses on the title on mount', () => {
      renderWithAttrs({})
      expect(document.activeElement).to.not.be.false()
    })

    it('displays an error when title is blank', () => {
      renderWithAttrs({title: ''})
      expect(testbed.rootNode.innerText.match('Title cannot be blank')).to.exist()
    })

    it('displays an error when body is blank', () => {
      renderWithAttrs({body: ''})
      expect(testbed.rootNode.innerText.match('Content cannot be blank')).to.exist()
    })

    it('displays no errors when errorsShowing is falsey', () => {
      renderWithAttrs({body: '', title: ''}, false)
      expect(testbed.rootNode.innerText.match('Content cannot be blank')).to.not.exist()
    })
  })

  it('tries to open the delete stimulus modal on click', () => {
    testbed.render()
    const button = testbed.findChildrenByTagNameAndAttribute('div', 'role', 'button')[DELETE_STIMULUS_MODAL_BUTTON]
    ReactTestUtils.Simulate.click(button)
    expect(openDeleteStimulusModalStub).to.have.been.called()
  })

  it('renders an immutability warning wrapper when not banked', () => {
    testbed.render()
    const wrapper = testbed.findChildByType(WarningWrapper)
    expect(wrapper).to.exist()
  })

  it('renders as show when banked', () => {
    testbed.render({ isBankedContent: true })
    const wrapper = testbed.findChildByType(StimulusShowInfo)
    expect(wrapper).to.exist()
  })

  it('clears temporary stimulus on delete stimulus button click when temporary', () => {
    testbed.render({stimulus: tempStimulus})
    const button = testbed.findChildrenByTagNameAndAttribute('div', 'role', 'button')[DELETE_STIMULUS_MODAL_BUTTON]
    ReactTestUtils.Simulate.click(button)
    expect(openDeleteStimulusModalStub).to.have.not.been.called()
    expect(clearStub).to.have.been.called()
  })

  describe('discard changes', () => {
    it('clears if temporary stimulus', () => {
      testbed.render({stimulus: tempStimulus})
      const button = findTestbedCancelButton(testbed)
      ReactTestUtils.Simulate.click(button)
      expect(clearStub).to.have.been.calledWith(defaultProps.quizId)
    })

    it('clears modifications and turns off editing', () => {
      testbed.render()
      const button = findTestbedCancelButton(testbed)
      ReactTestUtils.Simulate.click(button)
      expect(clearModificationsStub).to.have.been.called()
      expect(switchOffEditingStub).to.have.been.called()
    })

    it('sets shouldClone to false', () => {
      testbed.render()
      const button = findTestbedCancelButton(testbed)
      ReactTestUtils.Simulate.click(button)
      expect(setUiStub).to.be.calledWith(SHOULD_CLONE_ITEM, false)
    })
  })

  describe('clicked outside', () => {
    const clickOutside = () => {
      const outerElement = document.createElement('div')
      outerElement.className = 'root'
      document.body.appendChild(outerElement)
      mouseEventOn(outerElement, 'click')
    }

    it('submits on outside click', (done) => {
      testbed.render()
      clickOutside()
      setTimeout(() => {
        expect(clearModificationsStub).to.have.been.called()
        done()
      })
    })

    it('calls switchOffEditing if immutable', () => {
      sandbox.stub(stimulus, 'isImmutable').returns(true)
      testbed.render()
      clickOutside()
      expect(switchOffEditingStub).to.have.been.called()
    })

    it('calls switchOffEditing if banked', () => {
      testbed.render({ isBankedContent: true })
      clickOutside()
      expect(switchOffEditingStub).to.have.been.called()
    })
  })

  describe('saving', () => {
    context('invalid', () => {
      it('shows errors if the stimulus is invalid', () => {
        const stim = stimulus.merge({title: ''})
        stim.isValid = () => false
        testbed.render({ workingStimulus: stim })

        const button = findTestbedSaveButton(testbed)
        ReactTestUtils.Simulate.click(button)

        expect(showErrorStub).to.have.been.calledWith(defaultProps.guid)
      })

      it('clears the nextQuizEntry if invalid', () => {
        const stim = stimulus.merge({title: ''})
        stim.isValid = () => false
        testbed.render({ workingStimulus: stim })

        const button = findTestbedSaveButton(testbed)
        ReactTestUtils.Simulate.click(button)

        expect(clearNextQuizEntryStub).to.have.been.called()
      })

      it('does not update the stimulus if the stimulus is invalid', () => {
        const stim = stimulus.merge({title: ''})
        stim.isValid = () => false
        testbed.render({ workingStimulus: stim })

        const button = findTestbedSaveButton(testbed)
        ReactTestUtils.Simulate.click(button)

        expect(hideErrorStub).to.have.not.been.called()
        expect(updateStimulusStub).to.have.not.been.called()
      })
    })

    it('hides the errors if the stimulus is valid', () => {
      testbed.render()
      const button = findTestbedSaveButton(testbed)
      ReactTestUtils.Simulate.click(button)

      expect(showErrorStub).to.have.not.been.called()
      expect(hideErrorStub).to.have.been.calledWith(defaultProps.guid)
    })

    it('clears the temporary stimulus on save', (done) => {
      testbed.render({stimulus: tempStimulus})
      const button = findTestbedSaveButton(testbed)
      ReactTestUtils.Simulate.click(button)
      setTimeout(() => {
        expect(clearStub).to.have.been.calledWith(defaultProps.quizId)
        done()
      })
    })

    it('calls all methods in afterSubmit', (done) => {
      testbed.render()
      const button = findTestbedSaveButton(testbed)
      ReactTestUtils.Simulate.click(button)
      setTimeout(() => {
        expect(clearModificationsStub).to.have.been.called()
        expect(switchOffEditingStub).to.have.been.called()
        expect(setUiStub).to.have.been.called()
        expect(screenreaderNotificationStub).to.have.been.called()
        done()
      })
    })

    it('cannot be created twice if submit is double clicked', () => {
      testbed.render()
      const button = findTestbedSaveButton(testbed)
      ReactTestUtils.Simulate.click(button)
      ReactTestUtils.Simulate.click(button)
      expect(updateStimulusStub).to.have.been.calledOnce()
    })

    context('with normal stimulus', () => {
      it('shows the correct save text', () => {
        testbed.render()
        expect(testbed.rootNode.innerText.match('Save Stimulus')).to.exist()
      })

      it('updates the stimulus on save', (done) => {
        testbed.render()
        const button = findTestbedSaveButton(testbed)
        ReactTestUtils.Simulate.click(button)
        setTimeout(() => {
          expect(updateStimulusStub).to.have.been.calledOnce()
          done()
        })
      })

      context('when updating stimulus fails', () => {
        it('re-enables button', (done) => {
          testbed.render({ updateStimulus: sandbox.stub().rejects() })
          const button = findTestbedSaveButton(testbed)
          ReactTestUtils.Simulate.click(button)
          setTimeout(() => {
            expect(button.disabled).to.be.false()
            done()
          })
        })
      })
    })

    context('with tempStimulus', () => {
      it('creates the stimulus and quiz entry on save', () => {
        testbed.render({stimulus: tempStimulus})
        const button = findTestbedSaveButton(testbed)
        ReactTestUtils.Simulate.click(button)
        expect(createStimulusAndQuizEntryStub).to.have.been.calledWith(defaultProps.quizId)
      })

      it('clears the temporary stimulus on save', (done) => {
        testbed.render({stimulus: tempStimulus})
        const button = findTestbedSaveButton(testbed)
        ReactTestUtils.Simulate.click(button)
        setTimeout(() => {
          expect(clearStub).to.have.been.calledWith(defaultProps.quizId)
          done()
        })
      })

      it('calls switchOffEditing on save', (done) => {
        testbed.render({stimulus: tempStimulus})
        const button = findTestbedSaveButton(testbed)
        ReactTestUtils.Simulate.click(button)
        setTimeout(() => {
          expect(switchOffEditingStub).to.have.been.called()
          done()
        })
      })

      it('shows the correct save text', () => {
        testbed.render({stimulus: tempStimulus})
        expect(testbed.rootNode.innerText.match('Add Stimulus')).to.exist()
      })

      it('creates the next quiz entry when needed', (done) => {
        testbed.render({stimulus: tempStimulus, nextQuizEntry: Map()})
        const button = findTestbedSaveButton(testbed)
        ReactTestUtils.Simulate.click(button)
        setTimeout(() => {
          expect(createTempStimulusStub).to.have.been.called()
          expect(clearNextQuizEntryStub).to.have.been.called()
          done()
        })
      })

      context('when creating stimulus or quiz entry fails', () => {
        it('re-enables button', (done) => {
          testbed.render({
            stimulus: tempStimulus,
            createStimulusAndQuizEntry: sandbox.stub().rejects()
          })
          const button = findTestbedSaveButton(testbed)
          ReactTestUtils.Simulate.click(button)
          setTimeout(() => {
            expect(button.disabled).to.be.false()
            done()
          })
        })
      })
    })

    context('with shouldClone as true', () => {
      it('calls createStimulus', () => {
        testbed.render({shouldClone: true})
        const button = findTestbedSaveButton(testbed)
        ReactTestUtils.Simulate.click(button)
        expect(createStimulusStub).to.have.been.called()
      })

      it('calls updateQuizEntry', (done) => {
        testbed.render({shouldClone: true})
        const button = findTestbedSaveButton(testbed)
        ReactTestUtils.Simulate.click(button)
        setTimeout(() => {
          expect(updateQuizEntryStub).to.have.been.called()
          done()
        })
      })

      it('calls getQuizEntries', (done) => {
        testbed.render({shouldClone: true})
        const button = findTestbedSaveButton(testbed)
        ReactTestUtils.Simulate.click(button)
        setTimeout(() => {
          expect(getQuizEntriesStub).to.have.been.called()
          done()
        })
      })

      it('calls all methods in afterSubmit', (done) => {
        testbed.render()
        const button = findTestbedSaveButton(testbed)
        ReactTestUtils.Simulate.click(button)
        setTimeout(() => {
          expect(clearModificationsStub).to.have.been.called()
          expect(switchOffEditingStub).to.have.been.called()
          expect(setUiStub).to.have.been.called()
          expect(screenreaderNotificationStub).to.have.been.called()
          done()
        })
      })

      context('when cloning stimulus fails', () => {
        it('re-enables button', (done) => {
          testbed.render({
            shouldClone: true,
            createStimulus: sandbox.stub().rejects()
          })
          const button = findTestbedSaveButton(testbed)
          ReactTestUtils.Simulate.click(button)
          setTimeout(() => {
            expect(button.disabled).to.be.false()
            done()
          })
        })
      })

      context('when updating quiz entry fails', () => {
        it('re-enables button', (done) => {
          testbed.render({
            shouldClone: true,
            updateQuizEntry: sandbox.stub().rejects()
          })
          const button = findTestbedSaveButton(testbed)
          ReactTestUtils.Simulate.click(button)
          setTimeout(() => {
            expect(button.disabled).to.be.false()
            done()
          })
        })
      })
    })
  })

  describe('modifications', () => {
    let clock = null
    beforeEach(() => {
      clock = sinon.useFakeTimers()
    })

    afterEach(() => {
      clock.restore()
    })

    it('clears modifications on unmount', () => {
      clearModificationsStub.resetHistory() // Be extra sure we've reset this stub
      testbed.render()
      testbed.unmount()

      clock.tick(20)

      expect(clearModificationsStub).to.have.been.calledOnce()
    })
  })

  describe('adding to banks', () => {
    beforeEach(() => {
      // needed for withTestFeatures to work
      unsetFakeStore()
    })
    withTestFeatures(['stimulus_banking'])

    it('renders AddToBankOptions', () => {
      testbed.render()
      const details = testbed.findChildByType(ToggleDetails)
      const button = testbed.findChildrenByTagName('button', details)[0]
      ReactTestUtils.Simulate.click(button)
      const bankOptions = testbed.findChildByType(AddToBankOptions)
      expect(bankOptions).to.not.be.undefined()
    })

    it('renders AddToBankModal', () => {
      testbed.render()
      const modal = testbed.findChildByType(AddToBankModal)
      expect(modal).to.not.be.undefined()
    })

    it('disables submit on continue', () => {
      const subject = testbed.render()
      testbed.findChildByType(AddToBankModal).props.onContinue()
      expect(subject.refs.composedComponent.state.canSubmit).to.be.false()
    })

    it('creates the entry for the bank if it does not exist', () => {
      testbed.render({stimulus: tempStimulus})
      testbed.findChildByType(AddToBankModal).props.getEntry()
      expect(updateStimulusStub).not.to.have.been.called()
      expect(createStimulusStub).to.have.been.called()
    })

    it('updates the entry and returns it for the bank if it exists', () => {
      testbed.render()
      testbed.findChildByType(AddToBankModal).props.getEntry()
      expect(createStimulusStub).not.to.have.been.called()
      expect(updateStimulusStub).to.have.been.called()
    })

    it('creates the entry for the bank if immutable with changes', () => {
      testbed.render({ shouldClone: true })
      testbed.findChildByType(AddToBankModal).props.getEntry()
      expect(updateStimulusStub).not.to.have.been.called()
      expect(createStimulusStub).to.have.been.called()
    })

    it('updates the quiz entry with freshly banked content', () => {
      const bankEntry = { id: '11' }
      testbed.render()
      testbed.findChildByType(AddToBankModal).props.onAdd(bankEntry)
      expect(updateQuizEntryStub).to.have.been
        .calledWith('100', 3, { entryId: '11', entryType: 'BankEntry' })
    })
  })

  describe('test with connect', () => {
    const connectedTestbed = createTestbed(
      withContext(ConnectedStimulusEdit),
      defaultProps
    )

    shouldRenderComponent(
      connectedTestbed, {}, WrappedStimulusEdit
    )
  })

  describe('a11y tests', () => {
    it('should meet a11y standards', (done) => {
      testbed.render()
      // ignoring button-name because of the textcolor tinymce plugin
      testbed.checkA11yStandards(done, {
        ignores: [
          'aria-allowed-role', // TODO: remove this when canvas-rce is fixed
          'button-name'
        ]
      })
    })
  })
})
