import InteractionType from 'quiz-interactions/lib/records/InteractionType'

import Alert from '@instructure/ui-alerts/lib/components/Alert'
import BreadcrumbLink from '@instructure/ui-breadcrumb/lib/components/Breadcrumb/BreadcrumbLink'
import Heading from '@instructure/ui-elements/lib/components/Heading'
import Button from '@instructure/ui-buttons/lib/components/Button'
import TextInput from '@instructure/ui-forms/lib/components/TextInput'

import BankEntry from '../presenter'

import Bank from '../../../../common/records/Bank'
import BankEntryRecord from '../../../../common/records/BankEntry'
import EntrySave from '../../../../common/components/resources/entry/EntrySave'
import Item from '../../../../common/records/Item'
import ItemEdit from '../../../../common/components/resources/item/ItemEdit'
import ItemFeedbackModal from '../../../../building/components/resources/ItemFeedbackModal'
import Page from '../../../../common/components/layout/Page'
import Stimulus from '../../../../common/records/Stimulus'
import StimulusEditInfo from '../../../../common/components/resources/stimulus/StimulusEditInfo'
import TagSuggestSelect from '../../../../common/components/shared/TagSuggestSelect'
import {
  ITEM_FEEDBACK_MODAL,
  PARENT_TYPE_BANK
} from '@instructure/quiz-common/lib/constants'
import { createItem } from '../../../../building/api/items'
import { createStimulus } from '../../../../building/api/stimuli'

import Immutable from 'immutable'

