import { fromJS, List } from 'immutable'

import ChoiceShowComponent from 'quiz-interactions/lib/components/multiple_choice/Show'
import ChoiceEditComponent from 'quiz-interactions/lib/components/multiple_choice/Edit'
import InteractionType from 'quiz-interactions/lib/records/InteractionType'
import Alert from '@instructure/ui-alerts/lib/components/Alert'

import Item from '../../../../../../common/records/Item'
import QuizEntryRecord from '../../../../../../common/records/QuizEntry'
import Bank from '../../../../../../common/records/Bank'
import ItemEdit from '../../../../../../common/components/resources/item/ItemEdit'
import ItemShow from '../../../../../../common/components/resources/item/ItemShow'
import ItemOverride from '../../../../../../common/components/resources/item/ItemOverride'
import Stimulus from '../../../../../../common/records/Stimulus'

import QuizEntry from '../presenter'
import ConnectedQuizEntry from '../index'
import QuizEntryShow from '../../QuizEntryShow'
import QuizEntryEdit from '../../QuizEntryEdit'
import StimulusEdit from '../../../../../../common/components/resources/stimulus/StimulusEdit'
import StimulusShow from '../../../../../../common/components/resources/stimulus/StimulusShow'

import overlayStyles from '../../../../../../common/components/shared/overlay/styles.css'
import { TEMPORARY_ITEM_ID } from '@instructure/quiz-common/lib/constants'
import { QuizEntryBankEntry } from '../../../../../../../tests/factories/QuizEntry'
import WarningWrapper from '../../../../../../common/components/resources/WarningWrapper'

