import configureStore from 'redux-mock-store'
import thunkMiddleware from 'redux-thunk'
import multi from 'redux-multi'

import { clear } from '../../../common/actions/ui'
import { setBankFilters } from '../../../common/actions/banks'
import Fetcher from '../../../common/util/Fetcher'
import makeFakeFetch from '../../../../tests/util/fakeFetch'
import { jsoniblePromise } from '../../../common/util/jsonify'
import BankEntry from '../../../common/records/BankEntry'
import Item from '../../../common/records/Item'

import { handleBankEntriesResponse, handleBankEntryResponse } from '../../../common/api/callHandlers'

import {
  cloneBankEntry,
  createBankEntry,
  createEntryAndBankEntry,
  getBankEntries,
  getBankEntry,
  removeBankEntry,
  updateBankEntry
} from '../bankEntries'

describe('bank entries api actions', () => {
  const bankEntryData = { id: 1, entry_type: 'Item', entry: { id: 2, itemBody: 'fjkasndgkjs' } }
  const bankEntryStimulusData = { id: 2, entry_type: 'Stimulus', entry: { id: 3 } }
  const bankEntriesData = [ bankEntryData ]
  const bankSearchData = {
    entries: bankEntriesData,
    filters: { some: 'filters' }
  }

  const fetchBankEntriesStub = makeFakeFetch(jsoniblePromise(bankSearchData))
  const fetchBankEntryStub = makeFakeFetch(jsoniblePromise(bankEntryData))
  const fetchBankEntryStimulusStub = makeFakeFetch(jsoniblePromise(bankEntryStimulusData))

  useFakeFetching()

  const mockStore = configureStore([multi, thunkMiddleware])

  afterEach(() => {
    fetchBankEntriesStub.resetHistory()
    fetchBankEntryStub.resetHistory()
    fetchBankEntryStimulusStub.resetHistory()
  })

  describe('getBankEntries', () => {
    beforeEach(() => {
      Fetcher.fakeFetch = fetchBankEntriesStub
    })

    it('should hit the right endpoint', () => {
      const store = mockStore()
      store.dispatch(getBankEntries(123, true))
      expect(fetchBankEntriesStub).to.have.been.calledWithMatch('/api/banks/123/bank_entries/search')
    })

    it('should trigger additional actions', (done) => {
      const store = mockStore()
      store.dispatch(getBankEntries(123, true)).then(() => {
        const expectedFilter = setBankFilters(123, bankSearchData.filters)
        const expectedEntries = handleBankEntriesResponse(bankEntriesData)
        expect(store.getActions()).to.deep.include(expectedEntries[0])
        expect(store.getActions()).to.deep.include(expectedFilter)
      }).then(done, done)
    })

    it('does not update filters with search results', async () => {
      const store = mockStore()
      await store.dispatch(getBankEntries(123, true, { text: 'example' }))
      const filter = setBankFilters(123, bankSearchData.filters)
      const actions = store.getActions().filter((a) => a.type === filter.type)
      expect(actions.length).to.equal(0)
    })
  })

  describe('createBankEntry', () => {
    const bankId = '1'
    const entryType = 'Item'
    const entryId = '1'

    beforeEach(() => {
      Fetcher.fakeFetch = fetchBankEntryStub
    })

    it('should hit the right endpoint', () => {
      const store = mockStore()
      store.dispatch(createBankEntry(bankId, entryType, entryId))
      expect(fetchBankEntryStub).to.have.been.calledWith('/api/banks/1/bank_entries')
    })

    it('should trigger additional actions', (done) => {
      const store = mockStore()
      store.dispatch(createBankEntry(bankId, entryType, entryId)).then(() => {
        const expectedResponse = handleBankEntryResponse(bankEntryData)
        expect(store.getActions()).to.deep.include(expectedResponse[0])
      }).then(done, done)
    })

    it('should fetch the affected bank', (done) => {
      const store = mockStore()
      store.dispatch(createBankEntry(bankId, entryType, entryId)).then(() => {
        expect(fetchBankEntryStub).to.have.been.calledWith('/api/banks/1')
      }).then(done, done)
    })
  })

  describe('createEntryAndBankEntry', () => {
    const bankId = '7'
    const bankEntry = new BankEntry({
      id: '22',
      entryType: 'Item'
    })
    const dispatchItemStub = sinon.stub().resolves(fakeItemData)
    const stubbedBankEntryCreation = sinon.stub()
    const stubbedItemCreation = sinon.stub()
    const fakeItemData = {id: 'foo'}

    const fakeCreateEntryAndBankEntry = () => {
      const response = jsoniblePromise(bankEntry)
      const fakeFetch = makeFakeFetch(response)

      Fetcher.fakeFetch = fakeFetch
      const createEntryAndBankEntryThunk = createEntryAndBankEntry(bankId, fakeItemData, bankEntry,
        stubbedItemCreation, stubbedBankEntryCreation)
      return createEntryAndBankEntryThunk(dispatchItemStub)
    }

    it('calls createItem with the right argument', () => {
      const fetchPromise = fakeCreateEntryAndBankEntry()
      fetchPromise.then(() => {
        expect(stubbedItemCreation.calledWith(fakeItemData)).to.be.true()
      })
    })

    it('calls createBankEntry with the right arguments', () => {
      const fetchPromise = fakeCreateEntryAndBankEntry()
      fetchPromise.then(() => {
        expect(stubbedBankEntryCreation.getCall(0).args[0]).to.eq('7')
        expect(stubbedBankEntryCreation.getCall(0).args[1]).to.eq('Item')
        expect(stubbedBankEntryCreation.getCall(0).args[2]).to.eq('foo')
      })
    })
  })

  describe('updateBankEntry', () => {
    const bankEntryId = '1'
    const bankId = '5'

    beforeEach(() => {
      Fetcher.fakeFetch = fetchBankEntryStub
    })

    it('should hit the right endpoint', () => {
      const store = mockStore()
      store.dispatch(updateBankEntry(bankId, bankEntryId, { bankId: '5', entryId: '12' }))
      expect(fetchBankEntryStub).to.have.been.calledWith(
        '/api/banks/5/bank_entries/1',
        sinon.match({ body: JSON.stringify({ bank_entry: { bank_id: '5', entry_id: '12' } }) })
      )
    })

    it('should trigger additional actions', (done) => {
      const store = mockStore()
      store.dispatch(updateBankEntry(bankId, bankEntryId, { bankId: '5', entryId: '12' })).then(() => {
        const expectedResponse = handleBankEntryResponse(bankEntryData)
        expect(store.getActions()).to.deep.include(expectedResponse[0])
        expect(store.getActions()).not.to.deep.include(clear('BANKS_BANK_ENTRY_PAGINATION:5'))
        expect(store.getActions()).not.to.deep.include(clear('BANKS_BANK_ENTRY_PAGINATION:6'))
        expect(fetchBankEntryStub).not.to.have.been.calledWith('/api/banks/5')
        expect(fetchBankEntryStub).not.to.have.been.calledWith('/api/banks/6')
      }).then(done, done)
    })

    it('clears pagination state for the banks if entry was moved', (done) => {
      const store = mockStore()
      store.dispatch(updateBankEntry(bankId, bankEntryId, { bankId: '6' })).then(() => {
        expect(store.getActions()).to.deep.include(clear('BANKS_BANK_ENTRY_PAGINATION:5'))
        expect(store.getActions()).to.deep.include(clear('BANKS_BANK_ENTRY_PAGINATION:6'))
      }).then(done, done)
    })

    it('should fetch the affected banks if entry was moved', (done) => {
      const store = mockStore()
      store.dispatch(updateBankEntry(bankId, bankEntryId, { bankId: '6' })).then(() => {
        expect(fetchBankEntryStub).to.have.been.calledWith('/api/banks/5')
        expect(fetchBankEntryStub).to.have.been.calledWith('/api/banks/6')
      }).then(done, done)
    })
  })

  describe('cloneBankEntry', () => {
    const bankEntry = new BankEntry({id: '5', bankId: '1', entryId: '1', entryType: 'Item'})
    const item = new Item()
    sinon.stub(bankEntry, 'getEntry').returns(item)
    sinon.stub(item, 'getWorkingInstance').returns(item)
    const stubbedItemCreation = sinon.stub()
    const stubbedBankEntryUpdate = sinon.stub()
    const dispatchCreateItemStub = sinon.stub().resolves({id: '2'})

    const stubbedCloneBankEntryCall = () => {
      const response = jsoniblePromise(bankEntryData)
      Fetcher.fakeFetch = makeFakeFetch(response)
      const cloneBankEntruThunk = cloneBankEntry(bankEntry, stubbedItemCreation, stubbedBankEntryUpdate)
      return cloneBankEntruThunk(dispatchCreateItemStub)
    }

    it('should hit the right endpoint', (done) => {
      const fetchPromise = stubbedCloneBankEntryCall()
      fetchPromise.then(() => {
        expect(stubbedItemCreation.called).to.be.true()
        done()
      })
    })

    it('should trigger additional actions', (done) => {
      const fetchPromise = stubbedCloneBankEntryCall()

      fetchPromise.then(() => {
        expect(stubbedBankEntryUpdate.getCall(0).args[0]).to.eq('1')
        expect(stubbedBankEntryUpdate.getCall(0).args[1]).to.eq('5')
        expect(stubbedBankEntryUpdate.getCall(0).args[2]).to.deep.equal({entryId: '2'})
        done()
      })
    })
  })

  describe('getBankEntry', () => {
    const bankEntryId = '1'
    const bankId = '5'

    beforeEach(() => {
      Fetcher.fakeFetch = fetchBankEntryStub
    })

    it('should hit the right endpoint', () => {
      const store = mockStore()
      store.dispatch(getBankEntry(bankId, bankEntryId))
      expect(fetchBankEntryStub).to.have.been.calledWith('/api/banks/5/bank_entries/1')
    })

    it('should return bank entry data', async () => {
      const store = mockStore()
      const response = await store.dispatch(getBankEntry(bankId, bankEntryId))
      expect(response).to.eq(bankEntryData)
    })

    it('should trigger additional actions', (done) => {
      const store = mockStore()
      store.dispatch(getBankEntry(bankId, bankEntryId)).then(() => {
        const expectedResponse = handleBankEntryResponse(bankEntryData)
        expect(store.getActions()).to.deep.include(expectedResponse[0])
      }).then(done, done)
    })
  })

  describe('removeBankEntry', () => {
    const bankEntryId = '1'
    const bankId = '5'

    beforeEach(() => {
      Fetcher.fakeFetch = fetchBankEntryStub
    })

    it('should hit the right endpoint', () => {
      const store = mockStore()
      store.dispatch(removeBankEntry(bankId, bankEntryId))
      expect(fetchBankEntryStub).to.have.been.calledWith('/api/banks/5/bank_entries/1')
    })

    it('should fetch the affected bank', (done) => {
      const store = mockStore()
      store.dispatch(removeBankEntry(bankId, bankEntryId)).then(() => {
        expect(fetchBankEntryStub).to.have.been.calledWith('/api/banks/5')
        const actions = store.getActions()
        expect(actions.map((a) => { return { type: a.type } })).to.deep.include({type: 'ADD_BANKS'})
      }).then(done, done)
    })
  })
})
