import Fetcher from '../../../common/util/Fetcher'
import makeFakeFetch from '../../../../tests/util/fakeFetch'
import { jsoniblePromise } from '../../../common/util/jsonify'
import checkActionDispatched, { checkActionNotDispatched } from '../../../../tests/util/checkActionDispatched'
import { BANKS_BANK_PAGINATION, CAN_EDIT, REMOVED_ACCESS } from '@instructure/quiz-common/lib/constants'

import { handleBankResponse, handleBanksResponse, handleShareBankResponse, handleShareBanksResponse } from '../../../common/api/callHandlers'
import { clear, newActiveBank, newActiveBankEntry } from '../../../common/actions/ui'
import { archiveBank } from '../../../common/actions/banks'

import { getBanks, getBank, createBank, updateBank, deleteBank, shareBank, getSharedBanks, updateSharedBankPermission, getAllBanks } from '../banks'

describe('banks api actions', () => {
  const bankData = { id: '1', title: 'Name', owner: '1' }
  const sharedBanksData = [
    { id: '1', user_uuid: '1', bank_id: '1', permission: CAN_EDIT },
    { id: '2', user_uuid: '2', bank_id: '1', permission: CAN_EDIT }
  ]
  const getUserInfoData = [
    { id: '1', uuid: '1', fullName: 'firstFullName', avatar: 'http://avatars.com/avatar1' },
    { id: '2', uuid: '2', fullName: 'secondFullName', avatar: 'http://avatars.com/avatar2' }
  ]
  const sharedBanksDataMerged = [
    { id: '1', userUuid: '1', bankId: '1', permission: CAN_EDIT, fullName: 'firstFullName', avatar: 'http://avatars.com/avatar1' },
    { id: '2', userUuid: '2', bankId: '1', permission: CAN_EDIT, fullName: 'secondFullName', avatar: 'http://avatars.com/avatar2' }
  ]
  const banksData = [bankData]
  const pagedBanksData = [banksData, [{ id: '2', title: 'Foo', owner: '1' }]]
  const dispatchStub = sinon.stub().resolves(bankData)
  const getUserInfoStub = sinon.stub().resolves(getUserInfoData)
  const fetchStub = sinon.stub()
  const dispatchStubIgnore = sinon.stub()
  const getStateStub = sinon.stub()
  const fakeState = {
    ui: { activeBankId: '5' },
    quizzes: { activeQuizId: '7' }
  }

  useFakeFetching({dispatchStub, getStateStub, fetchStub, fakeState})

  describe('getBanks', () => {
    const stubbedGetBanksCall = () => {
      const response = jsoniblePromise(banksData)
      Fetcher.fakeFetch = makeFakeFetch(response)
      return getBanks()(dispatchStub, getStateStub)
    }

    it('should hit the right endpoint', () => {
      Fetcher.fakeFetch = fetchStub
      getBanks()(dispatchStubIgnore, getStateStub)
      expect(fetchStub).to.have.been.calledWith('/api/banks')
    })

    it('should trigger additional actions', async () => {
      await stubbedGetBanksCall()

      const expectedResponse = handleBanksResponse(banksData)
      checkActionDispatched(dispatchStub, expectedResponse[0])
    })

    it('should accept parameters', async () => {
      Fetcher.fakeFetch = fetchStub
      getBanks({ page: 2 })(dispatchStubIgnore, getStateStub)
      expect(fetchStub).to.have.been.calledWith('/api/banks?page=2')
    })
  })

  describe('getAllBanks', () => {
    const makeFakeFetch = () => {
      return sinon.stub()
        .onCall(0).resolves(jsoniblePromise(pagedBanksData[0], {
          headers: {
            'per-page': '1',
            total: '2',
          }
        }))
        .onCall(1).resolves(jsoniblePromise(pagedBanksData[1]))
    }

    it('should fetch all pages', async () => {
      Fetcher.fakeFetch = makeFakeFetch()
      await getAllBanks()(dispatchStubIgnore, getStateStub)

      expect(Fetcher.fakeFetch).to.have.been.calledTwice()
      expect(Fetcher.fakeFetch.args[0][0]).to.equal('/api/banks?per_page=100')
      // second call matches page size returned by server
      expect(Fetcher.fakeFetch.args[1][0]).to.equal('/api/banks?page=2&per_page=1')
    })

    it('should combine all the results and return the ids', async () => {
      Fetcher.fakeFetch = makeFakeFetch()
      const response = await getAllBanks()(dispatchStubIgnore, getStateStub)
      expect(response).to.have.length(2)
      expect(response).to.deep.equal(['1', '2'])
    })

    it('should dispatch event with the full banks', async () => {
      Fetcher.fakeFetch = makeFakeFetch()
      await getAllBanks()(dispatchStub, getStateStub)

      const expectedResponse = handleBanksResponse([...pagedBanksData[0], ...pagedBanksData[1]])
      checkActionDispatched(dispatchStub, expectedResponse[0])
    })
  })

  describe('getBank', () => {
    const stubbedGetBankCall = () => {
      const response = jsoniblePromise(bankData)
      Fetcher.fakeFetch = makeFakeFetch(response)
      return getBank(1)(dispatchStub, getStateStub)
    }

    it('should hit the right endpoint', () => {
      Fetcher.fakeFetch = fetchStub
      getBank(1)(dispatchStubIgnore, getStateStub)

      expect(fetchStub).to.have.been.calledWith('/api/banks/1')
    })

    it('should trigger additional actions', async () => {
      await stubbedGetBankCall()

      const expectedResponse = handleBanksResponse([bankData])
      checkActionDispatched(dispatchStub, expectedResponse[0])
    })
  })

  describe('createBank', () => {
    const stubbedCreateBankCall = () => {
      const response = jsoniblePromise(bankData)
      Fetcher.fakeFetch = makeFakeFetch(response)
      return createBank({ title: 'newbank' })(dispatchStub, getStateStub)
    }

    it('should hit the right endpoint', () => {
      Fetcher.fakeFetch = fetchStub
      createBank({ title: 'newbank' })(dispatchStubIgnore, getStateStub)
      expect(fetchStub).to.have.been.calledWith('/api/banks')
    })

    it('should provide bank language', async () => {
      Fetcher.fakeFetch = fetchStub
      createBank({ title: 'newbank', language: 'es' })(dispatchStubIgnore, getStateStub)
      expect(fetchStub).to.have.been.calledWith('/api/banks')
      expect(JSON.parse(fetchStub.args[0][1].body)).to.deep.equal({
        bank: {
          title: 'newbank',
          language: 'es'
        }
      })
    })

    it('should trigger additional actions', async () => {
      await stubbedCreateBankCall()

      const expectedResponse = handleBankResponse(bankData)
      checkActionDispatched(dispatchStub, expectedResponse[0])
    })
  })

  describe('updateBank', () => {
    const updatedBank = {id: 50, title: 'updatedbank'}
    const stubbedUpdateBankCall = () => {
      const response = jsoniblePromise(updatedBank)
      Fetcher.fakeFetch = makeFakeFetch(response)
      return updateBank(updatedBank)(dispatchStub, getStateStub)
    }

    it('should hit the right endpoint', () => {
      Fetcher.fakeFetch = fetchStub
      updateBank(updatedBank)(dispatchStubIgnore, getStateStub)
      expect(fetchStub).to.have.been.calledWith('/api/banks/50')
    })

    it('should trigger additional actions', async () => {
      await stubbedUpdateBankCall()

      const expectedResponse = handleBankResponse(updatedBank)
      checkActionDispatched(dispatchStub, expectedResponse[0])
    })
  })

  describe('deleteBank', () => {
    it('should hit the right endpoint', () => {
      Fetcher.fakeFetch = fetchStub
      deleteBank('5')(dispatchStubIgnore, getStateStub)
      expect(fetchStub).to.have.been.calledWith('/api/banks/5')
    })

    it('should trigger additional actions', async () => {
      const stubbedDeleteBankCall = () => {
        Fetcher.fakeFetch = makeFakeFetch(jsoniblePromise())
        return deleteBank('5')(dispatchStub, getStateStub)
      }
      await stubbedDeleteBankCall()
      checkActionDispatched(dispatchStub, clear(BANKS_BANK_PAGINATION))
      checkActionDispatched(dispatchStub, archiveBank('5'))
    })

    it('should clear activeBankId and activeBankEntryId if deleting the active bank', async () => {
      const stubbedDeleteBankCall = () => {
        Fetcher.fakeFetch = makeFakeFetch(jsoniblePromise())
        return deleteBank('5')(dispatchStub, getStateStub)
      }
      await stubbedDeleteBankCall()
      checkActionDispatched(dispatchStub, newActiveBank(void 0))
      checkActionDispatched(dispatchStub, newActiveBankEntry(void 0))
    })

    it('should not clear activeBankId and activeBankEntryId if not deleting the active bank', async () => {
      const stubbedDeleteBankCall = () => {
        Fetcher.fakeFetch = makeFakeFetch(jsoniblePromise())
        return deleteBank('4')(dispatchStub, getStateStub)
      }
      await stubbedDeleteBankCall()
      checkActionNotDispatched(dispatchStub, newActiveBank(void 0))
      checkActionNotDispatched(dispatchStub, newActiveBankEntry(void 0))
    })
  })

  describe('shareBank', () => {
    const stubbedShareBankCall = () => {
      const response = jsoniblePromise({id:'1', userUuid: '1', bankId: '1', permission: CAN_EDIT})
      Fetcher.fakeFetch = makeFakeFetch(response)
      return shareBank({id:'1', userUuid: '1', bankId: '1', permission: CAN_EDIT}, getUserInfoStub)(dispatchStub, getStateStub)
    }
    it('should hit the right endpoint', () => {
      Fetcher.fakeFetch = fetchStub
      shareBank({id:'1', userUuid: '1', bankId: '2', permission: CAN_EDIT}, getUserInfoStub)(dispatchStubIgnore, getStateStub)
      expect(fetchStub).to.have.been.calledWith('/api/banks/2/shared_banks')
    })

    it('should trigger additional actions', async () => {
      await stubbedShareBankCall()

      const expectedResponse = handleShareBankResponse({ id: '1', userUuid: '1', bankId: '1', permission: CAN_EDIT, fullName: 'firstFullName', avatar: 'http://avatars.com/avatar1' })
      checkActionDispatched(dispatchStub, expectedResponse[0])
    })
  })

  describe('getSharedBanks', () => {
    const stubbedGetSharedBanksCall = (sharedBanksData) => {
      const response = jsoniblePromise(sharedBanksData)
      Fetcher.fakeFetch = makeFakeFetch(response)
      return getSharedBanks(bankData, getUserInfoStub)(dispatchStub, getStateStub)
    }

    it('should hit the right endpoint', () => {
      Fetcher.fakeFetch = fetchStub
      getSharedBanks(bankData, getUserInfoStub)(dispatchStubIgnore, getStateStub)
      expect(fetchStub).to.have.been.calledWith('/api/banks/1/shared_banks')
    })

    it('should trigger additional actions', async () => {
      await stubbedGetSharedBanksCall(sharedBanksData)

      const expectedResponse = handleShareBanksResponse(sharedBanksDataMerged)
      checkActionDispatched(dispatchStub, expectedResponse[0])
    })

    it('should dispatch action with the owner when the api returns no data', async () => {
      await stubbedGetSharedBanksCall([])

      const expectedResponse = handleShareBanksResponse([
        {
          id: '-1',
          userUuid: bankData.owner,
          fullName: 'firstFullName',
          avatar: 'http://avatars.com/avatar1',
          permission: CAN_EDIT,
          bankId: bankData.id
        }
      ])
      checkActionDispatched(dispatchStub, expectedResponse[0])
    })
  })

  describe('updateSharedBankPermission', () => {
    it('should hit the right endpoint', () => {
      Fetcher.fakeFetch = fetchStub
      updateSharedBankPermission('1', '1', REMOVED_ACCESS)(dispatchStubIgnore, getStateStub)
      expect(fetchStub).to.have.been.calledWith('/api/banks/1/shared_banks/1')
    })

    it('should trigger additional actions', async () => {
      const stubbedUpdateSharedBankPermissionCall = () => {
        Fetcher.fakeFetch = makeFakeFetch(jsoniblePromise())
        return updateSharedBankPermission('1', '1', REMOVED_ACCESS)(dispatchStub, getStateStub)
      }
      await stubbedUpdateSharedBankPermissionCall()
      checkActionDispatched(dispatchStub, clear(BANKS_BANK_PAGINATION))
    })
  })
})
