require '../common'
User = require '../../../server/models/User'
Article = require '../../../server/models/Article'
Patch = require '../../../server/models/Patch'
request = require '../request'
utils = require '../utils'
co = require 'co'
Promise = require 'bluebird'

makeArticle = utils.wrap (done) ->
  @creator = yield utils.initAdmin()
  yield utils.loginUser(@creator)
  @article = yield utils.makeArticle()
  @json = {
    commitMessage: 'Accept this patch!'
    delta: { name: ['test'] }
    target:
      id: @article.id
      collection: 'article'
  }
  @url = utils.getURL('/db/patch')
  @user = yield utils.initUser()
  yield utils.loginUser(@user)
  done()

describe 'POST /db/patch', ->
  beforeEach utils.wrap (done) ->
    yield utils.clearModels([User, Patch, Article])
    done()
    
  beforeEach makeArticle

  it 'allows someone to submit a patch to something they don\'t control', utils.wrap (done) ->
    [res, body] = yield request.postAsync { @url, @json }
    expect(res.statusCode).toBe(201)
    expect(body.target.original).toBe(@article.get('original').toString())
    expect(body.target.version.major).toBeDefined()
    expect(body.target.version.minor).toBeDefined()
    expect(body.status).toBe('pending')
    expect(body.created).toBeDefined()
    expect(body.creator).toBe(@user.id)
    done()

  it 'adds a patch to the target document', utils.wrap (done) ->
    [res, body] = yield request.postAsync { @url, @json }
    article = yield Article.findById(@article.id)
    expect(article.get('patches').length).toBe(1)
    done()

  it 'is always based on the latest document', utils.wrap (done) ->
    @json.delta = {i18n: [{de: {name:'German translation'}}]}
    [res, body] = yield request.postAsync { @url, @json }
    expect(res.statusCode).toBe(201)
    expect(res.body.status).toBe('accepted')
    [res, body] = yield request.postAsync { @url, @json }
    expect(res.statusCode).toBe(422) # should be a no-change
    done()

  it 'shows up in patch requests', utils.wrap (done) ->
    [res, body] = yield request.postAsync { @url, @json }
    patchID = res.body._id
    url = utils.getURL("/db/article/#{@article.id}/patches")
    [res, body] = yield request.getAsync { url, json: true }
    expect(res.statusCode).toBe(200)
    expect(body.length).toBe(1)
    expect(body[0]._id).toBe(patchID)
    done()
    
  it 'accepts all patchable collections', utils.wrap (done) ->
    admin = yield utils.initAdmin()
    yield utils.loginUser(admin)
    
    targets = [
      { collection: 'achievement', modelPromise: utils.makeAchievement() }
      { collection: 'article', modelPromise: utils.makeArticle() }
      { collection: 'campaign', modelPromise: utils.makeCampaign() }
      { collection: 'course', modelPromise: utils.makeCourse() }
      { collection: 'level', modelPromise: utils.makeLevel() }
      { collection: 'level_component', modelPromise: utils.makeLevelComponent() }
      { collection: 'level_system', modelPromise: utils.makeLevelSystem() }
      { collection: 'poll', modelPromise: utils.makePoll() }
      { collection: 'thang_type', modelPromise: utils.makeThangType() }
    ]
    
    # concisely test everything in parallel
    promises = targets.map((target) => co =>
      model = yield target.modelPromise
      json = {
        commitMessage: 'Accept this patch!'
        delta: { name: ['test'] }
        target:
          id: model.id
          collection: target.collection
      }
      [res, body] = yield request.postAsync { @url, json }
      expect(res.statusCode).toBe(201)
    )
    yield promises
    count = yield Patch.count()
    expect(count).toBe(targets.length) # make sure all patches got created
    done()