describe('QuizEntry Building', () => {
  const sandbox = sinon.createSandbox()
  const QuizEntryWithContext = withContext(wrapInDragAndDropTestContext(QuizEntry))
  const fakeItem = new Item({ title: 'Super Neat Item' })
  fakeItem.getInteractionType = () => new InteractionType({ slug: 'choice' })
  const changeQuizEntryPointsStub = sandbox.stub()
  const openModalStub = sandbox.stub()
  const screenreaderNotificationStub = sandbox.stub()
  const showErrorStub = sandbox.stub()
  const itemQuizEntry = new QuizEntryRecord({ id: 3 })
  sinon.stub(itemQuizEntry, 'getEntry').returns(fakeItem)
  const propsForQuizEntryStub = sandbox.stub().returns({
    quizEntry: itemQuizEntry,
    quizEntries: List([])
  })

  const props = {
    connectDropTarget: sinon.stub().returnsArg(0),
    changeItemState: () => { },
    clearModifications: () => { },
    changeQuizEntryPoints: changeQuizEntryPointsStub,
    editable: true,
    errorsAreShowing: false,
    hideError: () => { },
    interactionType: new InteractionType({ name: 'foo' }),
    isEditing: false,
    isValid: true,
    entry: fakeItem,
    workingEntry: fakeItem,
    openModal: openModalStub,
    propsForQuizEntry: propsForQuizEntryStub,
    quizEntry: new QuizEntryRecord({ entryType: 'Item', entry: fakeItem }),
    quizEntries: List([]),
    removeItem: () => { },
    screenreaderNotification: screenreaderNotificationStub,
    scrollKey: 'scrollKey',
    setUi: () => { },
    showError: showErrorStub,
    switchOnEditing: () => { },
    switchOffEditing: () => { },
    onEditQuizEntryToggle: () => { },
    editQuizEntryToggle: false
  }

  const testbed = createTestbed(QuizEntryWithContext, props)

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

  describe('is editing', () => {
    it('is does not show an overlay when mouse is over', () => {
      testbed.render({
        isEditing: true
      })

      const overlayElement = testbed.findChildrenByClass(overlayStyles.overlay)[0]
      expect(overlayElement).to.be.undefined()
    })

    it('passes the right props to the interaction component', () => {
      testbed.render({
        isEditing: true
      })

      const editElement = testbed.findChildByType(ChoiceEditComponent)
      expect(Object.keys(editElement.props)).to.include('itemBody')
      expect(Object.keys(editElement.props)).to.include('changeItemState')
    })

    it('renders a quizEntryEdit child', () => {
      testbed.render({
        isEditing: true
      })

      const editElement = testbed.findChildByType(QuizEntryEdit)
      expect(editElement).to.not.be.undefined()
    })

    it('renders a quizEntryEdit child when item is temporary', () => {
      testbed.render({
        isEditing: false,
        entry: fakeItem.set('id', TEMPORARY_ITEM_ID)
      })

      const editElement = testbed.findChildByType(QuizEntryEdit)
      expect(editElement).to.not.be.undefined()
    })

    it('calls points modifications on entry points change', () => {
      testbed.render({ isEditing: true })
      const component = testbed.findChildrenByType(ItemEdit)[0]
      component.props.handleQuizEntryPointsChange(1)

      expect(changeQuizEntryPointsStub.getCall(0).args[0]).to.eq('0')
      expect(changeQuizEntryPointsStub.getCall(0).args[1]).to.eq(1)
    })

    it('renders an immutability warning if immutable', () => {
      const item = new Item({ status: 'immutable' })
      const qe = new QuizEntryRecord({ status: 'immutable', entryType: 'Item' })
      sinon.stub(qe, 'getEntry').returns(item)
      testbed.render({
        isEditing: true,
        quizEntry: qe,
        entry: item
      })
      const warning = testbed.findChildByType(Alert)
      expect(warning.props.variant).to.equal('warning')
    })

    it('renders a deleted bankEntry warning if an archived bankEntry', () => {
      const { entry, quizEntry } = QuizEntryBankEntry({ bankEntryAttrs: { archived: true } })
      testbed.render({
        isEditing: true,
        quizEntry: quizEntry,
        entry
      })
      const warning = testbed.findChildByType(Alert)
      expect(warning.props.variant).to.equal('info')
    })

    describe('stimuli', () => {
      it('renders a stimulus quizEntry without items', () => {
        const stimulus = new Stimulus()
        testbed.render({
          quizEntry: new QuizEntryRecord({ entryType: 'Stimulus', entry: stimulus })
        })

        const element = testbed.findChildByType(StimulusShow)
        expect(element).to.not.be.undefined()

        const quizEntryElements = testbed.findChildrenByType(QuizEntry)
        expect(quizEntryElements.length).to.eq(1)
      })

      it('renders a stimulus quizEntry with items', () => {
        const stimulus = new Stimulus()
        const childEntry = new QuizEntryRecord({ id: 1, entryType: 'Item', entry: new Item() })
        sinon.stub(childEntry, 'getEntry').returns(new Item())
        const childEntries = List([childEntry])
        const quizEntry = new QuizEntryRecord({ id: 2, entryType: 'Stimulus', entry: stimulus })
        sinon.stub(quizEntry, 'getNestedEntries').returns(childEntries)
        sinon.stub(quizEntry, 'getEntry').returns(stimulus)
        testbed.render({
          quizEntry,
          childEntries,
          entry: stimulus
        })

        const element = testbed.findChildByType(StimulusShow)
        expect(element).to.not.be.undefined()

        const quizEntryElements = testbed.findChildrenByType(QuizEntry)
        expect(quizEntryElements.length).to.eq(2)
        // Rendered by stimulus
        const connectedQuizEntryElements = testbed.findChildrenByType(ConnectedQuizEntry)
        expect(connectedQuizEntryElements.length).to.eq(1)
      })

      it('renders an editing stimulus quizEntry', () => {
        const stimulus = new Stimulus()
        testbed.render({
          isEditing: true,
          quizEntry: new QuizEntryRecord({ entryType: 'Stimulus', entry: stimulus }),
          entry: stimulus
        })

        const element = testbed.findChildByType(StimulusEdit)
        expect(element).to.not.be.undefined()
      })

      it('renders a deleted bankEntry warning if an archived bankEntry', () => {
        const { entry, quizEntry } = QuizEntryBankEntry({
          bankEntryAttrs: { archived: true },
          entryType: 'Stimulus'
        })
        testbed.render({
          isEditing: true,
          quizEntry: quizEntry,
          entry
        })
        const warning = testbed.findChildByType(Alert)
        expect(warning.props.variant).to.equal('info')
      })
    })

    describe('renders bank', () => {
      it('renders a quizEntryShow child', () => {
        const bank = new Bank({ title: 'Astronomy Questions' })
        testbed.render({
          entry: bank,
          quizEntry: new QuizEntryRecord({ entryType: 'Bank', entry: bank })
        })

        const editElement = testbed.findChildByType(QuizEntryShow)
        expect(editElement.props.name).to.eq('Astronomy Questions')
      })
    })

    describe('renders bank entry', () => {
      it('renders a quizEntryShow child', () => {
        const { entry, quizEntry } = QuizEntryBankEntry()

        testbed.render({
          quizEntry,
          entry
        })

        const editElement = testbed.findChildByType(QuizEntryShow)
        expect(editElement).to.not.be.undefined()
      })

      it('renders a QuizEntryEdit child', () => {
        const { entry, quizEntry } = QuizEntryBankEntry()

        testbed.render({
          quizEntry,
          entry,
          isEditing: true
        })

        const editElement = testbed.findChildByType(QuizEntryEdit)
        expect(editElement).to.not.be.undefined()
        const alert = testbed.findChildByType(Alert)
        expect(alert).to.not.be.undefined()
        expect(alert.props.variant).to.equal('info')
      })

      it('renders an unauthorized QuizEntryEdit child', () => {
        const { entry, quizEntry } = QuizEntryBankEntry({ quizEntryAttrs: { entryEditable: false } })

        testbed.render({
          quizEntry,
          entry,
          isEditing: true
        })

        const editElement = testbed.findChildByType(QuizEntryEdit)
        expect(editElement).to.not.be.undefined()
        const alert = testbed.findChildByType(Alert)
        expect(alert).to.not.be.undefined()
        expect(alert.props.variant).to.equal('warning')
      })

      it('merges quiz entry overrides into item', () => {
        const { entry, quizEntry } = QuizEntryBankEntry({
          itemAttrs: { properties: fromJS({
            shuffleRules: {
              choices: { shuffled: true, toLock: [0, 3] }
            }
          }) },
          quizEntryAttrs: {
            properties: fromJS({
              itemOverrides: {
                properties: {
                  shuffleRules: {
                    choices: { toLock: [3] }
                  }
                }
              }
            })
          }
        })
        testbed.render({
          quizEntry,
          entry
        })
        const itemShow = testbed.findChildByType(ItemShow)
        const shuffleRules = itemShow.props.entry.properties.getIn(['shuffleRules', 'choices']).toJS()
        expect(shuffleRules.shuffled).to.be.true()
        expect(shuffleRules.toLock).to.eql([3])
      })

      it('renders ItemOverride for overridable types', () => {
        const { quizEntry } = QuizEntryBankEntry({ itemAttrs: { title: 'T/F Item' } })
        testbed.render({
          isEditing: true,
          quizEntry
        })
        const itemComponents = testbed.findChildrenByType(ItemOverride)
        expect(itemComponents).to.have.length(1)
      })

      it('renders ItemShow for non-overridable types', () => {
        const { entry, quizEntry } = QuizEntryBankEntry({ itemAttrs: { title: 'T/F Item' } })
        entry.getInteractionType.restore()
        sinon.stub(entry, 'getInteractionType').returns(new InteractionType({ slug: 'true-false' }))
        testbed.render({
          entry,
          isEditing: true,
          quizEntry
        })
        const itemComponents = testbed.findChildrenByType(ItemShow)
        expect(itemComponents).to.have.length(1)
      })
    })
  })

  describe('with item banking enabled', () => {
    it('includes Item Banking as additionalOptions', () => {
      testbed.render({ isEditing: true })
      const component = testbed.findChildByType(ItemEdit)
      expect(component.props.additionalOptions).to.have.length(1)
      expect(component.props.additionalOptions.map((opt) => opt.key)).to.include('banks')
    })
  })

  describe('is showing', () => {
    it('renders a non-navigable tab index for scroll focus', () => {
      testbed.render()
      const subject = testbed.findChildrenByTagNameAndAttribute(
        'div', 'tabindex', '-1'
      )[0]
      expect(subject).to.be.ok()
    })

    it('is shows an overlay when mouse is over', () => {
      testbed.render()

      const overlayElement = testbed.findChildrenByClass(overlayStyles.overlay)[0]
      expect(overlayElement).to.not.be.undefined()
    })

    it('passes itemBody to the interaction component', () => {
      testbed.render()

      const showElement = testbed.findChildByType(ChoiceShowComponent)
      expect(Object.keys(showElement.props)).to.include('itemBody')
    })

    it('renders a QuizEntryShow child', () => {
      testbed.render()

      const showElement = testbed.findChildByType(QuizEntryShow)
      expect(showElement).to.not.be.undefined()
    })

    it('passes scrollKey to QuizEntryShow', () => {
      testbed.render()

      const showElement = testbed.findChildByType(QuizEntryShow)
      expect(Object.keys(showElement.props)).to.include('scrollKey')
    })

    describe('renders bank entry', () => {
      it('renders a quizEntryShow child', () => {
        const { entry, quizEntry } = QuizEntryBankEntry()
        testbed.render({
          quizEntry,
          entry
        })

        const editElement = testbed.findChildByType(QuizEntryShow)
        expect(editElement).to.not.be.undefined()
      })
    })

    describe('renders bank entry with isEditing true', () => {
      it('renders a quizEntryEdit child and respond to defaultActionCallback', () => {
        const { entry, quizEntry } = QuizEntryBankEntry()
        const setUiStub = sandbox.stub()
        const newActiveBankEntryStub = sandbox.stub()
        const newActiveBankStub = sandbox.stub()
        const switchOffEditingStub = sandbox.stub()
        testbed.render({
          quizEntry,
          entry,
          isEditing: true,
          setUi: setUiStub,
          newActiveBankEntry: newActiveBankEntryStub,
          newActiveBank: newActiveBankStub,
          switchOffEditing: switchOffEditingStub
        })
        const editElement = testbed.findChildByType(QuizEntryEdit)
        expect(editElement).to.not.be.undefined()

        const warningWrapper = testbed.findChildByType(WarningWrapper)
        expect(warningWrapper).to.not.be.undefined()
        warningWrapper.props.defaultActionCallback()
        expect(setUiStub).to.be.called()
        expect(newActiveBankEntryStub).to.be.called()
        expect(newActiveBankStub).to.be.called()
        expect(setUiStub).to.be.called()
      })
    })
  })

  describe('a11y tests', () => {
    it('should meet a11y standards', (done) => {
      testbed.render()
      testbed.checkA11yStandards(done)
    })
  })
})
