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

import InteractionType from 'quiz-interactions/lib/records/InteractionType'

import {
  TEMPORARY_ITEM_ID,
  TEMPORARY_QUIZ_ENTRY_ID,
  IN_PROGRESS
} from '@instructure/quiz-common/lib/constants'
import stateHelper, { timeIsUp } from '../stateHelper'
import { setFakeStore, unsetFakeStore } from '../../../reduxStore'

import Item from '../../records/Item'
import Quiz from '../../records/Quiz'
import Response from '../../records/Response'
import SessionItem from '../../records/SessionItem'
import SessionItemResult from '../../records/SessionItemResult'
import QuizSession from '../../records/QuizSession'
import QuizEntry from '../../records/QuizEntry'
import QuizSessionEvent from '../../records/QuizSessionEvent'
import QuizSessionResult from '../../records/QuizSessionResult'

// Replacement Selectors
import * as quizzesSelectors from '../../selectors/quizzes'
import * as quizSessionsSelectors from '../../selectors/quizSessions'
import * as takingSelectors from '../../selectors/taking'

const deepCompareAsJSON = (record1, record2) => {
  let jsonRecord1 = record1
  if (record1.toJSON) {
    jsonRecord1 = record1.toJSON()
  } else {
    // Strip funcitons and other unserializable features of the record
    jsonRecord1 = JSON.parse(JSON.stringify(record1))
  }
  let jsonRecord2 = record2
  if (record2.toJSON) {
    jsonRecord2 = record2.toJSON()
  } else {
    // Strip funcitons and other unserializable features of the record
    jsonRecord2 = JSON.parse(JSON.stringify(record2))
  }

  expect(jsonRecord1).to.be.deep.equal(jsonRecord2)
}