describe 'PUT /db/:collection/:handle/watch', ->
  beforeEach makeArticle
  
  it 'adds the user to the list of watchers idempotently when body is {on: true}', utils.wrap (done) ->
    url = getURL("/db/article/#{@article.id}/watch")
    [res, body] = yield request.putAsync({url, json: {on: true}})
    expect(body.watchers[1]).toBeDefined()
    expect(_.last(body.watchers)).toBe(@user.id)
    previousWatchers = body.watchers
    [res, body] = yield request.putAsync({url, json: {on: true}})
    expect(_.isEqual(previousWatchers, body.watchers)).toBe(true)
    done()

  it 'removes user from the list of watchers when body is {on: false}', utils.wrap (done) ->
    url = getURL("/db/article/#{@article.id}/watch")
    [res, body] = yield request.putAsync({url, json: {on: true}})
    expect(_.contains(body.watchers, @user.id)).toBe(true)
    [res, body] = yield request.putAsync({url, json: {on: false}})
    expect(_.contains(body.watchers, @user.id)).toBe(false)
    done()
    
    
describe 'PUT /db/patch/:handle/status', ->
  beforeEach makeArticle
  
  beforeEach utils.wrap (done) ->
    [res, body] = yield request.postAsync { url: utils.getURL('/db/patch'), @json }
    @patchID = body._id
    @url = utils.getURL("/db/patch/#{@patchID}/status")
    done()

  it 'withdraws the submitter\'s patch', utils.wrap (done) ->
    [res, body] = yield request.putAsync {@url, json: {status: 'withdrawn'}}
    expect(res.statusCode).toBe(200)
    expect(body.status).toBe('withdrawn')
    yield new Promise((resolve) -> setTimeout(resolve, 50))
    article = yield Article.findById(@article.id)
    expect(article.get('patches').length).toBe(0)
    done()

  it 'does not allow the submitter to reject or accept the pull request', utils.wrap (done) ->
    [res, body] = yield request.putAsync {@url, json: {status: 'rejected'}}
    expect(res.statusCode).toBe(403)
    [res, body] = yield request.putAsync {@url, json: {status: 'accepted'}}
    expect(res.statusCode).toBe(403)
    patch = yield Patch.findById(@patchID)
    expect(patch.get('status')).toBe('pending')
    done()

  it 'allows the recipient to accept or reject the pull request', utils.wrap (done) ->
    yield utils.loginUser(@creator)
    [res, body] = yield request.putAsync {@url, json: {status: 'rejected'}}
    expect(res.statusCode).toBe(200)
    patch = yield Patch.findById(@patchID)
    expect(patch.get('status')).toBe 'rejected'
    [res, body] = yield request.putAsync {@url, json: {status: 'accepted'}}
    expect(body.status).toBe('accepted')
    expect(body.acceptor).toBe(@creator.id)
    done()

  it 'keeps track of amount of submitted and accepted patches', utils.wrap (done) ->
    yield utils.loginUser(@creator)
    [res, body] = yield request.putAsync {@url, json: {status: 'accepted'}}
    expect(res.statusCode).toBe(200)
    yield new Promise((resolve) -> setTimeout(resolve, 100))
    user = yield User.findById(@user.id)
    expect(user.get 'stats.patchesSubmitted').toBe 1
    expect(user.get 'stats.patchesContributed').toBe 1
    expect(user.get 'stats.totalMiscPatches').toBe 1
    expect(user.get 'stats.articleMiscPatches').toBe 1
    expect(user.get 'stats.totalTranslationPatches').toBeUndefined()
    done()

  it 'does not allow the recipient to withdraw the pull request', utils.wrap (done) ->
    yield utils.loginUser(@creator)
    [res, body] = yield request.putAsync {@url, json: {status: 'withdrawn'}}
    expect(res.statusCode).toBe(403)
    done()

  it 'only allows artisans and admins to set patch status for courses', utils.wrap (done) ->
    submitter = yield utils.initUser()
    course = yield utils.makeCourse()
    patch = new Patch({
      delta: { name: 'test' }
      target: { collection: 'course', id: course._id, original: course._id }
      creator: submitter._id
      status: 'pending'
      commitMessage: '...'
    })
    yield patch.save()
    anotherUser = yield utils.initUser()
    yield utils.loginUser(anotherUser)
    json = { status: 'rejected' }
    [res, body] = yield request.putAsync({ url: utils.getURL("/db/patch/#{patch.id}/status"), json})
    expect(res.statusCode).toBe(403)
    done()
