import { camelizeKeys } from 'humps'

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

import { uploadStatus as setUploadStatus } from '../../../common/actions/upload'

import {
  FILE_UPLOAD_URL_CALL,
  CREATE_QTI_IMPORT,
  FILE_UPLOAD_SUCCESS,
  FILE_UPLOAD_FAILURE,
  PARENT_TYPE_QUIZ,
  PARENT_TYPE_BANK
} from '@instructure/quiz-common/lib/constants'

import {
  getQTIInfo,
  pollForQtiInfo,
  postQtiCreate,
  qtiFileUpload
} from '../qtiImports'

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

import {
  addQtiImports
} from '../../../common/actions/qtiImports'

describe('qti import api action creators', () => {
  const dispatchStub = sinon.stub()
  const getStateStub = sinon.stub()
  const fetchStub = sinon.stub()
  const quizId = '2'
  const bankId = '5'

  useFakeFetching({dispatchStub, getStateStub, fetchStub})

  describe('getQTIInfo', () => {
    const getQTIImportsThunk = getQTIInfo(quizId)

    it('gets the correct url', () => {
      getQTIImportsThunk(dispatchStub, getStateStub)
      fetchStub.calledWith(`/api/quizzes/${quizId}/qti_imports`)
    })

    it('tries to persist the response data to the store', (done) => {
      const qtiResponseData = [
        {id: 1, parent_id: 2, parent_type: 'Quiz', status: 'failed'},
        {id: 3, parent_id: 2, parent_type: 'Quiz', status: 'started'}
      ]
      const response = jsoniblePromise(qtiResponseData)
      Fetcher.fakeFetch = makeFakeFetch(response)

      const fetchPromise = getQTIImportsThunk(dispatchStub, getStateStub)
      const parsedImportData = [
        {id: 1, parentId: 2, parentType: 'Quiz', status: 'failed'},
        {id: 3, parentId: 2, parentType: 'Quiz', status: 'started'}
      ]

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

  describe('pollForQtiInfo', () => {
    let clock = null
    beforeEach(() => {
      clock = sinon.useFakeTimers()
    })

    afterEach(() => {
      clock.restore()
      getQuizAndItemsStub.resetHistory()
    })

    const getQuizAndItemsStub = sinon.stub()
    const getBankAndEntriesStub = sinon.stub()
    const pollingStub = sinon.stub()
    const pollForQtiInfoThunk = pollForQtiInfo(quizId, PARENT_TYPE_QUIZ, {
      getExistingQuizAndItems: getQuizAndItemsStub,
      pollForQtiInfo: pollingStub
    })

    const pollForBankQtiInfoThunk = pollForQtiInfo(bankId, PARENT_TYPE_BANK, {
      getExistingBankAndEntries: getBankAndEntriesStub,
      pollForQtiInfo: pollingStub
    })

    it('gets the correct url', () => {
      pollForQtiInfoThunk(dispatchStub, getStateStub)
      fetchStub.calledWith(`/api/quizzes/${quizId}/qti_imports`)
    })

    it('gets the correct url for bank', () => {
      pollForBankQtiInfoThunk(dispatchStub, getStateStub)
      fetchStub.calledWith(`/api/banks/${bankId}/qti_imports`)
    })

    it('persists the response data to the store and triggers the next loop', (done) => {
      const qtiResponseData = [
        {id: 1, parent_id: 2, parent_type: 'Quiz', status: 'scheduled'},
        {id: 3, parent_id: 2, parent_type: 'Quiz', status: 'importing'}
      ]
      const response = jsoniblePromise(qtiResponseData)
      Fetcher.fakeFetch = makeFakeFetch(response)

      const fetchPromise = pollForQtiInfoThunk(dispatchStub, getStateStub)
      clock.tick(2500)
      fetchPromise.then(() => {
        checkActionDispatched(dispatchStub, addQtiImports(camelizeKeys(qtiResponseData)))
        expect(pollingStub.called).to.be.true()
        done()
      })
    })

    context('on import success', () => {
      it('persists the response data to the store and triggers the next quiz reload when done', (done) => {
        const qtiResponseData = [
          {id: 1, parent_id: 2, parent_type: 'Quiz', status: 'scheduled'},
          {id: 3, parent_id: 2, parent_type: 'Quiz', status: 'completed'}
        ]

        const response = jsoniblePromise(qtiResponseData)
        Fetcher.fakeFetch = makeFakeFetch(response)

        const fetchPromise = pollForQtiInfoThunk(dispatchStub, getStateStub)
        clock.tick(2500)
        fetchPromise.then(() => {
          checkActionDispatched(dispatchStub, addQtiImports(camelizeKeys(qtiResponseData)))
          checkActionDispatched(dispatchStub, setUploadStatus(FILE_UPLOAD_SUCCESS))
          expect(getQuizAndItemsStub.called).to.be.true()
          done()
        })
      })

      context('on bank import success', () => {
        it('persists the response data to the store and triggers the next bank reload when done', (done) => {
          const qtiResponseData = [
            {id: 1, parent_id: 2, parent_type: 'Bank', status: 'scheduled'},
            {id: 3, parent_id: 2, parent_type: 'Bank', status: 'completed'}
          ]

          const response = jsoniblePromise(qtiResponseData)
          Fetcher.fakeFetch = makeFakeFetch(response)

          const fetchPromise = pollForBankQtiInfoThunk(dispatchStub, getStateStub)
          clock.tick(2500)
          fetchPromise.then(() => {
            checkActionDispatched(dispatchStub, addQtiImports(camelizeKeys(qtiResponseData)))
            checkActionDispatched(dispatchStub, setUploadStatus(FILE_UPLOAD_SUCCESS))
            expect(getBankAndEntriesStub.called).to.be.true()
            done()
          })
        })
      })
    })

    context('on import failure', () => {
      it('persists the response data to the store and stops loop', (done) => {
        const qtiResponseData = [
          {id: 1, parent_id: 2, parent_type: 'Quiz', status: 'failed'}
        ]

        const response = jsoniblePromise(qtiResponseData)
        Fetcher.fakeFetch = makeFakeFetch(response)

        const fetchPromise = pollForQtiInfoThunk(dispatchStub, getStateStub)
        clock.tick(2500)
        fetchPromise.then(() => {
          checkActionDispatched(dispatchStub, addQtiImports(camelizeKeys(qtiResponseData)))
          checkActionDispatched(dispatchStub, setUploadStatus(FILE_UPLOAD_FAILURE))
          expect(getQuizAndItemsStub.called).to.not.be.true()
          done()
        })
      })

      it('will not block future imports from polling', (done) => {
        const qtiResponseData = [
          {id: 1, parent_id: 2, parent_type: 'Quiz', status: 'failed'},
          {id: 3, parent_id: 2, parent_type: 'Quiz', status: 'importing'}
        ]

        const response = jsoniblePromise(qtiResponseData)
        Fetcher.fakeFetch = makeFakeFetch(response)

        const fetchPromise = pollForBankQtiInfoThunk(dispatchStub, getStateStub)
        clock.tick(2500)
        fetchPromise.then(() => {
          checkActionDispatched(dispatchStub, addQtiImports(camelizeKeys(qtiResponseData)))
          expect(pollingStub.called).to.be.true()
          done()
        })
      })
    })
  })

  describe('upload actions', () => {
    const quizId = '22'
    const bankId = '32'
    const fakeS3Url = 'http://s3/fake/url'
    const uploadUrlResponse = { url: fakeS3Url }
    const file = { type: 'filetype', name: 'BLOB' }

    const errorResponse = jsoniblePromise({status: 500})
    const fakeErrorFetch = makeFakeFetch(errorResponse)
    const emptyResponse = jsoniblePromise({})
    const fakeEmptyFetch = makeFakeFetch(emptyResponse)

    const onErrorStub = sinon.stub()
    const s3UploadFetchStub = sinon.stub()
    const postQtiCreateStub = sinon.stub()

    const dispatchStub = sinon.stub()
    const getStateStub = sinon.stub()
    const fetchStub = sinon.stub()
    useFakeFetching({dispatchStub, getStateStub, fetchStub})

    afterEach(() => {
      onErrorStub.resetHistory()
      s3UploadFetchStub.resetHistory()
      postQtiCreateStub.resetHistory()
    })

    describe('postQtiCreate', () => {
      const fakePostURL = 'api/quizzes/22/qti_imports'
      const stubbedPostQtiCreateCall = () => {
        const postQtiCreateWithUrl = postQtiCreate(fakePostURL)
        const postQtiCreateThunk = postQtiCreateWithUrl(fakeS3Url, dispatchStub, getStateStub)
        return postQtiCreateThunk
      }

      const fakePostBankURL = 'api/banks/32/qti_imports'
      const stubbedPostQtiCreateBankCall = () => {
        const postQtiCreateWithUrl = postQtiCreate(fakePostBankURL)
        const postQtiCreateThunk = postQtiCreateWithUrl(fakeS3Url, dispatchStub, getStateStub)
        return postQtiCreateThunk
      }

      it('should mark the file upload url fetch call as started', () => {
        stubbedPostQtiCreateCall()

        checkActionDispatched(dispatchStub, startCallFor(CREATE_QTI_IMPORT))
      })

      it('should upload to the provided url', () => {
        stubbedPostQtiCreateCall()

        expect(
          fetchStub.calledWith(fakePostURL)
        ).to.be.true()
      })

      it('should upload to the provided url for bank', () => {
        stubbedPostQtiCreateBankCall()

        expect(
          fetchStub.calledWith(fakePostBankURL)
        ).to.be.true()
      })

      const fakePostQtiCreate = () => {
        Fetcher.fakeFetch = fakeEmptyFetch
        const postQtiCreateWithUrl = postQtiCreate(fakePostURL, dispatchStub)
        const postQtiCreateThunk = postQtiCreateWithUrl(fakeS3Url, dispatchStub, getStateStub)
        return postQtiCreateThunk
      }

      it('should mark the create call as a success on return', (done) => {
        fakePostQtiCreate().then(() => {
          checkActionDispatched(dispatchStub, successCallFor(CREATE_QTI_IMPORT))
          done()
        })
      })

      context('error on creating the qti import', () => {
        beforeEach(() => {
          Fetcher.fakeFetch = fakeErrorFetch
        })

        it('should call the onError callback', (done) => {
          fakePostQtiCreate().then(done())

          expect(onErrorStub.called).to.be.true()
        })
      })
    })

    describe('qtiFileUpload', () => {
      const fakeUploadFile = () => {
        const uploadFileThunk = qtiFileUpload(
          file, quizId, PARENT_TYPE_QUIZ, onErrorStub, s3UploadFetchStub, postQtiCreateStub
        )
        return uploadFileThunk(dispatchStub, getStateStub)
      }

      const fakeUploadBankFile = () => {
        const uploadFileThunk = qtiFileUpload(
          file, bankId, PARENT_TYPE_BANK, onErrorStub, s3UploadFetchStub, postQtiCreateStub
        )
        return uploadFileThunk(dispatchStub, getStateStub)
      }

      it('should mark the file upload url fetch call as started', () => {
        fakeUploadFile()

        checkActionDispatched(dispatchStub, startCallFor(FILE_UPLOAD_URL_CALL))
      })

      it('should hit the right upload_url endpoint', () => {
        fakeUploadFile()

        expect(
          fetchStub.calledWith('/api/quizzes/22/qti_imports/upload_url')
        ).to.be.true()
      })

      it('should hit the right upload_url endpoint for bank', () => {
        fakeUploadBankFile()

        expect(
          fetchStub.calledWith('/api/banks/32/qti_imports/upload_url')
        ).to.be.true()
      })

      it('should mark the create call as a success on return', (done) => {
        const response = jsoniblePromise(uploadUrlResponse)
        const fakeUrlFetch = makeFakeFetch(response)
        Fetcher.fakeFetch = fakeUrlFetch

        fakeUploadFile().then(() => {
          checkActionDispatched(dispatchStub, successCallFor(FILE_UPLOAD_URL_CALL))
          done()
        })
      })

      it('passes the error callback to fileUploadFxn', () => {
        fakeUploadFile()

        expect(
          s3UploadFetchStub.getCall(0).args[2]
        ).to.eq(onErrorStub)
      })

      it('passes the error callback to qtiCreateFxn', () => {
        fakeUploadFile()

        expect(
          postQtiCreateStub.getCall(0).args[2]
        ).to.eq(onErrorStub)
      })

      context('error on getting the qti import upload url', () => {
        beforeEach(() => {
          Fetcher.fakeFetch = fakeErrorFetch
        })

        it('should call the onError callback', (done) => {
          fakeUploadFile().then(done())

          expect(onErrorStub.called).to.be.true()
        })
      })
    })
  })
})
