import { Map } from 'immutable'
import TimerService from '../TimerService'

describe('TimerService', () => {
  let timer
  let clock
  const updateTimeRemainingStub = sinon.stub()
  const submitQuizStub = sinon.stub()
  const alertStub = sinon.stub()

  beforeEach(() => {
    clock = sinon.useFakeTimers()
    const getTimeRemaining = (cb) => {
      cb({
        timerRunning: true,
        timeLeftInSeconds: 100
      })
    }
    const getTimerData = () => Map({ timerRunning: true, timeLeftInSeconds: 100 })
    timer = new TimerService(
      getTimeRemaining,
      updateTimeRemainingStub,
      getTimerData,
      submitQuizStub,
      alertStub
    )
  })

  afterEach(() => {
    clock.restore()
    updateTimeRemainingStub.resetHistory()
    submitQuizStub.resetHistory()
    alertStub.resetHistory()
  })

  it('performs the API call immediately upon starting', () => {
    expect(updateTimeRemainingStub.callCount).to.eq(0)
    timer.start()
    expect(updateTimeRemainingStub.callCount).to.eq(1)
  })

  it('does not allow restarting', () => {
    timer.start()
    timer.stop()
    expect(() => timer.start()).to.throw(Error)
  })

  it('sets a clock interval upon starting', () => {
    timer.start()
    expect(timer._clockInterval).to.not.be.null()
  })

  it('updates the remaining time on the clock interval', () => {
    timer.start()
    clock.tick(1000)
    expect(updateTimeRemainingStub.callCount).to.eq(2)
    clock.tick(1000)
    expect(updateTimeRemainingStub.callCount).to.eq(3)
  })

  it('updates the remaining time on the API interval', () => {
    timer.start()
    clock.tick(9000)
    expect(updateTimeRemainingStub.callCount).to.eq(10)
    clock.tick(1000)
    expect(updateTimeRemainingStub.callCount).to.eq(12)
  })

  it('only starts the clock when the API says the timer is running', () => {
    timer._getTimerData = () => Map({ timerRunning: false, timeLeftInSeconds: 100 })
    timer.start()
    clock.tick(1000)
    expect(updateTimeRemainingStub.callCount).to.eq(1)
  })

  it('does not submit the quiz before the timer has run out', () => {
    timer._getTimerData = () => Map({ timeLeftInSeconds: 100 })
    timer.start()
    clock.tick(99)
    expect(submitQuizStub.called).to.be.false()
  })

  it('submits the quiz when the timer has run out', () => {
    let timeLeft = 1
    timer._updateTimeRemaining = ({ timeLeftInSeconds }) => { timeLeft = timeLeftInSeconds }
    timer._getTimerData = () => Map({ timerRunning: true, timeLeftInSeconds: timeLeft })
    timer._getTimeRemaining = (cb) => {
      cb({timerRunning: true,
        timeLeftInSeconds: 1})
    }
    timer.start()
    clock.tick(1000)
    expect(submitQuizStub.callCount).to.be.eq(1)
  })

  it('stops after submitting a quiz', () => {
    let timeLeft = 1
    timer._updateTimeRemaining = ({ timeLeftInSeconds }) => { timeLeft = timeLeftInSeconds }
    timer._getTimerData = () => Map({ timerRunning: true, timeLeftInSeconds: timeLeft })
    timer._getTimeRemaining = (cb) => {
      cb({
        timerRunning: true,
        timeLeftInSeconds: 1
      })
    }
    timer.start()
    expect(timer._apiInterval).to.not.be.null()
    clock.tick(2000)
    expect(timer._apiInterval).to.be.null()
    expect(submitQuizStub.callCount).to.eq(1)
  })

  it('does not submit the quiz if time remaining is null', () => {
    timer._getTimerData = () => Map({ timerRunning: false, timeLeftInSeconds: null })
    timer._getTimeRemaining = (cb) => {
      cb({
        timerRunning: false,
        timeLeftInSeconds: null
      })
    }
    timer.start()
    clock.tick(2000)
    expect(submitQuizStub.called).to.be.false()
  })

  it('calls the alert only once after hitting the 30 second mark', () => {
    let timeLeft = 32
    timer._updateTimeRemaining = ({ timeLeftInSeconds }) => { timeLeft = timeLeftInSeconds }
    timer._getTimerData = () => Map({ timerRunning: true, timeLeftInSeconds: timeLeft })
    timer._getTimeRemaining = (cb) => {
      cb({
        timerRunning: true,
        timeLeftInSeconds: timeLeft
      })
    }

    timer.start()

    clock.tick(1000)
    expect(alertStub.callCount).to.eq(0)
    clock.tick(1000)
    expect(alertStub.callCount).to.eq(1)
    clock.tick(1000)
    expect(alertStub.callCount).to.eq(1)
    clock.tick(1000)
    expect(alertStub.callCount).to.eq(1)

    expect(alertStub.calledWith('You have less than 30 seconds remaining.')).to.be.true()
  })
})
