import { fromJS, Set } from 'immutable'
import { expect } from 'chai'

import {
  QUIZ_MODIFICATION,
  BANK_MODIFICATION,
  STIMULUS_MODIFICATION,
  QUIZ_ENTRIES_MODIFICATION,
  QUIZ_SESSION_RESULT_MODIFICATION,
  ITEM_MODIFICATION,
  TEMPORARY_QUIZ_ENTRY_ID,
  TEMPORARY_BANK_ENTRY_ID,
  TEMPORARY_STIMULUS_ID,
  TEMPORARY_ITEM_ID,
  TEMPORARY_BANK_ID,
  CREATE_TEMPORARY_ITEM,
  CREATE_TEMPORARY_BANK_ITEM,
  CREATE_TEMPORARY_STIMULUS,
  CREATE_TEMPORARY_BANK,
  CLEAR_TEMPORARY_ITEM,
  CLEAR_TEMPORARY_BANK_ITEM,
  CLEAR_TEMPORARY_STIMULUS,
  CLEAR_MODIFICATIONS,
  MOVE_TEMPORARY_ITEM,
  SESSION_ITEM_RESULTS_MODIFICATION
} from '@instructure/quiz-common/lib/constants'

import modificationsReducer from '../modifications'

describe('modifications reducer', () => {
  const sharedExamplesForType = (topLevelKey, actionType, { deep = false } = {}) => {
    const modForIdOne = (mods) => {
      return {
        type: actionType,
        payload: {
          id: '1',
          modifications: mods
        }
      }
    }

    it('can add a value for an attr', () => {
      const initialState = fromJS({[topLevelKey]: {}})
      const action = modForIdOne({foo: 'bar'})
      const nextStateItem = modificationsReducer(initialState, action).getIn([topLevelKey, '1'])
      expect(nextStateItem.equals(fromJS({id: '1', foo: 'bar'}))).to.be.true()
    })

    it('can replace a value for an attr', () => {
      const initialState = fromJS({
        [topLevelKey]: {1: {id: '1', foo: 'foo'}}
      })
      const action = modForIdOne({foo: 'bar'})
      const nextStateItem = modificationsReducer(initialState, action).getIn([topLevelKey, '1'])
      expect(nextStateItem.equals(fromJS({id: '1', foo: 'bar'}))).to.be.true()
    })

    it('replaces set values (rather than merging)', () => {
      const initialState = fromJS({
        [topLevelKey]: {1: {id: '1', foo: Set(['1', '2'])}}
      })
      const action = modForIdOne({foo: Set(['3'])})

      const nextStateItem = modificationsReducer(initialState, action).getIn([topLevelKey, '1'])
      expect(nextStateItem.equals(fromJS({id: '1', foo: Set(['3'])}))).to.be.true()
    })

    if (deep) {
      it('deep merges object values', () => {
        const initialState = fromJS({
          [topLevelKey]: {1: {id: '1', foo: {1: 'hey'}}}
        })
        const action = modForIdOne({foo: {2: 'ho'}})

        const nextStateItem = modificationsReducer(initialState, action).getIn([topLevelKey, '1'])
        expect(nextStateItem.equals(fromJS({id: '1', foo: {1: 'hey', 2: 'ho'}}))).to.be.true()
      })
    } else {
      it('replaces object values (rather than merging)', () => {
        const initialState = fromJS({
          [topLevelKey]: {1: {id: '1', foo: {1: 'hey'}}}
        })
        const action = modForIdOne({foo: {2: 'ho'}})

        const nextStateItem = modificationsReducer(initialState, action).getIn([topLevelKey, '1'])
        expect(nextStateItem.equals(fromJS({id: '1', foo: {2: 'ho'}}))).to.be.true()
      })
    }
  }

  describe('quizzes', () => {
    sharedExamplesForType('quizzes', QUIZ_MODIFICATION)
  })

  describe('items', () => {
    sharedExamplesForType('items', ITEM_MODIFICATION)
  })

  describe('quizEntries', () => {
    sharedExamplesForType('quizEntries', QUIZ_ENTRIES_MODIFICATION, { deep: true })
  })

  describe('quizSessionResults', () => {
    sharedExamplesForType('quizSessionResults', QUIZ_SESSION_RESULT_MODIFICATION)
  })

  describe('sessionItemResults', () => {
    sharedExamplesForType('sessionItemResults', SESSION_ITEM_RESULTS_MODIFICATION)
  })

  describe('banks', () => {
    sharedExamplesForType('banks', BANK_MODIFICATION)
  })

  describe('create temporary bank', () => {
    const initialState = fromJS({
      banks: {}
    })

    const createBankAction = (payload = {}) => ({
      type: CREATE_TEMPORARY_BANK,
      payload
    })

    it('creates a bank', () => {
      const state = modificationsReducer(initialState, createBankAction())
      const nextStateItem = state.getIn(['banks', TEMPORARY_BANK_ID])
      expect(nextStateItem.equals(fromJS({ id: TEMPORARY_BANK_ID, title: '' }))).to.be.true()
    })

    it('creates a bank with a specified locale', () => {
      const state = modificationsReducer(initialState, createBankAction({ language: 'en' }))
      const nextStateItem = state.getIn(['banks', TEMPORARY_BANK_ID])
      const bank = { id: TEMPORARY_BANK_ID, title: '' , language: 'en' }
      expect(nextStateItem.toJS()).to.deep.equal(bank)
    })
  })

  describe('create temporary stimulus', () => {
    const initialState = fromJS({
      quizzes: {id: '1'},
      quizEntries: {},
      stimuli: {}
    })

    const position = '77'
    const createStimulusAction = {
      type: CREATE_TEMPORARY_STIMULUS,
      payload: {
        position: position,
        quizId: '1'
      }
    }

    const state = modificationsReducer(initialState, createStimulusAction)

    it('associates the quiz and the temp quiz entry', () => {
      const nextStateItem = state.getIn(['quizzes', '1', 'quizEntries'])
      expect(nextStateItem.equals(fromJS([TEMPORARY_QUIZ_ENTRY_ID]))).to.be.true()
    })

    it('adds temporary quiz entry associated with temporary item', () => {
      const nextStateItem = state.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID, 'entry'])
      expect(nextStateItem).to.equal(TEMPORARY_STIMULUS_ID)
    })

    context('adds temporary quiz entry with position 0.1', () => {
      it('works with quizzes entries already created', () => {
        const nextStateItem = state.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID, 'position'])
        expect(nextStateItem).to.equal(position - 0.1)
      })

      it('works with no quizzes entries already created', () => {
        const initialState = fromJS({
          quizzes: {id: '1'},
          quizEntries: {},
          items: {}
        })

        const position = 0
        const createStimulusActionForNewStimulus = {
          type: CREATE_TEMPORARY_STIMULUS,
          payload: {
            position: position,
            quizId: '1'
          }
        }
        const state = modificationsReducer(initialState, createStimulusActionForNewStimulus)
        const nextStateItem = state.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID, 'position'])
        expect(nextStateItem).to.equal(0.9)
      })
    })

    it('adds the temporary stimulus with data', () => {
      const nextStateItem = state.getIn(['stimuli', TEMPORARY_STIMULUS_ID, 'stimulusType'])
      expect(nextStateItem).to.equal('text')
    })
  })

  describe('modify stimulus', () => {
    const initialState = fromJS({
      stimuli: {
        [TEMPORARY_STIMULUS_ID]: {
          id: TEMPORARY_STIMULUS_ID,
          title: 'hello'
        }
      }
    })

    const modifyStimulusAction = {
      type: STIMULUS_MODIFICATION,
      payload: {
        stimulus: { id: TEMPORARY_STIMULUS_ID, title: 'world' }
      }
    }

    const state = modificationsReducer(initialState, modifyStimulusAction)

    it('overwrites the old stimulus data with the new one', () => {
      const nextStateItem = state.getIn(['stimuli', TEMPORARY_STIMULUS_ID, 'title'])
      expect(nextStateItem).to.equal('world')
    })
  })

  describe('clear temporary stimulus', () => {
    const initialState = fromJS({
      quizzes: {
        44: {
          id: '44',
          quizEntries: [TEMPORARY_QUIZ_ENTRY_ID]
        }
      },
      quizEntries: {
        [TEMPORARY_QUIZ_ENTRY_ID]: {
          id: TEMPORARY_QUIZ_ENTRY_ID,
          entry: TEMPORARY_STIMULUS_ID,
          entryType: 'Stimulus',
          position: 0.9
        }
      },
      stimuli: {
        [TEMPORARY_STIMULUS_ID]: {
          id: TEMPORARY_STIMULUS_ID,
          title: 'hello'
        }
      }
    })

    const clearStimulusAction = {
      type: CLEAR_TEMPORARY_STIMULUS,
      payload: {
        quizId: '44'
      }
    }

    const state = modificationsReducer(initialState, clearStimulusAction)

    it('removes the quiz modifications', () => {
      const nextStateItem = state.getIn(['quizzes', '44', 'quizEntries'])
      expect(nextStateItem).to.be.empty()
    })

    it('removes the temporary quiz entry', () => {
      expect(state.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID])).to.not.exist()
    })

    it('removes the temporary stimulus', () => {
      const nextStateItem = state.get('stimuli')
      expect(nextStateItem.equals(fromJS({}))).to.be.true()
    })
  })

  describe('create temporary item', () => {
    const initialState = fromJS({
      quizzes: {1: {id: '1'}},
      quizEntries: {},
      items: {}
    })

    const position = '77'
    const createItemAction = {
      type: CREATE_TEMPORARY_ITEM,
      payload: {
        interactionTypeData: {
          scoringData: 'someData',
          properties: {hasLabels: true}
        },
        position: position,
        quizId: '1',
        parentEntryId: '66'
      }
    }

    const state = modificationsReducer(initialState, createItemAction)

    it('associates the quiz and the temp quiz entry', () => {
      const nextStateItem = state.getIn(['quizzes', '1', 'quizEntries'])
      expect(nextStateItem.equals(fromJS([TEMPORARY_QUIZ_ENTRY_ID]))).to.be.true()
    })

    it('adds temporary quiz entry associated with temporary item', () => {
      const nextStateItem = state.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID, 'entry'])
      expect(nextStateItem).to.equal(TEMPORARY_ITEM_ID)
    })

    it('adds the temporary quiz entry with stimulus quiz entry id', () => {
      const nextStateItem = state.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID, 'stimulusQuizEntryId'])
      expect(nextStateItem).to.equal('66')
    })

    context('adds temporary quiz entry with position 0.1', () => {
      it('works with quizzes entries already created', () => {
        const nextStateItem = state.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID, 'position'])
        expect(nextStateItem).to.equal(position - 0.1)
      })

      it('works with no quizzes entries already created', () => {
        const initialState = fromJS({
          quizzes: {id: '1'},
          quizEntries: {},
          items: {}
        })

        const position = 0
        const createItemActionForNewItem = {
          type: CREATE_TEMPORARY_ITEM,
          payload: {
            interactionTypeData: {
              scoringData: 'someData',
              properties: {hasLabels: true}
            },
            position: position,
            quizId: '1'
          }
        }
        const state = modificationsReducer(initialState, createItemActionForNewItem)
        const nextStateItem = state.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID, 'position'])
        expect(nextStateItem).to.equal(0.9)
      })
    })

    it('adds the temporary item with data', () => {
      const nextStateItem = state.getIn(['items', TEMPORARY_ITEM_ID, 'scoringData'])
      expect(nextStateItem).to.equal('someData')
    })

    it('includes properties in item data', () => {
      const nextStateItem = state.getIn(['items', TEMPORARY_ITEM_ID, 'properties', 'hasLabels'])
      expect(nextStateItem).to.be.true()
    })
  })

  describe('create temporary bank item', () => {
    const initialState = fromJS({
      banks: {1: {id: '1'}},
      bankEntries: {},
      items: {}
    })

    const createBankItemAction = {
      type: CREATE_TEMPORARY_BANK_ITEM,
      payload: {
        interactionTypeData: {
          scoringData: 'someData',
          properties: {hasLabels: true}
        },
        bankId: '1',
        bankEntryType: 'Item'
      }
    }

    const state = modificationsReducer(initialState, createBankItemAction)

    it('associates the bank and the temp bank entry', () => {
      const newStateItem = state.getIn(['banks', '1', 'bankEntries'])
      expect(newStateItem.equals(fromJS([TEMPORARY_BANK_ENTRY_ID]))).to.be.true()
    })

    it('adds temporary bank entry associated with temporary item', () => {
      const nextStateItem = state.getIn(['bankEntries', TEMPORARY_BANK_ENTRY_ID, 'entry'])
      expect(nextStateItem).to.equal(TEMPORARY_ITEM_ID)
    })

    it('adds the temporary item with data', () => {
      const nextStateItem = state.getIn(['items', TEMPORARY_ITEM_ID, 'scoringData'])
      expect(nextStateItem).to.equal('someData')
    })
  })

  describe('clear temporary item', () => {
    const initialState = fromJS({
      quizzes: {1: {id: '1', quizEntries: [TEMPORARY_QUIZ_ENTRY_ID]}},
      quizEntries: {
        [TEMPORARY_QUIZ_ENTRY_ID]: {
          id: TEMPORARY_QUIZ_ENTRY_ID,
          entry: TEMPORARY_ITEM_ID
        }
      },
      items: {
        [TEMPORARY_ITEM_ID]: {
          id: TEMPORARY_ITEM_ID
        }
      }
    })

    const clearTempItemAction = {
      type: CLEAR_TEMPORARY_ITEM,
      payload: {quizId: '1'}
    }

    const nextState = modificationsReducer(initialState, clearTempItemAction)

    it('removes the association from the quiz', () => {
      const nextStateItem = nextState.getIn(['quizzes', '1', 'quizEntries'])
      expect(nextStateItem).to.be.empty()
    })

    it('clears the quiz entry', () => {
      expect(nextState.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID])).to.not.exist()
    })

    it('clears the item', () => {
      expect(nextState.getIn(['items', TEMPORARY_ITEM_ID])).to.not.exist()
    })
  })

  describe('clear temporary bank item', () => {
    const initialState = fromJS({
      banks: {1: {id: '1', bankEntries: [TEMPORARY_BANK_ENTRY_ID]}},
      bankEntries: {
        [TEMPORARY_BANK_ENTRY_ID]: {
          id: TEMPORARY_BANK_ENTRY_ID,
          entry: TEMPORARY_ITEM_ID
        }
      },
      items: {
        [TEMPORARY_ITEM_ID]: {
          id: TEMPORARY_ITEM_ID
        }
      }
    })

    const clearTempItemAction = {
      type: CLEAR_TEMPORARY_BANK_ITEM,
      payload: {bankId: '1'}
    }

    const nextState = modificationsReducer(initialState, clearTempItemAction)

    it('removes the association from the bank', () => {
      const nextStateItem = nextState.getIn(['banks', '1', 'bankEntries'])
      expect(nextStateItem).to.be.empty()
    })

    it('clears the bank entry', () => {
      expect(nextState.getIn(['bankEntries', TEMPORARY_BANK_ENTRY_ID])).to.not.exist()
    })

    it('clears the item', () => {
      expect(nextState.getIn(['items', TEMPORARY_ITEM_ID])).to.not.exist()
    })
  })

  describe('clear modifications', () => {
    it('clears the modifications on the object', () => {
      const initialState = fromJS({
        testType: {1: {id: '1', testAttr: true}}
      })

      const clearTempItemAction = {
        type: CLEAR_MODIFICATIONS,
        payload: {objectType: 'testType', objectId: '1', deepKeys: []}
      }

      const nextState = modificationsReducer(initialState, clearTempItemAction)
      const nextItemState = nextState.get('testType')
      expect(nextItemState.equals(fromJS({}))).to.be.true()
    })

    it('clears a specific property using deepKeys', () => {
      const initialState = fromJS({
        testType: {1: {id: '1', testAttr: true}}
      })

      const clearTempItemAction = {
        type: CLEAR_MODIFICATIONS,
        payload: {objectType: 'testType', objectId: '1', deepKeys: ['testAttr']}
      }

      const nextState = modificationsReducer(initialState, clearTempItemAction)
      const nextItemState = nextState.getIn(['testType', '1'])
      expect(nextItemState.equals(fromJS({id: '1'}))).to.be.true()
    })
  })

  describe('move temporary item', () => {
    const initialState = fromJS({
      quizEntries: {
        [TEMPORARY_QUIZ_ENTRY_ID]: {id: TEMPORARY_QUIZ_ENTRY_ID, entry: TEMPORARY_ITEM_ID, position: 2}
      }
    })

    const stateAfterAction = (moveAction) => {
      return modificationsReducer(initialState, moveAction)
    }

    it('increments position if direction is down', () => {
      const moveAction = {
        type: MOVE_TEMPORARY_ITEM,
        payload: {direction: 'down'}
      }
      const nextState = stateAfterAction(moveAction)
      const nextStateItem = nextState.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID, 'position'])
      expect(nextStateItem).to.equal(3)
    })

    it('decrements position if direction is up', () => {
      const moveAction = {
        type: MOVE_TEMPORARY_ITEM,
        payload: {direction: 'up'}
      }
      const nextState = stateAfterAction(moveAction)
      const nextStateItem = nextState.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID, 'position'])
      expect(nextStateItem).to.equal(1)
    })

    it('moves to item position to 0.1 less than given position if moving up', () => {
      const moveAction = {
        type: MOVE_TEMPORARY_ITEM,
        payload: {position: 20, movingDown: false}
      }
      const nextState = stateAfterAction(moveAction)
      const nextStateItem = nextState.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID, 'position'])
      expect(nextStateItem).to.equal(19.9)
    })

    it('moves to item position to 0.9 greater than given position if moving down', () => {
      const moveAction = {
        type: MOVE_TEMPORARY_ITEM,
        payload: {position: 20, movingDown: true}
      }
      const nextState = stateAfterAction(moveAction)
      const nextStateItem = nextState.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID, 'position'])
      expect(nextStateItem).to.equal(20.9)
    })

    it('sets the stimulusQuizEntryId', () => {
      const moveAction = {
        type: MOVE_TEMPORARY_ITEM,
        payload: {stimulusQuizEntryId: '5'}
      }
      const nextState = stateAfterAction(moveAction)
      const nextStateItem = nextState.getIn(['quizEntries', TEMPORARY_QUIZ_ENTRY_ID, 'stimulusQuizEntryId'])
      expect(nextStateItem).to.equal('5')
    })
  })
})
