require '../common'
config = require '../../../server_config'
moment = require 'moment'
{findStripeSubscription} = require '../../../server/lib/utils'
async = require 'async'
nockUtils = require '../nock-utils'
utils = require '../utils'
Promise = require 'bluebird'
Payment = require '../../../server/models/Payment'
Prepaid = require '../../../server/models/Prepaid'
User = require '../../../server/models/User'
Course = require '../../../server/models/Course'
CourseInstance = require '../../../server/models/CourseInstance'
request = require '../request'

describe 'POST /db/prepaid', ->
  beforeEach utils.wrap (done) ->
    yield utils.clearModels([User, Prepaid])
    admin = yield utils.initAdmin()
    yield utils.loginUser(admin)
    done()
  
  it 'creates a new prepaid for type "course"', utils.wrap (done) ->
    user = yield utils.initUser()
    [res, body] = yield request.postAsync({url: getURL('/db/prepaid'), json: {
      type: 'course'
      creator: user.id
    }})
    expect(res.statusCode).toBe(201)
    prepaid = yield Prepaid.findById(res.body._id)
    expect(prepaid).toBeDefined()
    expect(prepaid.get('creator').equals(user._id)).toBe(true)
    expect(prepaid.get('code')).toBeDefined()
    done()
    
  it 'does not work for non-admins', utils.wrap (done) ->
    user = yield utils.initUser()
    yield utils.loginUser(user)
    [res, body] = yield request.postAsync({url: getURL('/db/prepaid'), json: {
      type: 'course'
      creator: user.id
    }})
    expect(res.statusCode).toBe(403)
    done()
    
  it 'accepts start and end dates', utils.wrap (done) ->
    user = yield utils.initUser()
    [res, body] = yield request.postAsync({url: getURL('/db/prepaid'), json: {
      type: 'course'
      creator: user.id
      startDate: new Date().toISOString(2001,1,1)
      endDate: new Date().toISOString(2010,1,1)
    }})
    expect(res.statusCode).toBe(201)
    prepaid = yield Prepaid.findById(res.body._id)
    expect(prepaid).toBeDefined()
    expect(prepaid.get('startDate')).toBeDefined()
    expect(prepaid.get('endDate')).toBeDefined()
    done()
    
    
describe 'GET /db/prepaid/:handle', ->
  it 'populates startDate and endDate with default values', utils.wrap (done) ->
    prepaid = new Prepaid({type: 'course' })
    yield prepaid.save()
    [res, body] = yield request.getAsync({url: getURL("/db/prepaid/#{prepaid.id}"), json: true})
    expect(body.endDate).toBe(Prepaid.DEFAULT_END_DATE)
    expect(body.startDate).toBe(Prepaid.DEFAULT_START_DATE)
    done()
  