describe('BankEntry', () => {
  const type = new InteractionType({ name: 'Multiple Choice', slug: 'choice' })
  const item = Item.create({
    id: '666',
    title: 'Tough Color Question',
    itemBody: 'Which color is happiest?',
    scoringData: {
      value: 'id'
    }
  })
  sinon.stub(item, 'getInteractionType').returns(type)
  sinon.stub(item, 'hasFeedback').returns(true)
  const stimulus = Stimulus.create({
    id: '555',
    title: 'Stuff'
  })
  sinon.stub(stimulus, 'hasModifications').returns(true)
  const bankEntry = BankEntryRecord.create({
    id: '1',
    entry: Item.create({id: '666'}),
    entryType: 'Item',
    updatedAt: new Date()
  })
  sinon.stub(bankEntry, 'getEntry').returns(item)
  const stimulusBankEntry = BankEntryRecord.create({
    id: '2',
    entry: stimulus,
    entryType: 'Stimulus',
    updatedAt: new Date()
  })
  sinon.stub(stimulusBankEntry, 'getEntry').returns(stimulus)
  const bank = Bank.create({
    id: '1',
    title: 'Units of Measurement'
  })

  const navigateBankStub = sinon.stub()
  const navigateBanksStub = sinon.stub()
  const updateItemStub = sinon.stub()
  const updateStimulusStub = sinon.stub()
  const clearModifcationsStub = sinon.stub()
  const createEntryAndBankEntryStub = sinon.stub()
  const setPendingAdditionTagAssociationsStub = sinon.stub()
  const setPendingDeletionTagAssociationsStub = sinon.stub()
  const createTagAssociationsStub = sinon.stub()
  const deleteTagAssociationsStub = sinon.stub()
  const clearTemporaryBankItemStub = sinon.stub()
  const setUiStub = sinon.stub()
  const openModalStub = sinon.stub()
  const changeItemTitleStub = sinon.stub()
  const setTagInputStub = sinon.stub()
  const getTagSuggestionsStub = sinon.stub()
  const setTagSuggestionsStub = sinon.stub()
  const getBankEntryStub = sinon.stub()

  const entryProps = {
    getBank: () => {},
    getBankEntry: getBankEntryStub,
    bank,
    bankId: bank.id,
    bankEntry,
    bankEntryId: bankEntry.id,
    entry: item,
    changeItemTitle: changeItemTitleStub,
    navigateToBanks: navigateBanksStub,
    navigateToBank: navigateBankStub,
    updateItem: updateItemStub,
    updateStimulus: updateStimulusStub,
    createEntryAndBankEntry: createEntryAndBankEntryStub,
    setPendingAdditionTagAssociations: setPendingAdditionTagAssociationsStub,
    setPendingDeletionTagAssociations: setPendingDeletionTagAssociationsStub,
    setTagInput: setTagInputStub,
    setTagSuggestions: setTagSuggestionsStub,
    getTagSuggestions: getTagSuggestionsStub,
    setShouldClone: () => {},
    clearModifications: clearModifcationsStub,
    createTagAssociations: createTagAssociationsStub,
    deleteTagAssociations: deleteTagAssociationsStub,
    clearTemporaryBankItem: clearTemporaryBankItemStub,
    setUi: setUiStub,
    openModal: openModalStub
  }
  const testbed = createTestbed(BankEntry, entryProps)
  const BankEntryWithContext = withContext(BankEntry)
  const connectedTestbed = createTestbed(BankEntryWithContext, entryProps)

  let clock = null

  beforeEach(() => {
    clock = sinon.useFakeTimers()
    sinon.stub(item, 'hasModifications').returns(true)
  })

  afterEach(() => {
    clock.restore()
    item.hasModifications.restore()
    navigateBankStub.resetHistory()
    navigateBanksStub.resetHistory()
    updateItemStub.resetHistory()
    updateStimulusStub.resetHistory()
    clearModifcationsStub.resetHistory()
    createEntryAndBankEntryStub.resetHistory()
    setPendingAdditionTagAssociationsStub.resetHistory()
    setPendingDeletionTagAssociationsStub.resetHistory()
    clearTemporaryBankItemStub.resetHistory()
    createTagAssociationsStub.resetHistory()
    deleteTagAssociationsStub.resetHistory()
    setUiStub.resetHistory()
    openModalStub.resetHistory()
    changeItemTitleStub.resetHistory()
    setTagInputStub.resetHistory()
    getTagSuggestionsStub.resetHistory()
    setTagSuggestionsStub.resetHistory()
    getBankEntryStub.resetHistory()
  })

  it('renders a Page component', () => {
    const output = testbed.renderShallowly()
    expect(output.find(Page)).to.have.length(1)
  })

  describe('navigation', () => {
    it('renders Bank Entry app navigation', () => {
      const output = testbed.renderShallowly()
      const crumbs = output.find(BreadcrumbLink)
      expect(crumbs.length).to.eq(2)
      const link = crumbs.first()
      expect(link.render().text()).to.contain('Units of Measurement')
      const link2 = crumbs.at(1)
      expect(link2.render().text()).to.contain('Edit Item')
    })

    it('calls right methods perform navigation', () => {
      const output = testbed.renderShallowly()
      const crumbs = output.find(BreadcrumbLink)
      crumbs.first().simulate('click')
      expect(navigateBankStub.called).to.be.true()
    })

    it('does not render a return-to-build button when not passed a navigateToBuild prop', () => {
      const output = testbed.renderShallowly({ navigateToBuild: null })
      const buttons = output.find(Button)
      const texts = buttons.map(button => buttons.first().prop('children'))
      expect(texts.join()).to.not.include('Return')
    })
  })

  context('with item entry type', () => {
    it('renders BankEntry app header', () => {
      const output = testbed.renderShallowly()
      const heading = output.find(Heading)
      expect(heading.render().text()).to.eq('Multiple Choice | Item 666')
    })

    it('renders bank entry', () => {
      const output = testbed.renderShallowly()
      const entryRows = output.find(ItemEdit)
      expect(entryRows.length).to.eq(1)
    })

    it('renders the title field', () => {
      const output = testbed.renderShallowly()
      const title = output.find(TextInput)
      expect(title.length).to.eq(1)
    })

    it('renders a warning that edits will appear on any assessment using the item', () => {
      const output = connectedTestbed.mount()
      const alert = output.find(Alert)
      expect(alert.length).to.eq(1)
      expect(alert.text()).to.include('Edits made here will appear on any assessment using this item')
    })

    it('updates the title field on change', () => {
      const output = testbed.renderShallowly()
      const title = output.find(TextInput)
      title.prop('onChange')({ target: { value: 'some new title' } })
      expect(changeItemTitleStub).to.have.been.calledWith('666', 'some new title')
    })

    it('submitHandler calls updateItem for edit', () => {
      connectedTestbed.render()
      const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
      entrySaveRow[0].props.submitHandler()
      expect(updateItemStub).to.be.called()
    })

    it('passes null contextUuid to ItemEdit', () => {
      // important because outcomes does not use course context for item banks
      const output = testbed.renderShallowly()
      expect(output.find(ItemEdit).props().contextUuid).to.equal(null)
    })

    it('does not render tag select', () => {
       const output = testbed.renderShallowly({ useBankTags: false })
       expect(output.find(TagSuggestSelect)).to.have.length(0)
    })

    it('renders tag select', () => {
       const output = testbed.renderShallowly({ useBankTags: true })
       expect(output.find(TagSuggestSelect)).to.have.length(1)
    })
  })

  context('with stimulus entry type', () => {
    it('renders BankEntry app header', () => {
      const output = testbed.renderShallowly({
        bankEntry: stimulusBankEntry,
        bankEntryId: stimulusBankEntry.id,
        entry: stimulus
      })
      const heading = output.find(Heading)
      expect(heading.render().text()).to.eq('Stimulus | Stimulus 555')
    })

    it('renders bank entry', () => {
      const output = testbed.renderShallowly({
        bankEntry: stimulusBankEntry,
        bankEntryId: stimulusBankEntry.id,
        entry: stimulus
      })
      const entryRows = output.find(StimulusEditInfo)
      expect(entryRows.length).to.eq(1)
    })

    it('does not render the extra title field', () => {
      const output = testbed.renderShallowly({
        bankEntry: stimulusBankEntry,
        bankEntryId: stimulusBankEntry.id,
        entry: stimulus
      })
      const title = output.find(TextInput)
      expect(title.length).to.eq(0)
    })

    it('renders a warning that edits will appear on any assessment using the stimulus', () => {
      const output = connectedTestbed.mount({
        bankEntry: stimulusBankEntry,
        bankEntryId: stimulusBankEntry.id,
        entry: stimulus
      })
      const alert = output.find(Alert)
      expect(alert.length).to.eq(1)
      expect(alert.text()).to.include('Edits made here will appear on any assessment using this item')
    })

    it('submitHandler calls updateStimulus for edit', () => {
      connectedTestbed.render({
        bankEntry: stimulusBankEntry,
        bankEntryId: stimulusBankEntry.id,
        entry: stimulus
      })
      const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
      entrySaveRow[0].props.submitHandler()
      expect(updateStimulusStub).to.be.called()
    })
  })

  it('renders entry save', () => {
    const output = testbed.renderShallowly()
    const entrySaveRow = output.find(EntrySave)
    expect(entrySaveRow.length).to.eq(2)
  })

  describe('tags', () => {
    it('shows tag when no pending tag changes', () => {
      connectedTestbed.render({
        tagAssociations: Immutable.fromJS(
          [{id: '1'}]
        )
      })
      const bankEntry = connectedTestbed.findChildrenByType(BankEntry)[0]
      expect(bankEntry.visibleTags().length).to.eq(1)
    })

    it('does not show tag when pending tag deletion', () => {
      connectedTestbed.render({
        tagAssociations: Immutable.fromJS(
          [{id: '1'}]
        ),
        pendingDeletionTags: Immutable.fromJS(
          [{id: '1'}]
        )
      })
      const bankEntry = connectedTestbed.findChildrenByType(BankEntry)[0]
      expect(bankEntry.visibleTags().length).to.eq(0)
    })

    it('shows pending tag additions', () => {
      connectedTestbed.render({
        tagAssociations: Immutable.fromJS(
          [{id: '1', tag_value: 'alpha'}]
        ),
        pendingAdditionTags: Immutable.fromJS(
          [{id: '2', tag_value: 'beta'}]
        )
      })
      const bankEntry = connectedTestbed.findChildrenByType(BankEntry)[0]
      expect(bankEntry.visibleTags().length).to.eq(2)
    })

    it('removes redundant pending additional tag', () => {
      connectedTestbed.render({
        tagAssociations: Immutable.fromJS(
          [{id: '1', tag_value: 'alpha'}]
        ),
        pendingAdditionTags: Immutable.fromJS(
          [{tag_value: 'alpha'}]
        )
      })
      const bankEntry = connectedTestbed.findChildrenByType(BankEntry)[0]
      expect(bankEntry.visibleTags().length).to.eq(1)
    })

    it('sets pending addition tag associations', () => {
      connectedTestbed.render({
        tagAssociations: Immutable.List()
      })
      const bankEntry = connectedTestbed.findChildrenByType(BankEntry)
      bankEntry[0].handleTagChange([{ id: '20', value: 'boo'}])
      expect(setPendingAdditionTagAssociationsStub).to.be.calledWith(
        'Item', '666', [{tag_value: 'boo'}]
      )
    })

    it('sets pending addition tag associations for created tags', () => {
      connectedTestbed.render({
        tagAssociations: Immutable.List()
      })
      const bankEntry = connectedTestbed.findChildrenByType(BankEntry)
      bankEntry[0].handleTagChange([{ id: null, value: 'boo'}])
      expect(setPendingAdditionTagAssociationsStub).to.be.calledWith(
        'Item', '666', [{tag_value: 'boo'}]
      )
    })

    it('sets pending deletion tag associations', () => {
      connectedTestbed.render({
        tagAssociations: Immutable.fromJS(
          [{id: '1'}]
        )
      })
      const bankEntry = connectedTestbed.findChildrenByType(BankEntry)
      bankEntry[0].handleTagChange([])
      expect(setPendingDeletionTagAssociationsStub).to.be.calledWith(
        'Item', '666', [{id: '1'}]
      )
    })

    it('restoring a deleted tag clears pending changes', () => {
      connectedTestbed.render({
        tagAssociations: Immutable.fromJS(
          [{id: '1', tag_value: 'apple'}]
        )
      })
      const bankEntry = connectedTestbed.findChildrenByType(BankEntry)
      bankEntry[0].handleTagChange([{id: '1', value: 'apple'}])
      expect(setPendingAdditionTagAssociationsStub).to.be.calledWith(
        'Item', '666', []
      )
      expect(setPendingDeletionTagAssociationsStub).to.be.calledWith(
        'Item', '666', []
      )
    })
  })

  it('submitHandler creates tag', async () => {
    connectedTestbed.render({
      tagAssociations: Immutable.fromJS(
        [{id: '1'}]
      ),
      pendingAdditionTags: Immutable.fromJS(
        [{tag_value: 'hi'}]
      )
    })
    const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
    expect(entrySaveRow[0].props.submitHandler).to.not.be.undefined()
    await entrySaveRow[0].props.submitHandler()
    expect(createTagAssociationsStub).to.be.calledWith(
      '1', 'Item', '666', Immutable.List(['hi'])
    )
  })

  it('submitHandler deletes tag', async () => {
    connectedTestbed.render({
      tagAssociations: Immutable.fromJS(
        [{id: '1'}]
      ),
      pendingDeletionTags: Immutable.fromJS(
        [{id: '1'}]
      )
    })
    const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
    expect(entrySaveRow[0].props.submitHandler).to.not.be.undefined()
    await entrySaveRow[0].props.submitHandler()
    expect(deleteTagAssociationsStub).to.be.calledWith(
      '1', 'Item', '666', Immutable.List('1')
    )
  })

  it('submitHandler clears modifications and navigates back to Bank', async () => {
    connectedTestbed.render()
    const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
    expect(entrySaveRow[0].props.submitHandler).to.not.be.undefined()
    await entrySaveRow[0].props.submitHandler()
    expect(clearModifcationsStub).to.be.called()
    expect(navigateBankStub).to.be.called()
  })

  it('submitHandler calls createEntryAndBankEntry when creating new item', async () => {
    const tempItem = Item.create({ id: '1' })
    sinon.stub(tempItem, 'getWorkingInstance').returns(tempItem)
    const tempBankEntry = BankEntryRecord.create({ id: '1', entry: tempItem })
    sinon.stub(tempItem, 'isTemporary').returns(true)
    sinon.stub(tempBankEntry, 'getEntry').returns(tempItem)
    connectedTestbed.render({ bankEntry: tempBankEntry, entry: tempItem })
    const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
    await entrySaveRow[0].props.submitHandler()
    expect(createEntryAndBankEntryStub).to.be.called()
    expect(clearModifcationsStub).to.be.called()
    expect(clearTemporaryBankItemStub).to.be.called()
  })

  it('submitHandler clones immutable item properly', () => {
    const cloneBankEntryStub = sinon.stub()
    updateItemStub.resetHistory()
    connectedTestbed.render({
      shouldClone: true,
      cloneBankEntry: cloneBankEntryStub
    })
    const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
    entrySaveRow[0].props.submitHandler()
    expect(cloneBankEntryStub).to.be.calledWith(bankEntry, createItem)
    expect(updateItemStub.called).to.be.false()
  })

  it('submitHandler clones item without modifications except pending tags', async () => {
    item.hasModifications.restore()
    sinon.stub(item, 'hasModifications').returns(false)
    const cloneBankEntryStub = sinon.stub().returns({
      id: '1000',
      entry_type: 'Item',
      entry: {
        id: '2000'
      }
    })
    updateItemStub.resetHistory()
    connectedTestbed.render({
      shouldClone: true,
      cloneBankEntry: cloneBankEntryStub,
      tagAssociations: Immutable.fromJS(
        [{tag_value: 'boo'}]
      ),
      pendingAdditionTags: Immutable.fromJS(
        [{tag_value: 'hi'}]
      )
    })
    const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
    await entrySaveRow[0].props.submitHandler()
    expect(cloneBankEntryStub).to.be.calledWith(bankEntry, createItem)
    expect(createTagAssociationsStub).to.be.calledWith(
      '1000', 'Item', '2000', ['boo', 'hi']
    )
    expect(setPendingAdditionTagAssociationsStub).to.be.calledWith(
      'Item', '666', []
    )
    expect(setPendingDeletionTagAssociationsStub).to.be.calledWith(
      'Item', '666', []
    )
  })

  it('submitHandler clones immutable stimulus properly', () => {
    const cloneBankEntryStub = sinon.stub()
    updateStimulusStub.resetHistory()
    connectedTestbed.render({
      shouldClone: true,
      cloneBankEntry: cloneBankEntryStub,
      bankEntry: stimulusBankEntry
    })
    const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
    entrySaveRow[0].props.submitHandler()
    expect(cloneBankEntryStub).to.be.calledWith(stimulusBankEntry, createStimulus)
    expect(updateStimulusStub.called).to.be.false()
  })

  it('cancelHandler navigates back to Bank', () => {
    connectedTestbed.render()
    const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
    expect(entrySaveRow[0].props.cancelHandler).to.not.be.undefined()
    entrySaveRow[0].props.cancelHandler()
    expect(navigateBankStub).to.be.called()
  })

  it('clearChanges clears modifications', () => {
    connectedTestbed.render()
    const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
    expect(entrySaveRow[0].props.clearChanges).to.not.be.undefined()
    entrySaveRow[0].props.clearChanges()
    expect(clearModifcationsStub).to.be.called()
  })

  it('clearChanges clears pending deletion tag associations', () => {
    connectedTestbed.render()
    const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
    expect(entrySaveRow[0].props.clearChanges).to.not.be.undefined()
    entrySaveRow[0].props.clearChanges()
    expect(setPendingDeletionTagAssociationsStub).to.be.calledWith('Item', '666', [])
  })

  it('clearChanges clears pending addition tag associations', () => {
    connectedTestbed.render()
    const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
    expect(entrySaveRow[0].props.clearChanges).to.not.be.undefined()
    entrySaveRow[0].props.clearChanges()
    expect(setPendingAdditionTagAssociationsStub).to.be.calledWith('Item', '666', [])
  })

  it('clearChanges clears temporaryBankItem when entry is temporary', () => {
    const tempItem = Item.create({ id: '1' })
    sinon.stub(tempItem, 'getWorkingInstance').returns(tempItem)
    const tempBankEntry = BankEntryRecord.create({ id: '1', entry: tempItem })
    sinon.stub(tempItem, 'isTemporary').returns(true)
    sinon.stub(tempBankEntry, 'getEntry').returns(tempItem)
    connectedTestbed.render({ bankEntry: tempBankEntry, entry: tempItem })
    const entrySaveRow = connectedTestbed.findChildrenByType(EntrySave)
    expect(entrySaveRow[0].props.clearChanges).to.not.be.undefined()
    entrySaveRow[0].props.clearChanges()
    expect(clearTemporaryBankItemStub).to.be.called()
  })

  describe('item feedback', () => {
    it('renders item feedback modal with bank parent', () => {
      const shallowTestbed = testbed.renderShallowly()
      const feedbackModal = shallowTestbed.find(ItemFeedbackModal)
      expect(feedbackModal.prop('parentId')).to.eq(bank.id)
      expect(feedbackModal.prop('parentType')).to.eq(PARENT_TYPE_BANK)
    })

    it('calls open item feedback modal', () => {
      const shallowTestbed = testbed.renderShallowly()
      const feedbackButton = shallowTestbed.find(Button).first()
      feedbackButton.simulate('click')
      expect(setUiStub).to.have.been.called()
      expect(openModalStub).to.have.been.calledWith(ITEM_FEEDBACK_MODAL)
    })
  })

  // FIXME: shallow rendering doesn't make sense for a11y tests
  xit('should meet a11y standards', (done) => {
    testbed.renderShallowly()
    testbed.checkA11yStandards(done)
  })
})
