import { expect } from 'chai'
import InteractionType from 'quiz-interactions/lib/records/InteractionType'

import {
  ADD_ALERT,
  CREATE_ITEM_CALL,
  UPDATE_ITEM_CALL
} from '@instructure/quiz-common/lib/constants'

import Fetcher from '../../../common/util/Fetcher'

import {
  startCallFor,
  successCallFor
} from '../../../common/util/callHelpers'

import {
  createItem,
  getItem,
  updateItem
} from '../items'

import {
  addItems
} from '../../../common/actions/items'

import Item from '../../../common/records/Item'

import makeFakeFetch from '../../../../tests/util/fakeFetch'
import {jsoniblePromise} from '../../../common/util/jsonify'

describe('items api actions creators', () => {
  const dispatchStub = sinon.stub()
  const getStateStub = sinon.stub()
  const fetchStub = sinon.stub()

  useFakeFetching({dispatchStub, getStateStub, fetchStub})

  const quizId = '22'
  const position = 3
  const item = new Item({
    id: '22',
    itemBody: 'itemBody',
    title: 'the title',
    position: position,
    properties: {},
    scoringData: 'answer',
    interactionType: 3,
    outcomeAlignmentSetGuid: 'ce39d262-b8f6-4650-91c1-5a76b2c18f70',
    interactionData: 'foo',
    stimulusId: '4',
    calculatorType: 'basic'
  })

  describe('getItem', () => {
    const itemId = '1'

    const stubbedGetItemCall = () => {
      const getItemThunk = getItem(itemId)
      getItemThunk(dispatchStub, getStateStub)
    }

    it('should hit the right endpoint', () => {
      stubbedGetItemCall()
      expect(
        fetchStub.calledWith(`/api/items/${itemId}`)
      ).to.be.true()
    })
  })

  describe('createItem', () => {
    afterEach(() => {
      item.getInteractionType.restore && item.getInteractionType.restore()
    })

    const quizEntry = {position: 8, pointsPossible: 18}

    const stubbedCreateItemCall = (qiData = quizEntry) => {
      const interactionType = new InteractionType({
        scoringAlgorithmOptions: ['string'],
        userResponseTypeOptions: ['string'],
        scoringAlgorithmDefault: 'string'
      })
      sinon.stub(item, 'getInteractionType').returns(interactionType)
      Fetcher.fakeFetch = fetchStub
      const createItemThunk = createItem(item, '3', 'quiz', 1)
      createItemThunk(dispatchStub, getStateStub)
    }

    it('should mark the create item call as started', () => {
      const createCallAction = startCallFor(CREATE_ITEM_CALL)
      stubbedCreateItemCall()
      checkActionDispatched(dispatchStub, createCallAction)
    })

    it('should hit the right endpoint', () => {
      stubbedCreateItemCall()
      expect(
        fetchStub.calledWith('/api/quizzes/3/items')
      ).to.be.true()
    })

    it('should serialize data properly', () => {
      stubbedCreateItemCall()
      const expectedData = JSON.stringify({
        item: {
          feedback: {},
          interaction_data: 'foo',
          interaction_type_id: 3,
          item_body: 'itemBody',
          outcome_alignment_set_guid: 'ce39d262-b8f6-4650-91c1-5a76b2c18f70',
          parent_item_id: 1,
          properties: {},
          scoring_algorithm: 'string',
          scoring_data: 'answer',
          title: 'the title',
          user_response_type: 'string',
          stimulus_id: '4',
          calculator_type: 'basic'
        }
      })

      expect(
        fetchStub.getCall(0).args[1].body
      ).to.be.eql(expectedData)
    })

    const fakeGetQuizEntryActionCreator = () => {
      return {type: 'FAKE_GET_QUIZ_ENTRY'}
    }

    const fakeCreateItem = () => {
      const response = jsoniblePromise(item.toJS())
      const fakeFetch = makeFakeFetch(response)
      const interactionType = new InteractionType({
        scoringAlgorithmOptions: ['string'],
        userResponseTypeOptions: ['string']
      })
      sinon.stub(item, 'getInteractionType').returns(interactionType)

      Fetcher.fakeFetch = fakeFetch
      const createItemThunk = createItem(
        item,
        {position: 8},
        {getQuizEntriesActionCreator: fakeGetQuizEntryActionCreator}
      )
      return createItemThunk(dispatchStub, getStateStub)
    }

    it('should mark the create call as a success on return', (done) => {
      const successfulCreate = successCallFor(CREATE_ITEM_CALL)
      const fetchPromise = fakeCreateItem()
      fetchPromise.then(() => {
        checkActionDispatched(dispatchStub, successfulCreate)
        done()
      })
    })

    it('should try to add the item to the store', (done) => {
      const fetchPromise = fakeCreateItem()
      const handleItemsAction = addItems([item.toJS()])
      fetchPromise.then(() => {
        checkActionDispatched(dispatchStub, handleItemsAction)
        done()
      })
    })
  })

  describe('updateItem', () => {
    const itemData = {
      id: '2',
      itemBody: 'new body',
      title: 'a new title',
      scoringData: '22',
      scoringAlgorithm: 'UpdatedAlgo',
      interactionData: {},
      properties: {},
      outcomeAlignmentSetGuid: '0281acd1-431a-4c94-a9cd-ba3429b887e6',
      calculatorType: 'basic'
    }

    const stubbedUpdateItemCall = () => {
      Fetcher.fakeFetch = fetchStub
      const item = new Item(itemData)
      const updateItemThunk = updateItem(item, quizId)
      updateItemThunk(dispatchStub, getStateStub)
    }

    it('should hit the right endpoint', () => {
      stubbedUpdateItemCall()
      expect(
        fetchStub.calledWith(`/api/quizzes/${quizId}/items/2`)
      ).to.be.true()
    })

    it('should mark the update item call as started', () => {
      stubbedUpdateItemCall()
      const updateCallStartAction = startCallFor(UPDATE_ITEM_CALL)
      checkActionDispatched(dispatchStub, updateCallStartAction)
    })

    it('should serialize data properly', () => {
      stubbedUpdateItemCall()
      const expectedData = JSON.stringify({
        item: {
          item_body: 'new body',
          title: 'a new title',
          scoring_data: '22',
          scoring_algorithm: 'UpdatedAlgo',
          interaction_data: {},
          properties: {},
          outcome_alignment_set_guid: '0281acd1-431a-4c94-a9cd-ba3429b887e6',
          feedback: {},
          calculator_type: 'basic'
        }
      })

      expect(
        fetchStub.getCall(0).args[1].body
      ).to.be.eql(expectedData)
    })

    const fakeUpdateItem = () => {
      const response = jsoniblePromise(itemData)
      const fakeFetch = makeFakeFetch(response)

      Fetcher.fakeFetch = fakeFetch
      const item = new Item(itemData)
      const updateItemThunk = updateItem(item, quizId)
      return updateItemThunk(dispatchStub, getStateStub)
    }

    it('should mark the update call as a success on return', (done) => {
      const fetchPromise = fakeUpdateItem()
      const successfulUpdate = successCallFor(UPDATE_ITEM_CALL)
      fetchPromise.then(() => {
        checkActionDispatched(dispatchStub, successfulUpdate)
        done()
      })
    })

    it('should try to update the item in the store', (done) => {
      const fetchPromise = fakeUpdateItem()
      const handleItemAction = addItems([itemData])
      fetchPromise.then(() => {
        checkActionDispatched(dispatchStub, handleItemAction)
        done()
      })
    })

    context('when a student has already started answering the question', () => {
      beforeEach(() => {
        const body = { errors: { base: ['cannot update an immutable item'] } }
        const blob = new Blob([JSON.stringify(body)], { type: 'application/json' })
        const response = new Response(blob, {
          status: '422',
          statusText: 'Unprocessable Entity'
        })
        fetchStub.resolves(response)
      })

      it('displays an alert', async () => {
        await stubbedUpdateItemCall()

        // TODO: use redux-mock-store everywhere so we don't have to do this dance
        const thunks = dispatchStub.getCalls().map(call => call.args[0]).filter(arg => typeof arg === 'function')
        for (const thunk of thunks) {
          await thunk(dispatchStub, getStateStub)
        }

        expect(dispatchStub).to.have.been.calledWithMatch({
          type: ADD_ALERT,
          payload: {
            message: sinon.match('item has now been viewed by a student'),
            variant: 'error'
          }
        })
      })
    })
  })
})