describe('stateHelper and selectors', () => {
  const activeQuizId = '5'
  const activeQuizSessionId = '123'
  const userId = '23'

  const theActiveQuiz = {
    id: activeQuizId,
    title: 'quiz num five',
    items: ['3', '8'],
    allowBacktracking: false,
    oneAtATimeType: 'none',
    calculatorType: 'basic'
  }
  const allItems = {
    3: {id: '3', interactionType: '1', errors: {itemBody: ['Cannot be blank']}},
    4: {id: '4', interactionType: '1'},
    7: {id: '7', interactionType: '2'},
    8: {id: '8', interactionType: '2'}
  }
  const allQuizEntries = {
    77: new QuizEntry({id: '77', quiz: activeQuizId, entry: Map({id: '8'}), entryType: 'Item', position: 1}),
    99: new QuizEntry({id: '99', quiz: activeQuizId, entry: Map({id: '3'}), entryType: 'Item', position: 2}),
    88: new QuizEntry({id: '88', quiz: '3', entry: Map({id: '3'}), entryType: 'Item', position: 3})
  }
  const intTypes = {
    1: {
      defaultProperties: {},
      getDefaultInteractionData: () => {},
      getDefaultScoringData: () => {},
      getReadableResponseText: () => {},
      getRenderedResponse: () => {},
      hideResultStem: false,
      id: '1',
      itemBody: 'Question Stem',
      isSubjective: false,
      rawName: 'Choice',
      scoringAlgorithmDefault: null,
      scoringAlgorithmOptions: [],
      slug: 'choice',
      translatedName: 'Multiple Choice',
      userResponseTypeOptions: []
    },
    2: {
      defaultProperties: {},
      getDefaultInteractionData: null,
      getDefaultScoringData: null,
      getRenderedResponse: () => {},
      getReadableResponseText: () => {},
      hideResultStem: false,
      id: '2',
      itemBody: 'Question Stem',
      isSubjective: false,
      rawName: 'True or False',
      scoringAlgorithmDefault: null,
      scoringAlgorithmOptions: [],
      slug: 't/f',
      translatedName: null,
      userResponseTypeOptions: []
    }
  }

  const allSessionItems = {
    100: {position: '100', id: '6', item: '3'},
    101: {position: '101', id: '7', item: '4'},
    102: {position: '102', id: '8', item: '5'}
  }

  const allQuizSessions = {
    activeQuizSessionId,
    [activeQuizSessionId]: {
      id: activeQuizSessionId,
      quiz: activeQuizId,
      sessionItems: ['100', '101'],
      sessionItemsCount: 2,
      user: {
        id: '88'
      },
      allowBacktracking: true,
      oneAtATimeType: 'question'
    },
    999: {
      id: '999',
      quiz: '888',
      sessionItems: ['102'],
      user: {
        id: userId
      }
    },
    111: {
      id: '111',
      quiz: '888',
      sessionItems: ['103'],
      user: {
        id: '88'
      }
    }
  }

  const allQuizSessionEvents = {
    [activeQuizSessionId]: [
      {
        createdAt: '2016-09-28T18:45:27.700Z|232982772',
        eventData: {
          itemId: '5',
          position: '2.0',
          userResponse: {
            value: '11'
          }
        },
        eventType: 'RESPONSE',
        quizSessionId: activeQuizSessionId
      }
    ],
    992: [ // <- not the active session id
      {
        createdAt: '2016-09-28T18:45:27.800Z|232982773',
        eventData: {
          itemId: '5',
          position: '1.0',
          userResponse: {
            value: '12'
          }
        },
        eventType: 'RESPONSE',
        quizSessionId: '992' // <- not the active session id
      }
    ]
  }

  const allSessionItemResults = {
    1: {attempt: '1', position: '1', feedback: 'foo', score: 2}
  }

  const allQuizSessionResults = {
    1: {id: '1', fudgePoints: 2, score: '3'}
  }

  const allQuizzes = {
    activeQuizId,
    [activeQuizId]: theActiveQuiz,
    8: {
      title: 'quiz num eight',
      items: ['3', '7']
    }
  }

  const allTakingResponses = {
    3: {itemId: '1', position: '3', attempt: '1', userResponse: {value: '222'}}
  }

  const timerData = {timerRunning: true, timeLeftInSeconds: 44}
  const browserSessionId = 'session_id'
  const taking = {
    browserSessionId,
    currentSessionItemPosition: '101',
    responses: allTakingResponses,
    quizSessionStatusOnPageLoad: IN_PROGRESS,
    timerData,
    isTaking: true
  }

  const files = [{ file: 'file.file' }]

  const fakeState = fromJS({
    editing: { '88_QUIZ_ENTRY_BUILD': true },
    interactionTypes: intTypes,
    items: allItems,
    modifications: {
      quizzes: {
        [activeQuizId]: 'fake quiz mods for 5 (would be a map irl)',
        8: 'fake quiz mods for 8'
      },
      items: {
        [TEMPORARY_ITEM_ID]: {id: TEMPORARY_ITEM_ID, itemBody: 'the temp item'}
      },
      quizEntries: {
        77: 'fake quiz entry mods for 77',
        [TEMPORARY_QUIZ_ENTRY_ID]: {id: TEMPORARY_QUIZ_ENTRY_ID, entry: TEMPORARY_ITEM_ID, position: 23}
      }
    },
    quizSessionEvents: allQuizSessionEvents,
    quizSessionResults: allQuizSessionResults,
    quizSessions: allQuizSessions,
    quizzes: allQuizzes,
    quizEntries: allQuizEntries,
    sessionItemResults: allSessionItemResults,
    sessionItems: allSessionItems,
    sidebar: {isOpen: false},
    taking,
    upload: { files }
  })

  beforeEach(() => {
    setFakeStore(fakeState)
  })

  afterEach(() => {
    unsetFakeStore()
  })

  it('returns the activeQuizId', () => {
    expect(quizzesSelectors.getActiveQuizId(fakeState))
      .to.be.equal(activeQuizId)
  })

  it('returns the activeQuizSessionId', () => {
    expect(quizSessionsSelectors.getActiveQuizSessionId(fakeState))
      .to.be.equal(activeQuizSessionId)
  })

  it('returns the right activeQuiz', () => {
    const expected = new Quiz(theActiveQuiz)
    deepCompareAsJSON(stateHelper.activeQuiz(), expected)
  })

  it('returns all of the interactionTypes', () => {
    expect(stateHelper.interactionTypes().toJS())
      .to.be.deep.equal(Object.values(intTypes))
  })

  describe('activeQuizSession', () => {
    it('finds the current QuizSession', () => {
      const expected = new QuizSession(allQuizSessions[activeQuizSessionId])
      deepCompareAsJSON(stateHelper.activeQuizSession(), expected)
    })
  })

  const findTest = (ClassString, RecordClass, recordsMap, existingId) => {
    it(`finds an existing ${ClassString}`, () => {
      const expected = new RecordClass(recordsMap[existingId])
      deepCompareAsJSON(stateHelper[`find${ClassString}`](existingId), expected)
    })
  }

  const findDefaultTest = (ClassString, RecordClass) => {
    it(`returns a default ${ClassString} if none found`, () => {
      const defaultRecordOfType = new RecordClass()
      const keyNotInState = 'this_should_not_be_found'
      deepCompareAsJSON(
        stateHelper[`find${ClassString}`](keyNotInState),
        defaultRecordOfType
      )
    })
  }

  describe('findQuiz', () => {
    findTest('Quiz', Quiz, allQuizzes, activeQuizId)
    findDefaultTest('Quiz', Quiz)
  })

  describe('findItem', () => {
    findTest('Item', Item, allItems, '3')
    findDefaultTest('Item', Item)

    it('returns the temporary item if ID matches', () => {
      const tempItem = stateHelper.findItem(TEMPORARY_ITEM_ID)
      expect(tempItem.itemBody).to.be.equal('the temp item')
    })
  })

  describe('findQuizEntry', () => {
    findTest('QuizEntry', QuizEntry, allQuizEntries, '77')
    findDefaultTest('QuizEntry', QuizEntry)

    it('returns the temporary quiz entry if ID matches', () => {
      const tempQuizEntry = stateHelper.findQuizEntry(TEMPORARY_QUIZ_ENTRY_ID)
      expect(tempQuizEntry.position).to.be.equal(23)
      expect(tempQuizEntry.entry).to.be.equal(TEMPORARY_ITEM_ID)
    })
  })

  describe('findQuizSession', () => {
    findTest('QuizSession', QuizSession, allQuizSessions, '123')
    findDefaultTest('QuizSession', QuizSession)
  })

  describe('findTakingResponse', () => {
    findTest('TakingResponse', Response, allTakingResponses, '3')
    findDefaultTest('TakingResponse', Response)
  })

  describe('findInteractionType', () => {
    findTest('InteractionType', InteractionType, intTypes, '1')
    findDefaultTest('InteractionType', InteractionType)
  })

  describe('findSessionItem', () => {
    findTest('SessionItem', SessionItem, allSessionItems, '101')
    findDefaultTest('SessionItem', SessionItem)
  })

  describe('findSessionItemResult', () => {
    findTest('SessionItemResult', SessionItemResult, allSessionItemResults, '1')
    findDefaultTest('SessionItemResult', SessionItemResult)
  })

  describe('findQuizSessionResult', () => {
    findTest('QuizSessionResult', QuizSessionResult, allQuizSessionResults, '1')
    findDefaultTest('QuizSessionResult', QuizSessionResult)
  })

  describe('findQuizSessionEvent', () => {
    findTest('QuizSessionEvent', QuizSessionEvent, allSessionItemResults, activeQuizSessionId)
    findDefaultTest('QuizSessionEvent', QuizSessionEvent)
  })

  describe('getSessionId', () => {
    it('returns the proper quiz sesison id', () => {
      expect(takingSelectors.getBrowserSessionId(fakeState))
        .to.eql(browserSessionId)
    })
  })

  describe('getTimerData', () => {
    it('returns the proper timer data', () => {
      expect(takingSelectors.getTimerData(fakeState).toJS())
        .to.eql(timerData)
    })
  })

  describe('getIsTaking', () => {
    it('returns isTaking', () => {
      expect(takingSelectors.getIsTaking(fakeState))
        .to.eql(taking.isTaking)
    })
  })

  describe('getResponseByPosition', () => {
    it('returns the correct response', () => {
      const resp = allTakingResponses['3']
      expect(takingSelectors.getResponseByPosition(fakeState, resp.position).toJS())
        .to.eql(resp)
    })

    it('returns an empty map if no response is found', () => {
      expect(takingSelectors.getResponseByPosition(fakeState, '3000').toJS())
        .to.eql({})
    })
  })

  describe('getCurrentSessionItemPosition', () => {
    it('returns the current session item position', () => {
      expect(takingSelectors.getCurrentSessionItemPosition(fakeState)).to.eql('101')
    })
  })

  describe('getSessionItemsCount', () => {
    it('returns the session items count', () => {
      // Add this one in!
      // expect(quizSessionsSelectors.getSessionItemsCount()).to.eql(2)
    })
  })

  describe('backtrackingIsAllowed', () => {
    it('returns whether backtracking is allowed', () => {
      expect(quizSessionsSelectors.getBacktrackingIsAllowed(fakeState)).to.eql(true)
    })
  })

  describe('isOneQuestionAtATime', () => {
    it('returns whether it is one question at a time', () => {
      expect(quizSessionsSelectors.isOneQuestionAtATime(fakeState)).to.eql(true)
    })
  })

  describe('calculatorType', () => {
    it('returns the calculator type', () => {
      expect(quizzesSelectors.getActiveQuizCaluclatorType(fakeState)).to.eql('basic')
    })
  })

  describe('findQuizSessions', () => {
    it('returns an empty collection without ids', () => {
      const { size } = stateHelper.findQuizSessions()
      expect(size).to.eq(0)
    })

    it('returns the requested quizSessions for ids', () => {
      const list = stateHelper.findQuizSessions(['111', '999'])
      const ids = list.map((qs) => qs.get('id')).toJS()

      expect(list.first()).to.be.an.instanceof(QuizSession)
      expect(ids).to.deep.eq(['111', '999'])
    })
  })

  describe('quizEntryWithErrorsShowing', () => {
    it('returns the invalid quizEntry', () => {
      expect(stateHelper.quizEntryWithErrorsShowing().position).to.eq(3)
    })
  })

  describe('editingQuizEntry', () => {
    it('returns true when editing a quizEntry', () => {
      expect(stateHelper.editingQuizEntry()).to.be.true()
    })

    it('returns false when not editing a quizEntry', () => {
      setFakeStore(fromJS({
        editing: { '88_QUIZ_ENTRY_BUILD': false, somethingElse: true }
      }))
      expect(stateHelper.editingQuizEntry() || false).to.be.false()
    })
  })
})

