import { expect } from 'chai'
import get from 'lodash/get'

import {
  ADD_BANK_ENTRIES,
  ADD_BANKS,
  ADD_SHARED_BANKS,
  ADD_INTERACTION_TYPES,
  ADD_ITEMS,
  ADD_QTI_IMPORTS,
  ADD_QUIZ_ENTRY,
  ADD_QUIZ_SESSION_EVENTS,
  ADD_QUIZ_SESSION_RESULTS,
  ADD_QUIZ_SESSIONS,
  ADD_QUIZZES,
  ADD_STIMULI,
  ADD_SESSION_ITEM_RESULTS,
  ADD_SESSION_ITEMS,
  LOAD_RESUME_DATA,
  CAN_EDIT
} from '@instructure/quiz-common/lib/constants'

import {
  handleBankEntriesResponse,
  handleBankEntryResponse,
  handleBankResponse,
  handleBanksResponse,
  handleInteractionTypesResponse,
  handleItemsResponse,
  handleStimuliResponse,
  handleQtiImportsResponse,
  handleQuizResponse,
  handleQuizEntryResponse,
  handleQuizSessionEventsResponse,
  handleQuizSessionResponse,
  handleQuizSessionResultsResponse,
  handleResumeDataResponse,
  handleShareBankResponse,
  handleSessionItemResultsResponse,
  handleSessionItemsResponse
} from '../callHandlers'

const fakeQuizSession = {
  id: 3,
  quiz: {
    id: 2,
    title: 'Chem 101 Exam 2'
  },
  quiz_session_results: [
    {
      id: 1,
      points_possible: 2,
      quiz_session_id: '55'
    }
  ],
  resume_data: [{position: 2, user_response: 'resp'}],
  start_at: null,
  end_at: null,
  session_items: [{
    position: 2,
    item: {
      id: '47.0',
      interaction_type: {
        id: '1.0'
      },
      title: 'First Publisher of Modern PTE',
      interaction_choices: [{
        id: '165.0'
      }]
    }
  }]
}

const getPersistenceActionsCalled = (
  actionType,
  nestedActionType,
  handler,
  fakeData,
  expectedIds,
  payloadKey,
  schemaValues = null,
  runSchemaCheck = true
) => {
  describe('getPersistenceActions', () => {
    context(`works for ${actionType} and nest action type ${nestedActionType}`, () => {
      it('sends the correct nested action', () => {
        const actions = handler(fakeData)
        const selectedAction = actions.find((action) => action.type === nestedActionType)
        expect(selectedAction).to.exist()
      })

      it('adds the right payload of nested objects to the store', () => {
        const actions = handler(fakeData)
        const selectedAction = actions.find((action) => action.type === nestedActionType)
        expect(selectedAction.payload.map((i) => i.id)).to.deep.equal(expectedIds)
      })

      it('converts the nested object to an array of objects', () => {
        const actions = handler(fakeData)
        const selectedAction = actions.find((action) => action.type === nestedActionType)
        expect(selectedAction.payload.map((o) => o.id)).to.deep.equal(expectedIds)
      })

      if (runSchemaCheck) {
        it('converts nested items to expected schema', () => {
          const actions = handler(fakeData)
          const selectedAction = actions.find((action) => action.type === actionType)
          const expected = schemaValues || expectedIds
          expect(selectedAction.payload[0][payloadKey]).to.deep.equal(expected)
        })
      }
    })
  })
}