describe 'POST /db/prepaid/:handle/redeemers', ->
  
  beforeEach utils.wrap (done) ->
    yield utils.clearModels([Course, CourseInstance, Payment, Prepaid, User])
    @teacher = yield utils.initUser({role: 'teacher'})
    @admin = yield utils.initAdmin()
    yield utils.loginUser(@admin)
    @prepaid = yield utils.makePrepaid({ creator: @teacher.id })
    yield utils.loginUser(@teacher)
    @student = yield utils.initUser()
    @url = getURL("/db/prepaid/#{@prepaid.id}/redeemers")
    done()

  it 'adds a given user to the redeemers property', utils.wrap (done) ->
    [res, body] = yield request.postAsync {uri: @url, json: { userID: @student.id } }
    expect(body.redeemers.length).toBe(1)
    expect(res.statusCode).toBe(201)
    prepaid = yield Prepaid.findById(body._id)
    expect(prepaid.get('redeemers').length).toBe(1)
    @student = yield User.findById(@student.id)
    expect(@student.get('coursePrepaid')._id.equals(@prepaid._id)).toBe(true)
    expect(@student.get('role')).toBe('student')
    done()

  it 'returns 403 if maxRedeemers is reached', utils.wrap (done) ->
    admin = yield utils.initAdmin()
    yield utils.loginUser(admin)
    prepaid = yield utils.makePrepaid({ creator: @teacher.id, maxRedeemers: 0 })
    url = getURL("/db/prepaid/#{prepaid.id}/redeemers")
    yield utils.loginUser(@teacher)
    [res, body] = yield request.postAsync({uri: url, json: { userID: @student.id } })
    expect(res.statusCode).toBe(403)
    expect(res.body.message).toBe('This prepaid is exhausted')
    done()

  it 'returns 403 unless the user is the "creator"', utils.wrap (done) ->
    @otherTeacher = yield utils.initUser({role: 'teacher'})
    yield utils.loginUser(@otherTeacher)
    [res, body] = yield request.postAsync({uri: @url, json: { userID: @student.id } })
    expect(res.statusCode).toBe(403)
    expect(res.body.message).toBe('You may not redeem licenses from this prepaid')
    done()

  it 'returns 403 if the prepaid is expired', utils.wrap (done) ->
    admin = yield utils.initAdmin()
    yield utils.loginUser(admin)
    prepaid = yield utils.makePrepaid({ creator: @teacher.id, endDate: moment().subtract(1, 'month').toISOString() })
    url = getURL("/db/prepaid/#{prepaid.id}/redeemers")
    yield utils.loginUser(@teacher)
    [res, body] = yield request.postAsync({uri: url, json: { userID: @student.id } })
    expect(res.statusCode).toBe(403)
    expect(res.body.message).toBe('This prepaid is expired')
    done()

  it 'is idempotent across prepaids collection', utils.wrap (done) ->
    student = yield utils.initUser({ coursePrepaid: { _id: new Prepaid()._id } })
    [res, body] = yield request.postAsync({uri: @url, json: { userID: student.id } })
    expect(res.statusCode).toBe(200)
    expect(body.redeemers.length).toBe(0)
    done()

  it 'is idempotent to itself', utils.wrap (done) ->
    [res, body] = yield request.postAsync({uri: @url, json: { userID: @student.id } })
    expect(body.redeemers?.length).toBe(1)
    expect(res.statusCode).toBe(201)
    [res, body] = yield request.postAsync({uri: @url, json: { userID: @student.id } })
    expect(body.redeemers?.length).toBe(1)
    expect(res.statusCode).toBe(200)
    prepaid = yield Prepaid.findById(body._id)
    expect(prepaid.get('redeemers').length).toBe(1)
    student = yield User.findById(@student.id)
    expect(student.get('coursePrepaid')._id.equals(@prepaid._id)).toBe(true)
    done()
    
  it 'updates the user if their license is expired', utils.wrap (done) ->
    yield utils.loginUser(@admin)
    prepaid = yield utils.makePrepaid({
      creator: @teacher.id
      startDate: moment().subtract(2, 'month').toISOString()
      endDate: moment().subtract(1, 'month').toISOString()
    })
    @student.set('coursePrepaid', _.pick(prepaid.toObject(), '_id', 'startDate', 'endDate'))
    yield @student.save()
    yield utils.loginUser(@teacher)
    [res, body] = yield request.postAsync {uri: @url, json: { userID: @student.id } }
    expect(body.redeemers.length).toBe(1)
    expect(res.statusCode).toBe(201)
    student = yield User.findById(@student.id)
    expect(student.get('coursePrepaid')._id.equals(@prepaid._id)).toBe(true)
    done()


describe 'GET /db/prepaid?creator=:id', ->
  beforeEach utils.wrap (done) ->
    yield utils.clearModels([Course, CourseInstance, Payment, Prepaid, User])
    @teacher = yield utils.initUser({role: 'teacher'})
    admin = yield utils.initAdmin()
    yield utils.loginUser(admin)
    @prepaid = yield utils.makePrepaid({ creator: @teacher.id })
    @otherPrepaid = yield utils.makePrepaid({ creator: admin.id })
    @expiredPrepaid = yield utils.makePrepaid({ creator: @teacher.id, endDate: moment().subtract(1, 'month').toISOString() })
    @unmigratedPrepaid = yield utils.makePrepaid({ creator: @teacher.id })
    yield @unmigratedPrepaid.update({$unset: { endDate: '', startDate: '' }})
    yield utils.loginUser(@teacher)
    done()
    
  it 'return all prepaids for the creator', utils.wrap (done) ->
    url = getURL("/db/prepaid?creator=#{@teacher.id}")
    [res, body] = yield request.getAsync({uri: url, json: true})
    expect(res.statusCode).toBe(200)
    expect(res.body.length).toEqual(3)
    if _.any((prepaid._id is @otherPrepaid.id for prepaid in res.body))
      fail('Found the admin prepaid in response')
    for prepaid in res.body
      unless prepaid.startDate and prepaid.endDate
        fail('All prepaids should have start and end dates')
    expect(res.body[0]._id).toBe(@prepaid.id)
    done()
    
  it 'returns 403 if the user tries to view another user\'s prepaids', utils.wrap (done) ->
    anotherUser = yield utils.initUser()
    url = getURL("/db/prepaid?creator=#{anotherUser.id}")
    [res, body] = yield request.getAsync({uri: url, json: true})
    expect(res.statusCode).toBe(403)
    done()

    