describe('timeIsUp', () => {
  context('context timerRunning false', () => {
    it('returns the proper timer data', () => {
      const fakeState = fromJS({
        taking: {
          timerData: {
            timerRunning: false,
            timeLeftInSeconds: -10
          }
        }
      })

      timeIsUp(fakeState)

      expect(timeIsUp(fakeState)).to.eql(false)
    })
  })

  context('context timerRunning', () => {
    it('returns true if time is at zero', () => {
      const fakeState = fromJS({
        taking: {
          timerData: {
            timerRunning: true,
            timeLeftInSeconds: 0
          }
        }
      })

      timeIsUp(fakeState)

      expect(timeIsUp(fakeState)).to.eql(true)
    })

    it('returns true if time is less than', () => {
      const fakeState = fromJS({
        taking: {
          timerData: {
            timerRunning: true,
            timeLeftInSeconds: -10
          }
        }
      })

      timeIsUp(fakeState)

      expect(timeIsUp(fakeState)).to.eql(true)
    })

    it('returns false if time is positive', () => {
      const fakeState = fromJS({
        taking: {
          timerData: {
            timerRunning: true,
            timeLeftInSeconds: 10
          }
        }
      })

      timeIsUp(fakeState)

      expect(timeIsUp(fakeState)).to.eql(false)
    })
  })
})
