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

import {
  DO_NOTHING,
  QUIZ_SAVE_CALL,
  PARENT_TYPE_QUIZ,
  PARENT_TYPE_BANK
} from '@instructure/quiz-common/lib/constants'

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

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

import { addQuizzes, newActiveQuiz } from '../../../common/actions/quizzes'

import {
  interactionFileUpload,
  getExistingQuizAndItemsForBuilding,
  quizBuildingSession,
  saveQuizChange,
  setCalculatorType,
  setBacktracking,
  setOneAtATimeType,
  setQuizHasTimeLimit,
  setQuizShuffleQuestions,
  setSessionTimeLimitInSecondsApi,
  setSessionTimeLimitInSecondsUi,
  setFilterIpAddress,
  setIpFiltersApi,
  setIpFiltersUi,
  startNewQuiz
} from '../quizzes'

import makeFakeFetch from '../../../../tests/util/fakeFetch'

import { jsoniblePromise } from '../../../common/util/jsonify'
import checkActionDispatched from '../../../../tests/util/checkActionDispatched'
import { httpHeaders } from '../../../common/util/httpHeaders'

describe('quizzes api action creators', () => {
  const fetchStub = sinon.stub().returns(new Promise((resolve) => resolve))
  const dispatchStub = sinon.stub()
  const getStateStub = sinon.stub()

  useFakeFetching({dispatchStub, getStateStub, fetchStub})
  const fakeRecord = (data) => {
    const r = fromJS(data)
    r.validData = () => (r)
    return r
  }

  describe('saveQuizChange', () => {
    it('does nothing if workingQuiz is the same as quiz', () => {
      const sameThing = fakeRecord({})
      const saveQuizAction = saveQuizChange(sameThing, sameThing)
      expect(saveQuizAction.type).to.be.equal(DO_NOTHING)
    })

    const stubbedQuizModCall = (quizId) => {
      const quiz = fakeRecord({id: quizId})
      const quizMod = fakeRecord({id: quizId, title: 'title mod'})

      const saveQuizThunk = saveQuizChange(quiz, quizMod)
      saveQuizThunk(dispatchStub, getStateStub)
    }

    it('marks that the quiz is saving', () => {
      stubbedQuizModCall('12')
      const savingAction = startCallFor(QUIZ_SAVE_CALL)
      checkActionDispatched(dispatchStub, savingAction)
    })

    it('puts to the right endpoint', () => {
      stubbedQuizModCall('22')
      expect(
        fetchStub.calledWith('/api/quizzes/22')
      ).to.be.true()
    })

    const fakeQuizModCall = (mockQuizMods) => {
      const quiz = fakeRecord({id: '22'})
      const quizMod = fakeRecord(mockQuizMods)
      const response = jsoniblePromise(mockQuizMods)
      const fakeFetch = makeFakeFetch(response)

      Fetcher.fakeFetch = fakeFetch
      const saveQuizThunk = saveQuizChange(quiz, quizMod)
      const fetchPromise = saveQuizThunk(dispatchStub, getStateStub)
      return fetchPromise
    }

    it('marks that the quiz saving as done in the cb', (done) => {
      const mockQuizMods = {id: '22', title: 'title mod'}
      const fetchPromise = fakeQuizModCall(mockQuizMods)
      const endSaveAction = successCallFor(QUIZ_SAVE_CALL)
      fetchPromise.then(() => {
        checkActionDispatched(dispatchStub, endSaveAction)
        done()
      })
    })

    it('tries to persist the change in the cb with addQuizzes action', (done) => {
      const mockQuizMods = {id: '22', title: 'title mod'}
      const fetchPromise = fakeQuizModCall(mockQuizMods)

      const expectedAction = addQuizzes([mockQuizMods])
      fetchPromise.then(() => {
        checkActionDispatched(dispatchStub, expectedAction)
        done()
      })
    })
  })

  describe('startNewQuiz', () => {
    const stubbedNewQuizCall = () => {
      const startNewQuizThunk = startNewQuiz()
      startNewQuizThunk(dispatchStub, getStateStub)
    }

    it('posts to the right endpoint', () => {
      stubbedNewQuizCall()
      expect(
        fetchStub.calledWith('/api/quizzes')
      ).to.be.true()
    })

    const fakeNewQuizCall = (quizInfo) => {
      const response = jsoniblePromise(quizInfo)
      const fakeFetch = makeFakeFetch(response)

      Fetcher.fakeFetch = fakeFetch
      const newQuizThunk = startNewQuiz()
      const fetchPromise = newQuizThunk(dispatchStub, getStateStub)
      return fetchPromise
    }

    it('adds a new active quiz id', (done) => {
      const quizInfo = {id: '28'}
      const fetchPromise = fakeNewQuizCall(quizInfo)
      const newActiveQuizAction = newActiveQuiz('28')

      fetchPromise.then(() => {
        checkActionDispatched(dispatchStub, newActiveQuizAction)
        done()
      })
    })
  })

  describe('getExistingQuizAndItemsForBuilding', () => {
    const stubbedGetQuizCall = (quizId = '8') => {
      const getExistingQuizThunk = getExistingQuizAndItemsForBuilding(quizId)
      getExistingQuizThunk(dispatchStub, getStateStub)
    }

    it('gets from the right endpoint with the correct httpHeaders and method', () => {
      const getParams = {
        method: 'GET',
        headers: httpHeaders('fakeusertoken')
      }
      stubbedGetQuizCall('10')

      expect(
        fetchStub.calledWith('/api/quizzes/10', getParams)
      ).to.be.true()
    })

    const fakeGetQuizCall = (quizInfo) => {
      const response = jsoniblePromise(quizInfo || {id: '22'})
      const fakeFetch = makeFakeFetch(response)

      Fetcher.fakeFetch = fakeFetch
      const getExistingQuizThunk = getExistingQuizAndItemsForBuilding('22')
      const fetchPromise = getExistingQuizThunk(dispatchStub, getStateStub)
      return fetchPromise
    }

    context('with qti import', () => {
      it('calls a qti_import retrieval', (done) => {
        const quizInfo = {id: '28'}
        const fetchPromise = fakeGetQuizCall(quizInfo)

        fetchPromise.then(() => {
          fetchStub.calledWith('/api/quizzes/28/qti_imports')
          done()
        })
      })
    })
  })

  describe('quizBuildingSession', () => {
    it('fetches the quiz', () => {
      const getStateStub = sinon.stub().returns(fromJS({
        quizzes: {activeQuizId: '1', 28: {id: '28'}}
      }))
      const routerParams = {
        contextId: '2',
        activeQuizId: '28'
      }
      quizBuildingSession(routerParams)(dispatchStub, getStateStub)
      const fetchQuizAction = dispatchStub.firstCall.args[0]
      expect(fetchQuizAction).to.be.a('function')

      fetchQuizAction(dispatchStub, getStateStub)
      expect(fetchStub.calledWith('/api/quizzes/28')).to.be.true()
    })

    it('starts new quiz if needed', () => {
      const getStateStub = sinon.stub().returns(fromJS({
        quizzes: {activeQuizId: '28', 28: {id: '28'}}
      }))
      const routerParams = {
        contextId: '2',
        createNew: true
      }
      quizBuildingSession(routerParams)(dispatchStub, getStateStub)
      const newQuizAction = dispatchStub.firstCall.args[0]
      expect(newQuizAction).to.be.a('function')

      newQuizAction(dispatchStub, getStateStub)
      expect(fetchStub.calledWith('/api/quizzes')).to.be.true()
    })

    it('does nothing if quiz and questions are loaded', () => {
      const getStateStub = sinon.stub().returns(fromJS({
        quizzes: {activeQuizId: '3', 28: {id: '28'}},
        quizEntries: { 1: {}, 2: {}}
      }))
      const routerParams = {
        contextId: '2',
        createNew: true,
        activeQuizId: '3'
      }
      const noopAction = quizBuildingSession(routerParams)(dispatchStub, getStateStub)
      expect(noopAction).to.deep.equal({ type: DO_NOTHING })
    })
  })

  const setUpSetQuizPropertyAssertion = (quizSetPropertyFunc) => {
    const getStateStub = sinon.stub().returns(fromJS(
      {quizzes: {}}
    ))
    useFakeFetching({dispatchStub, getStateStub, fetchStub})

    const quizId = '123'
    const stubbedCall = (value) => {
      Fetcher.fakeFetch = fetchStub
      quizSetPropertyFunc(quizId, value)(dispatchStub, getStateStub)
    }
    return (value, expectedPayload) => {
      stubbedCall(value)
      expect(
        fetchStub.calledWithMatch(`/api/quizzes/${quizId}`, {
          method: 'PATCH',
          body: JSON.stringify({
            id: quizId,
            quiz: expectedPayload
          })
        })
      ).to.be.true()
    }
  }

  describe('#setBacktracking', () => {
    const assertBacktracking = setUpSetQuizPropertyAssertion(setBacktracking)
    it('should send allow_backtracking with truthy value', () => {
      assertBacktracking(1, { allow_backtracking: true })
    })
  })

  describe('#setCalculatorType', () => {
    const assertCalculatorType = setUpSetQuizPropertyAssertion(setCalculatorType)
    it('should send calculator_type with value \'none\'', () => {
      assertCalculatorType('none', { calculator_type: 'none' })
    })
  })

  describe('#setOneAtATimeType', () => {
    const assertOQAAT = setUpSetQuizPropertyAssertion(setOneAtATimeType)
    it('should send one_at_a_time_type with value \'question\' if passed truthy value', () => {
      assertOQAAT(true, { one_at_a_time_type: 'question' })
    })

    it('should send one_at_a_time_type null if passed falsey value', () => {
      assertOQAAT(false, { one_at_a_time_type: null })
    })
  })

  describe('#setQuizShuffleQuestions', () => {
    const setQuizShuffleQuestionsAssertion = setUpSetQuizPropertyAssertion(setQuizShuffleQuestions)

    it('should send shuffle_questions with truthy value', () => {
      setQuizShuffleQuestionsAssertion(1, { shuffle_questions: true })
    })

    it('should send shuffle_questions with falsy value', () => {
      setQuizShuffleQuestionsAssertion(false, { shuffle_questions: false })
    })
  })

  describe('#setQuizHasTimeLimit', () => {
    const setQuizHasTimeLimitAssertion = setUpSetQuizPropertyAssertion(setQuizHasTimeLimit)

    it('should send has_time_limit with truthy value', () => {
      setQuizHasTimeLimitAssertion(1, { has_time_limit: true })
    })

    it('should send has_time_limit with falsy value', () => {
      setQuizHasTimeLimitAssertion(false, { has_time_limit: false })
    })
  })

  describe('#setSessionTimeLimitInSecondsApi', () => {
    const setSessionTimeLimitAssertion = setUpSetQuizPropertyAssertion(setSessionTimeLimitInSecondsApi)

    it('should send session_time_limit_in_seconds with integer value', () => {
      setSessionTimeLimitAssertion(1, { session_time_limit_in_seconds: 1 })
    })
  })

  describe('#setSessionTimeLimitInSecondsUi', () => {
    it('dispatches the correct action and payload', () => {
      const thunk = setSessionTimeLimitInSecondsUi(1, 3600)
      thunk(dispatchStub)
      expect(dispatchStub.firstCall.args[0]).to.deep.eq(
        {
          payload: {
            id: 1,
            modifications: {
              sessionTimeLimitInSeconds: 3600
            }
          },
          type: 'QUIZ_MODIFICATION'
        }
      )
    })
  })

  describe('#interactionFileUpload', () => {
    const parentId = '22'
    const onSuccess = () => {}
    const onError = () => {}
    const withUrlCallback = sinon.stub()

    const fakeInteractionUpload = (parentType) => {
      const uploadFileThunk = interactionFileUpload(
        parentType,
        parentId,
        'some file',
        onSuccess,
        onError,
        null,
        withUrlCallback
      )

      return uploadFileThunk(dispatchStub, getStateStub)
    }

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

    it('calls to the banks url if parentType is bank', () => {
      fakeInteractionUpload(PARENT_TYPE_BANK)

      expect(
        fetchStub.calledWith('/api/banks/22/items/media_upload_url')
      ).to.be.true()
    })

    it('calls to the quizzes url if parentType is quiz', () => {
      fakeInteractionUpload(PARENT_TYPE_QUIZ)

      expect(
        fetchStub.calledWith('/api/quizzes/22/items/media_upload_url')
      ).to.be.true()
    })
  })

  describe('#setFilterIpAddress', () => {
    const assertFilterIpAddress = setUpSetQuizPropertyAssertion(setFilterIpAddress)
    it('should send filter_ip_address to false', () => {
      assertFilterIpAddress(false, { filter_ip_address: false })
    })

    it('should send filter_ip_address to true', () => {
      assertFilterIpAddress(true, { filter_ip_address: true })
    })
  })

  describe('#setIpFiltersApi', () => {
    const assertSetIpFiltersApi = setUpSetQuizPropertyAssertion(setIpFiltersApi)
    it('should set filters[ips] to an empty array', () => {
      assertSetIpFiltersApi(
        [],
        { filters: { ips: [] } }
      )
    })

    it('should set filters[ips] to an array with empty strings', () => {
      assertSetIpFiltersApi(
        [['', '']],
        { filters: { ips: [['', '']] } }
      )
    })

    it('should set filters[ips] to an array with valid ips', () => {
      assertSetIpFiltersApi(
        [['1.1.1.1', '9.9.9.9']],
        { filters: { ips: [['1.1.1.1', '9.9.9.9']] } }
      )
    })

    it('should set filters[ips] to an array with many valid ips', () => {
      assertSetIpFiltersApi(
        [['1.1.1.1', '9.9.9.9'], ['100.100.100.100', '200.200.200.200']],
        { filters: { ips: [['1.1.1.1', '9.9.9.9'], ['100.100.100.100', '200.200.200.200']] } }
      )
    })
  })

  describe('#setIpFiltersUi', () => {
    it('dispatches the correct action and payload', () => {
      const thunk = setIpFiltersUi(1, [['0.0.0.0', '255.255.255.255']])
      thunk(dispatchStub)
      expect(dispatchStub.firstCall.args[0]).to.deep.eq({
        type: 'QUIZ_MODIFICATION',
        payload: {
          id: 1,
          modifications: {
            filters: { ips: [['0.0.0.0', '255.255.255.255']] }
          }
        }
      })
    })
  })
})