describe('Call Handlers', () => {
  const fakeItemsForRes = [
    {id: '2', title: 'someItem', interaction_type: {id: '1.0'}},
    {id: '3', title: 'someItem2', interaction_type: {id: '2.0'}},
    {id: '4', title: 'someItem3', interaction_type: {id: '3.0'}}
  ]

  describe('singleObject responses', () => {
    const handledAsSingleObject = (
      actionType,
      handler,
      fakeData,
      lookup,
      payloadVal
    ) => {
      context(`works for ${actionType}`, () => {
        it('sends the correct action', () => {
          const actions = handler(fakeData)
          const selectedAction = actions.find((action) => action.type === actionType)
          expect(selectedAction).to.exist()
        })

        it('has the correct payload in the action', () => {
          const actions = handler(fakeData)
          const selectedAction = actions.find((action) => action.type === actionType)
          expect(get(selectedAction.payload[0], lookup)).to.be.equal(payloadVal)
        })
      })
    }

    describe('handleQuizResponse', () => {
      const fakeQuizApiRes = {
        id: '1',
        title: 'fake quiz',
        items: fakeItemsForRes
      }

      handledAsSingleObject(ADD_QUIZZES, handleQuizResponse, fakeQuizApiRes, ['title'], 'fake quiz')
      getPersistenceActionsCalled(ADD_QUIZZES, ADD_ITEMS, handleQuizResponse, fakeQuizApiRes, ['2', '3', '4'], 'items')
    })

    describe('handleBankResponse', () => {
      const bankResponse = {id: 1, name: 'bank name'}
      handledAsSingleObject(ADD_BANKS, handleBankResponse, bankResponse, ['name'], 'bank name')
    })

    describe('handleShareBankResponse', () => {
      const sharedBankResponse = {id:'1', uuid: '1', bankId: '1', permission: CAN_EDIT}
      handledAsSingleObject(ADD_SHARED_BANKS, handleShareBankResponse, sharedBankResponse, ['bankId'], '1')
    })

    describe('handleStimuliResponse', () => {
      const fakeStimuliForRes = [
        {id: '2', title: 'hello'},
        {id: '3', title: 'there'},
        {id: '4', title: 'world'}
      ]

      it('adds a stimulus from a stimulus api response', () => {
        const actions = handleStimuliResponse(fakeStimuliForRes)
        const selectedAction = actions.filter((action) => action.type === ADD_STIMULI)[0]
        expect(selectedAction.type).to.be.equal(ADD_STIMULI)
        expect(selectedAction.payload[0].title).to.be.equal('hello')
      })
    })

    describe('handleBankEntryResponse', () => {
      const bankEntryResponse = { id: 1, entry: { itemBody: 'nsjfan' } }

      handledAsSingleObject(
        ADD_BANK_ENTRIES, handleBankEntryResponse, bankEntryResponse, ['entry', 'itemBody'], 'nsjfan'
      )
    })

    describe('handleQuizSessionResponse', () => {
      handledAsSingleObject(
        ADD_QUIZ_SESSIONS, handleQuizSessionResponse, fakeQuizSession, ['id'], 3
      )
      getPersistenceActionsCalled(
        ADD_QUIZ_SESSIONS, ADD_QUIZ_SESSION_RESULTS,
        handleQuizSessionResponse, fakeQuizSession, [1], 'quizSessionResults'
      )
      getPersistenceActionsCalled(
        ADD_QUIZ_SESSIONS, ADD_QUIZZES,
        handleQuizSessionResponse, fakeQuizSession, [2], 'quiz', 2
      )

      it('excludes deprecated keys', () => {
        const actions = handleQuizSessionResponse(fakeQuizSession)
        const selectedAction = actions.find((action) => action.type === ADD_QUIZ_SESSIONS)
        const deprecatedKeys = ['resumeData', 'sessionItems', 'sessionItemResponses']
        deprecatedKeys.forEach((key) => {
          expect(Object.keys(selectedAction.payload[0])).not.to.include(key)
        })
      })
    })

    describe('handleQuizEntryResponse', () => {
      const fakeQuizEntry = {
        entry: {id: '12'},
        entry_editable: true,
        entry_type: 'Item',
        id: '38',
        points_possible: 4,
        position: 2,
        properties: {},
        status: 'mutable',
        stimulus_quiz_entry_id: ''
      }
      handledAsSingleObject(
        ADD_QUIZ_ENTRY, handleQuizEntryResponse, fakeQuizEntry, ['id'], '38'
      )
      getPersistenceActionsCalled(
        ADD_QUIZ_ENTRY, ADD_ITEMS,
        handleQuizEntryResponse, fakeQuizEntry, ['12'], null, null, false
      )
    })
  })

  describe('array responses', () => {
    const handledAsArrayResponse = (
      actionType,
      handler,
      responseData, // this should be an array since some handlers take multiple params (payload always first)
      lookup,
      payloadVal
    ) => {
      it(`creates an ${actionType} action`, () => {
        const actions = handler(...responseData)
        const selectedAction = actions.find((action) => action.type === actionType)
        expect(selectedAction).to.exist()
      })

      it(`${actionType} has correct payload`, () => {
        const actions = handler(...responseData)
        const selectedAction = actions.find((action) => action.type === actionType)
        expect(get(selectedAction.payload[0], lookup)).to.equal(payloadVal)
      })

      it(`${actionType} has correct number of objects in payload`, () => {
        const actions = handler(...responseData)
        const selectedAction = actions.find((action) => action.type === actionType)
        expect(selectedAction.payload.length).to.equal(responseData[0].length)
      })
    }

    describe('handleQtiImportsResponse', () => {
      const qtiResponse = [{id: 1, status: 'complete', parent_id: '2', parent_type: 'Quiz', message: 'some_msg'}]

      handledAsArrayResponse(ADD_QTI_IMPORTS, handleQtiImportsResponse, [qtiResponse], ['message'], 'some_msg')
    })

    describe('handleInteractionTypesResponse', () => {
      const interactionTypesObjects = [
        {id: '2', hello: 'there'}, {id: '3'}
      ]
      handledAsArrayResponse(
        ADD_INTERACTION_TYPES, handleInteractionTypesResponse, [interactionTypesObjects], ['hello'], 'there'
      )
    })

    describe('handleItemsResponse', () => {
      handledAsArrayResponse(
        ADD_ITEMS, handleItemsResponse, [fakeItemsForRes], ['title'], 'someItem'
      )

      getPersistenceActionsCalled(
        ADD_ITEMS, ADD_INTERACTION_TYPES,
        handleItemsResponse, fakeItemsForRes, ['1.0', '2.0', '3.0'], 'interactionType', '1.0'
      )
    })

    describe('sessionItem* responses', () => {
      const handledAsSessionItemType = (actionType, handler, quizSessionId, requestData, expectedIds) => {
        context('acts as a sessionItem* request', () => {
          it('adds quizSessionId and an id to each session item', () => {
            const actionsToDispatch = handler(requestData, quizSessionId)
            const action = actionsToDispatch.find((a) => a.type === actionType)
            const mappedIds = action.payload.map((si) => si.id)
            expect(mappedIds).to.deep.equal(expectedIds)
          })

          it('adds quiz_session_id to each session item', () => {
            const actionsToDispatch = handler(requestData, quizSessionId)
            const action = actionsToDispatch.find((a) => a.type === actionType)
            action.payload.forEach(
              (si) => expect(si.quizSessionId).to.eq(quizSessionId)
            )
          })
        })
      }

      const quizSessionId = '34'

      describe('handleSessionItemsResponse', () => {
        const sessionItemsData = [
          {position: 1, item: {id: '2', title: 'someItem', interaction_type: {id: '1.0'}}},
          {position: 2, item: {id: '3', title: 'someItem', interaction_type: {id: '12.0'}}}
        ]
        handledAsArrayResponse(
          ADD_SESSION_ITEMS, handleSessionItemsResponse, [sessionItemsData, quizSessionId], ['position'], 1
        )
        getPersistenceActionsCalled(
          ADD_SESSION_ITEMS, ADD_INTERACTION_TYPES,
          handleSessionItemsResponse, sessionItemsData, ['1.0', '12.0'], 'interactionType', null, false
        )
        handledAsSessionItemType(
          ADD_SESSION_ITEMS, handleSessionItemsResponse, quizSessionId, sessionItemsData, ['34_1', '34_2']
        )
      })

      describe('handleSessionItemResultsResponse', () => {
        const sirResponseData = [
          {attempt: 1, position: 1, itemId: '2', gradedAt: 'Some Date', gradingMethod: 'Autograde', pointsPossible: 1},
          {attempt: 1, position: 2, itemId: '3', gradedAt: 'Some Date', gradingMethod: 'Autograde', pointsPossible: 1}
        ]
        handledAsArrayResponse(
          ADD_SESSION_ITEM_RESULTS, handleSessionItemResultsResponse,
          [sirResponseData, quizSessionId], ['position'], 1
        )
        handledAsSessionItemType(
          ADD_SESSION_ITEM_RESULTS, handleSessionItemResultsResponse, quizSessionId, sirResponseData, ['34_1', '34_2']
        )
      })

      describe('handleResumeDataResponse', () => {
        const resumeData = [
          {position: 1, itemId: '2', userResponse: {response: 'this is a response'}},
          {position: 2, itemId: '3', userResponse: {response: 'this is a response2'}}
        ]
        handledAsArrayResponse(
          LOAD_RESUME_DATA, handleResumeDataResponse,
          [resumeData, quizSessionId], ['position'], 1
        )
        handledAsSessionItemType(
          LOAD_RESUME_DATA, handleResumeDataResponse, quizSessionId, resumeData, ['34_1', '34_2']
        )
      })
    })

    // TODO: use the shared examples on these tests below

    describe('handleQuizSessionResultsResponse', () => {
      const fakeAuthoritativeResultRes = {
        id: 1,
        pointsPossible: 2,
        quizSessionId: '55'
      }

      it('adds the authoritative result', () => {
        const actions = handleQuizSessionResultsResponse(fakeQuizSession.quiz_session_results)
        const resultToPersist = actions[0].payload[0]
        expect(resultToPersist).to.be.deep.equal(fakeAuthoritativeResultRes)
      })
    })

    describe('handleBanksResponse', () => {
      const banksResponse = [{id: 1, name: 'bank name'}]

      it('adds banks from response', () => {
        const actions = handleBanksResponse(banksResponse)
        expect(actions[0].type).to.be.equal(ADD_BANKS)
        expect(actions[0].payload[0].name).to.be.equal('bank name')
      })
    })

    describe('handleBankEntriesResponse', () => {
      const bankEntriesResponse = [{ id: 1, entry: { itemBody: 'nsjfan' } }]

      it('adds entries from response', () => {
        const actions = handleBankEntriesResponse(bankEntriesResponse)
        expect(actions[0].type).to.be.equal(ADD_BANK_ENTRIES)
        expect(actions[0].payload[0].entry.itemBody).to.be.equal('nsjfan')
      })
    })

    describe('handleQuizSessionEventsResponse', () => {
      it('parses event data properly', () => {
        const quizSessionEventsRes = [
          {
            created_at: '2016-09-28T18:45:27.700Z|232982772',
            event_type: 'RESPONSE',
            event_data: {
              item_id: '46',
              position: '1.0',
              user_response: {
                value: '460202cc-feb7-4cf6-b000-34d9ee6535f2'
              }
            },
            quiz_session_id: '12'
          },
          {
            created_at: '2016-09-28T18:45:28.104Z|636885914',
            event_type: 'RESPONSE',
            event_data: {
              item_id: '46',
              position: '1.0',
              user_response: {
                value: '5f68cc77-5d8a-4807-a85a-4080616b3acc'
              }
            },
            quiz_session_id: '12'
          }
        ]

        const actions = handleQuizSessionEventsResponse(quizSessionEventsRes)
        expect(actions[0].type).to.be.equal(ADD_QUIZ_SESSION_EVENTS)
        expect(actions[0].payload[0].eventType).to.be.equal('RESPONSE')
      })
    })
  })
})