describe '/db/prepaid', ->
  prepaidURL = getURL('/db/prepaid')

  headers = {'X-Change-Plan': 'true'}

  joeData = null
  stripe = require('stripe')(config.stripe.secretKey)
  joeCode = null

  verifyCoursePrepaid = (user, prepaid, done) ->
    expect(prepaid.creator).toEqual(user.id)
    expect(prepaid.type).toEqual('course')
    expect(prepaid.maxRedeemers).toBeGreaterThan(0)
    expect(prepaid.code).toMatch(/^\w{8}$/)
    return done() if user.isAdmin()
    Payment.findOne {prepaidID: new ObjectId(prepaid._id)}, (err, payment) ->
      expect(err).toBeNull()
      expect(payment).not.toBeNull()
      expect(payment?.get('purchaser')).toEqual(user._id)
      done()

  verifySubscriptionPrepaid = (user, prepaid, done) ->
    expect(prepaid.creator).toEqual(user.id)
    expect(prepaid.type).toEqual('subscription')
    expect(prepaid.maxRedeemers).toBeGreaterThan(0)
    expect(prepaid.code).toMatch(/^\w{8}$/)
    expect(prepaid.properties?.couponID).toEqual('free')
    return done() if user.isAdmin()
    Payment.findOne {prepaidID: new ObjectId(prepaid._id)}, (err, payment) ->
      expect(err).toBeNull()
      expect(payment).not.toBeNull()
      expect(payment?.get('purchaser')).toEqual(user._id)
      done()

  it 'Clear database', (done) ->
    clearModels [Course, CourseInstance, Payment, Prepaid, User], (err) ->
      throw err if err
      done()

  it 'Anonymous creates prepaid code', (done) ->
    createPrepaid 'subscription', 1, 0, (err, res, body) ->
      expect(err).toBeNull()
      expect(res.statusCode).toBe(401)
      done()

  it 'Non-admin creates prepaid code', (done) ->
    loginNewUser (user1) ->
      expect(user1.isAdmin()).toEqual(false)
      createPrepaid 'subscription', 4, 0, (err, res, body) ->
        expect(err).toBeNull()
        expect(res.statusCode).toBe(403)
        done()

  it 'Admin creates prepaid code with type subscription', (done) ->
    loginNewUser (user1) ->
      user1.set('permissions', ['admin'])
      user1.save (err, user1) ->
        expect(err).toBeNull()
        expect(user1.isAdmin()).toEqual(true)
        createPrepaid 'subscription', 1, 0, (err, res, body) ->
          expect(err).toBeNull()
          expect(res.statusCode).toBe(200)
          verifySubscriptionPrepaid user1, body, done

  it 'Admin creates prepaid code with type terminal_subscription', (done) ->
    loginNewUser (user1) ->
      user1.set('permissions', ['admin'])
      user1.save (err, user1) ->
        expect(err).toBeNull()
        expect(user1.isAdmin()).toEqual(true)
        createPrepaid 'terminal_subscription', 2, 3, (err, res, body) ->
          expect(err).toBeNull()
          expect(res.statusCode).toBe(200)
          expect(body.creator).toEqual(user1.id)
          expect(body.type).toEqual('terminal_subscription')
          expect(body.maxRedeemers).toEqual(2)
          expect(body.properties?.months).toEqual(3)
          expect(body.code).toMatch(/^\w{8}$/)
          done()


  it 'Admin creates prepaid code with invalid type', (done) ->
    loginNewUser (user1) ->
      user1.set('permissions', ['admin'])
      user1.save (err, user1) ->
        expect(err).toBeNull()
        expect(user1.isAdmin()).toEqual(true)
        createPrepaid 'bulldozer', 1, 0, (err, res, body) ->
          expect(err).toBeNull()
          expect(res.statusCode).toBe(403)
          done()

  it 'Admin creates prepaid code with no type specified', (done) ->
    loginNewUser (user1) ->
      user1.set('permissions', ['admin'])
      user1.save (err, user1) ->
        expect(err).toBeNull()
        expect(user1.isAdmin()).toEqual(true)
        createPrepaid null, 1, 0, (err, res, body) ->
          expect(err).toBeNull()
          expect(res.statusCode).toBe(403)
          done()

  it 'Admin creates prepaid code with invalid maxRedeemers', (done) ->
    loginNewUser (user1) ->
      user1.set('permissions', ['admin'])
      user1.save (err, user1) ->
        expect(err).toBeNull()
        expect(user1.isAdmin()).toEqual(true)
        createPrepaid 'subscription', 0, 0, (err, res, body) ->
          expect(err).toBeNull()
          expect(res.statusCode).toBe(403)
          done()

  it 'Non-admin requests /db/prepaid', (done) ->
    loginNewUser (user1) ->
      expect(user1.isAdmin()).toEqual(false)
      request.get {uri: prepaidURL}, (err, res, body) ->
        expect(err).toBeNull()
        expect(res.statusCode).toBe(403)
        done()

  it 'Admin requests /db/prepaid', (done) ->
    loginNewUser (user1) ->
      user1.set('permissions', ['admin'])
      user1.save (err, user1) ->
        expect(err).toBeNull()
        expect(user1.isAdmin()).toEqual(true)
        createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
          expect(err).toBeNull()
          expect(res.statusCode).toBe(200)
          request.get {uri: prepaidURL}, (err, res, body) ->
            expect(err).toBeNull()
            expect(res.statusCode).toBe(200)
            prepaids = JSON.parse(body)
            found = false
            for p in prepaids
              if p._id is prepaid._id
                found = true
                verifySubscriptionPrepaid user1, p, done
                break
            expect(found).toEqual(true)
            done() unless found

  describe 'Purchase course', ->
    afterEach nockUtils.teardownNock

    it 'Standard user purchases a prepaid for 0 seats', (done) ->
      nockUtils.setupNock 'db-prepaid-purchase-course-test-1.json', (err, nockDone) ->
        stripe.tokens.create {
          card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
        }, (err, token) ->
          loginNewUser (user1) ->
            purchasePrepaid 'course', {}, 0, token.id, (err, res, prepaid) ->
              expect(err).toBeNull()
              expect(res.statusCode).toBe(422)
              nockDone()
              done()

    it 'Standard user purchases a prepaid for 1 seat', (done) ->
      nockUtils.setupNock 'db-prepaid-purchase-course-test-2.json', (err, nockDone) ->
        stripe.tokens.create {
          card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
        }, (err, token) ->
          loginNewUser (user1) ->
            purchasePrepaid 'course', {}, 1, token.id, (err, res, prepaid) ->
              expect(err).toBeNull()
              expect(res.statusCode).toBe(200)
              verifyCoursePrepaid user1, prepaid, ->
                nockDone()
                done()

    it 'Standard user purchases a prepaid for 3 seats', (done) ->
      nockUtils.setupNock 'db-prepaid-purchase-course-test-3.json', (err, nockDone) ->
        stripe.tokens.create {
          card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
        }, (err, token) ->
          loginNewUser (user1) ->
            purchasePrepaid 'course', {}, 3, token.id, (err, res, prepaid) ->
              expect(err).toBeNull()
              expect(res.statusCode).toBe(200)
              verifyCoursePrepaid user1, prepaid, ->
                nockDone()
                done()

  describe 'Purchase terminal_subscription', ->
    afterEach nockUtils.teardownNock
    
    it 'Anonymous submits a prepaid purchase', (done) ->
      nockUtils.setupNock 'db-prepaid-purchase-term-sub-test-1.json', (err, nockDone) ->
        stripe.tokens.create {
          card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
        }, (err, token) ->
          logoutUser () ->
            purchasePrepaid 'terminal_subscription', months: 3, 3, token.id, (err, res, prepaid) ->
              expect(err).toBeNull()
              expect(res.statusCode).toBe(401)
              nockDone()
              done()

    it 'Should error if type isnt terminal_subscription', (done) ->
      nockUtils.setupNock 'db-prepaid-purchase-term-sub-test-2.json', (err, nockDone) ->
        stripe.tokens.create {
          card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
        }, (err, token) ->
          loginNewUser (user1) ->
            purchasePrepaid 'subscription', months: 3, 3, token.id, (err, res, prepaid) ->
              expect(err).toBeNull()
              expect(res.statusCode).toBe(403)
              nockDone()
              done()

    it 'Should error if maxRedeemers is -1', (done) ->
      nockUtils.setupNock 'db-prepaid-purchase-term-sub-test-3.json', (err, nockDone) ->
        stripe.tokens.create {
          card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
        }, (err, token) ->
          loginNewUser (user1) ->
            purchasePrepaid 'terminal_subscription', months: 3, -1, token.id, (err, res, prepaid) ->
              expect(err).toBeNull()
              expect(res.statusCode).toBe(422)
              nockDone()
              done()

    it 'Should error if maxRedeemers is foo', (done) ->
      nockUtils.setupNock 'db-prepaid-purchase-term-sub-test-4.json', (err, nockDone) ->
        stripe.tokens.create {
          card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
        }, (err, token) ->
          loginNewUser (user1) ->
            purchasePrepaid 'terminal_subscription', months: 3, 'foo', token.id, (err, res, prepaid) ->
              expect(err).toBeNull()
              expect(res.statusCode).toBe(422)
              nockDone()
              done()

    it 'Should error if months is -1', (done) ->
      nockUtils.setupNock 'db-prepaid-purchase-term-sub-test-5.json', (err, nockDone) ->
        stripe.tokens.create {
          card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
        }, (err, token) ->
          loginNewUser (user1) ->
            purchasePrepaid 'terminal_subscription', months: -1, 3, token.id, (err, res, prepaid) ->
              expect(err).toBeNull()
              expect(res.statusCode).toBe(422)
              nockDone()
              done()

    it 'Should error if months is foo', (done) ->
      nockUtils.setupNock 'db-prepaid-purchase-term-sub-test-6.json', (err, nockDone) ->
        stripe.tokens.create {
          card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
        }, (err, token) ->
          loginNewUser (user1) ->
            purchasePrepaid 'terminal_subscription', months: 'foo', 3, token.id, (err, res, prepaid) ->
              expect(err).toBeNull()
              expect(res.statusCode).toBe(422)
              nockDone()
              done()

    it 'Should error if maxRedeemers and months are less than 3', (done) ->
      nockUtils.setupNock 'db-prepaid-purchase-term-sub-test-7.json', (err, nockDone) ->
        stripe.tokens.create {
          card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
        }, (err, token) ->
          loginNewUser (user1) ->
            purchasePrepaid 'terminal_subscription', months: 1, 1, token.id, (err, res, prepaid) ->
              expect(err).toBeNull()
              expect(res.statusCode).toBe(403)
              nockDone()
              done()

    it 'User submits valid prepaid code purchase', (done) ->
      nockUtils.setupNock 'db-prepaid-purchase-term-sub-test-8.json', (err, nockDone) ->
        stripe.tokens.create {
          card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
        }, (err, token) ->
          stripeTokenID = token.id
          loginJoe (joe) ->
            joeData = joe.toObject()
            joeData.stripe = {
              token: stripeTokenID
              planID: 'basic'
            }
            request.put {uri: getURL('/db/user'), json: joeData, headers: headers }, (err, res, body) ->
              joeData = body
              expect(res.statusCode).toBe(200)
              expect(joeData.stripe.customerID).toBeDefined()
              expect(firstSubscriptionID = joeData.stripe.subscriptionID).toBeDefined()
              expect(joeData.stripe.planID).toBe('basic')
              expect(joeData.stripe.token).toBeUndefined()
              # TODO: is this test still valid after new token?
              stripe.tokens.create {
                card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
              }, (err, token) ->
                purchasePrepaid 'terminal_subscription', months: 3, 3, token.id, (err, res, prepaid) ->
                  expect(err).toBeNull()
                  expect(res.statusCode).toBe(200)
                  expect(prepaid.type).toEqual('terminal_subscription')
                  expect(prepaid.code).toBeDefined()
                  # Saving this code for later tests
                  # TODO: don't make tests dependent on each other
                  joeCode = prepaid.code
                  expect(prepaid.creator).toBeDefined()
                  expect(prepaid.maxRedeemers).toEqual(3)
                  expect(prepaid.exhausted).toBe(false)
                  expect(prepaid.properties).toBeDefined()
                  expect(prepaid.properties.months).toEqual(3)
                  nockDone()
                  done()

    it 'Should have logged a Payment with the correct amount', (done) ->
      loginJoe (joe) ->
        query =
          purchaser: joe._id
        Payment.find query, (err, payments) ->
          expect(err).toBeNull()
          expect(payments).not.toBeNull()
          expect(payments.length).toEqual(1)
          expect(payments[0].get('amount')).toEqual(900)
          done()

    it 'Anonymous cant redeem a prepaid code', (done) ->
      logoutUser () ->
        subscribeWithPrepaid joeCode, (err, res) ->
          expect(err).toBeNull()
          expect(res?.statusCode).toEqual(401)
          done()

    it 'User cant redeem a nonexistant prepaid code', (done) ->
      loginJoe (joe) ->
        subscribeWithPrepaid 'abc123', (err, res) ->
          expect(err).toBeNull()
          expect(res.statusCode).toEqual(404)
          done()

    it 'User cant redeem empty code', (done) ->
      loginJoe (joe) ->
        subscribeWithPrepaid '', (err, res) ->
          expect(err).toBeNull()
          expect(res.statusCode).toEqual(422)
          done()

    it 'Anonymous cant fetch a prepaid code', (done) ->
      expect(joeCode).not.toBeNull()
      logoutUser () ->
        fetchPrepaid joeCode, (err, res) ->
          expect(err).toBeNull()
          expect(res.statusCode).toEqual(403)
          done()

    it 'User can fetch a prepaid code', (done) ->
      expect(joeCode).not.toBeNull()
      loginJoe (joe) ->
        fetchPrepaid joeCode, (err, res, body) ->
          expect(err).toBeNull()
          expect(res.statusCode).toEqual(200)

          expect(body).toBeDefined()
          return done() unless body

          prepaid = JSON.parse(body)
          expect(prepaid.code).toEqual(joeCode)
          expect(prepaid.maxRedeemers).toEqual(3)
          expect(prepaid.properties?.months).toEqual(3)
          done()

  # TODO: Move redeem subscription prepaid code tests to subscription tests file
  describe 'Subscription redeem tests', ->
    afterEach nockUtils.teardownNock
    
    it 'Creator can redeeem a prepaid code', (done) ->
      nockUtils.setupNock 'db-sub-redeem-test-1.json', (err, nockDone) ->
        loginJoe (joe) ->
          expect(joeCode).not.toBeNull()
          expect(joeData.stripe?.customerID).toBeDefined()
          expect(joeData.stripe?.subscriptionID).toBeDefined()
          return done() unless joeData.stripe?.customerID

          # joe has a stripe subscription, so test if the months are added to the end of it.
          stripe.customers.retrieve joeData.stripe.customerID, (err, customer) =>
            expect(err).toBeNull()
  
            findStripeSubscription customer.id, subscriptionID: joeData.stripe?.subscriptionID, (err, subscription) =>
              if subscription
                stripeSubscriptionPeriodEndDate = new moment(subscription.current_period_end * 1000)
              else
                expect(stripeSubscriptionPeriodEndDate).toBeDefined()
                return done()

              subscribeWithPrepaid joeCode, (err, res, result) =>
                expect(err).toBeNull()
                expect(res.statusCode).toEqual(200)
                endDate = stripeSubscriptionPeriodEndDate.add(3, 'months').toISOString().substring(0, 10)
                expect(result?.stripe?.free.substring(0,10)).toEqual(endDate)
                expect(result?.purchased?.gems).toEqual(14000)
                findStripeSubscription customer.id, subscriptionID: joeData.stripe?.subscriptionID, (err, subscription) =>
                  expect(subscription).toBeNull()
                  nockDone()
                  done()

    it 'User can redeem a prepaid code', (done) ->
      loginSam (sam) ->
        subscribeWithPrepaid joeCode, (err, res, result) ->
          expect(err).toBeNull()
          expect(res.statusCode).toEqual(200)
          endDate = new moment().add(3, 'months').toISOString().substring(0, 10)
          expect(result?.stripe?.free.substring(0,10)).toEqual(endDate)
          expect(result?.purchased?.gems).toEqual(10500)
          done()

    it 'Wont allow the same person to redeem twice', (done) ->
      loginSam (sam) ->
        subscribeWithPrepaid joeCode, (err, res, result) ->
          expect(err).toBeNull()
          expect(res.statusCode).toEqual(403)
          done()

    it 'Will return redeemed code as part of codes list', (done) ->
      loginSam (sam) ->
        request.get "#{getURL('/db/user')}/#{sam.id}/prepaid_codes", (err, res) ->
          expect(err).toBeNull()
          expect(res.statusCode).toEqual(200)
          codes = JSON.parse res.body
          expect(codes.length).toEqual(1)
          done()

    it 'Third user can redeem a prepaid code', (done) ->
      loginNewUser (user) ->
        subscribeWithPrepaid joeCode, (err, res, result) ->
          expect(err).toBeNull()
          expect(res.statusCode).toEqual(200)
          endDate = new moment().add(3, 'months').toISOString().substring(0, 10)
          expect(result?.stripe?.free.substring(0,10)).toEqual(endDate)
          expect(result?.purchased?.gems).toEqual(10500)
          done()

    it 'Fourth user cannot redeem code', (done) ->
      loginNewUser (user) ->
        subscribeWithPrepaid joeCode, (err, res, result) ->
          expect(err).toBeNull()
          expect(res.statusCode).toEqual(403)
          done()

    it 'Can fetch a list of purchased and redeemed prepaid codes', (done) ->
      nockUtils.setupNock 'db-sub-redeem-test-2.json', (err, nockDone) ->
        stripe.tokens.create {
          card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
        }, (err, token) ->
          loginNewUser (user) ->
            purchasePrepaid 'terminal_subscription', months: 1, 3, token.id, (err, res, prepaid) ->
              request.get "#{getURL('/db/user')}/#{user.id}/prepaid_codes", (err, res) ->
                expect(err).toBeNull()
                expect(res.statusCode).toEqual(200);
                codes = JSON.parse res.body
                expect(codes.length).toEqual(1)
                expect(codes[0].maxRedeemers).toEqual(3)
                expect(codes[0].properties).toBeDefined()
                expect(codes[0].properties.months).toEqual(1)
                nockDone()
                done()

    it 'thwarts query injections', utils.wrap (done) ->
      user = yield utils.initUser()
      yield utils.loginUser(user)
      code = { $exists: true }
      subscribeWithPrepaidAsync = Promise.promisify(subscribeWithPrepaid)
      res = yield subscribeWithPrepaidAsync(code)
      expect(res.statusCode).toBe(422)
      expect(res.body.message).toBe('You must provide a valid prepaid code.')
      done()

    it 'enforces the maximum number of redeemers in a race condition', utils.wrap (done) ->
      nockDone = yield nockUtils.setupNockAsync 'db-sub-redeem-test-3.json'
      stripe.tokens.createAsync = Promise.promisify(stripe.tokens.create, {context: stripe.tokens})
      token = yield stripe.tokens.createAsync({
        card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
      })

      user = yield utils.initUser()
      yield utils.loginUser(user)
      
      codeRedeemers = 50
      codeMonths = 3
      redeemers = 51
      
      purchasePrepaidAsync = Promise.promisify(purchasePrepaid, {multiArgs: true})
      [res, prepaid] = yield purchasePrepaidAsync('terminal_subscription', months: codeMonths, codeRedeemers, token.id)
      
      expect(prepaid).toBeDefined()
      expect(prepaid.code).toBeDefined()

      # Make 'threads', which are objects that encapsulate each user and their cookies
      threads = []
      for index in [0...redeemers]
        thread = {}
        thread.request = request.defaults({jar: request.jar()})
        thread.request.postAsync = Promise.promisify(thread.request.post, { context: thread.request })
        thread.user = yield utils.initUser()
        yield utils.loginUser(thread.user, {request: thread.request})
        threads.push(thread)
    
      # Spawn all requests at once!
      requests = []
      options = {
        url: getURL('/db/subscription/-/subscribe_prepaid')
        json: { ppc: prepaid.code }
      }
      for thread in threads
        requests.push(thread.request.postAsync(options))
        
      # Wait until all requests finish, make sure all but one succeeded
      responses = yield requests
      redeemed = _.size(_.where(responses, {statusCode: 200}))
      errors = _.size(_.where(responses, {statusCode: 403}))
      expect(redeemed).toEqual(codeRedeemers)
      expect(errors).toEqual(redeemers - codeRedeemers)
      nockDone()
      done()
