_ = require 'underscore'
fs = require 'fs'
Actions = require '../src/flux/actions'
NylasAPI = require '../src/flux/nylas-api'
Thread = require '../src/flux/models/thread'
DatabaseStore = require '../src/flux/stores/database-store'

describe "NylasAPI", ->
  beforeEach ->
    spyOn(DatabaseStore, "atomically").andCallFake (fn) -> fn()

  describe "handleModel404", ->
    it "should unpersist the model from the cache that was requested", ->
      model = new Thread(id: 'threadidhere')
      spyOn(DatabaseStore, 'unpersistModel')
      spyOn(DatabaseStore, 'find').andCallFake (klass, id) =>
        return Promise.resolve(model)
      NylasAPI._handleModel404("/threads/#{model.id}")
      advanceClock()
      expect(DatabaseStore.find).toHaveBeenCalledWith(Thread, model.id)
      expect(DatabaseStore.unpersistModel).toHaveBeenCalledWith(model)

    it "should not do anything if the model is not in the cache", ->
      spyOn(DatabaseStore, 'unpersistModel')
      spyOn(DatabaseStore, 'find').andCallFake (klass, id) =>
        return Promise.resolve(null)
      NylasAPI._handleModel404("/threads/1234")
      advanceClock()
      expect(DatabaseStore.find).toHaveBeenCalledWith(Thread, '1234')
      expect(DatabaseStore.unpersistModel).not.toHaveBeenCalledWith()

    it "should not do anything bad if it doesn't recognize the class", ->
      spyOn(DatabaseStore, 'find')
      spyOn(DatabaseStore, 'unpersistModel')
      waitsForPromise ->
        NylasAPI._handleModel404("/asdasdasd/1234")
      runs ->
        expect(DatabaseStore.find).not.toHaveBeenCalled()
        expect(DatabaseStore.unpersistModel).not.toHaveBeenCalled()

    it "should not do anything bad if the endpoint only has a single segment", ->
      spyOn(DatabaseStore, 'find')
      spyOn(DatabaseStore, 'unpersistModel')
      waitsForPromise ->
        NylasAPI._handleModel404("/account")
      runs ->
        expect(DatabaseStore.find).not.toHaveBeenCalled()
        expect(DatabaseStore.unpersistModel).not.toHaveBeenCalled()

  describe "handle401", ->
    it "should post a notification", ->
      spyOn(Actions, 'postNotification')
      NylasAPI._handle401('/threads/1234')
      expect(Actions.postNotification).toHaveBeenCalled()
      expect(Actions.postNotification.mostRecentCall.args[0].message).toEqual("Nylas can no longer authenticate with your mail provider. You will not be able to send or receive mail. Please unlink your account and sign in again.")

  describe "handleModelResponse", ->
    beforeEach ->
      spyOn(DatabaseStore, "persistModels").andCallFake (models) ->
        Promise.resolve(models)

    stubDB = ({models, testClass, testMatcher}) ->
      spyOn(DatabaseStore, "findAll").andCallFake (klass)  ->
        testClass?(klass)
        where: (matcher) ->
          testMatcher?(matcher)
          Promise.resolve(models)

    it "should reject if no JSON is provided", ->
      waitsForPromise ->
        NylasAPI._handleModelResponse()
        .then -> throw new Error("Should reject!")
        .catch (err) ->
          expect(err.message).toEqual "handleModelResponse with no JSON provided"

    it "should resolve if an empty JSON array is provided", ->
      waitsForPromise ->
        NylasAPI._handleModelResponse([])
        .then (resp) ->
          expect(resp).toEqual []

    describe "if JSON contains objects which are of unknown types", ->
      it "should warn and resolve", ->
        spyOn(console, "warn")
        waitsForPromise ->
          NylasAPI._handleModelResponse([{id: 'a', object: 'unknown'}])
          .then (resp) ->
            expect(resp).toEqual []
            expect(console.warn).toHaveBeenCalled()
            expect(console.warn.calls.length).toBe 1

    describe "if JSON contains the same object more than once", ->
      beforeEach ->
        stubDB(models: [])
        spyOn(console, "warn")
        @dupes = [
          {id: 'a', object: 'thread'}
          {id: 'a', object: 'thread'}
          {id: 'b', object: 'thread'}
        ]

      it "should warn", ->
        waitsForPromise =>
          NylasAPI._handleModelResponse(@dupes)
          .then ->
            expect(console.warn).toHaveBeenCalled()
            expect(console.warn.calls.length).toBe 1

      it "should omit duplicates", ->
        waitsForPromise =>
          NylasAPI._handleModelResponse(@dupes)
          .then ->
            models = DatabaseStore.persistModels.calls[0].args[0]
            expect(models.length).toBe 2
            expect(models[0].id).toBe 'a'
            expect(models[1].id).toBe 'b'

    describe "when locked by the optimistic change tracker", ->
      it "should remove locked models from the set", ->
        json = [
          {id: 'a', object: 'thread'}
          {id: 'b', object: 'thread'}
        ]
        spyOn(NylasAPI._optimisticChangeTracker, "acceptRemoteChangesTo").andCallFake (klass, id) ->
          if id is "a" then return false

        stubDB models: [new Thread(json[1])], testMatcher: (whereMatcher) ->
          expect(whereMatcher.val).toEqual ['b']

        waitsForPromise =>
          NylasAPI._handleModelResponse(json)
          .then (models) ->
            expect(models.length).toBe 1
            models = DatabaseStore.persistModels.calls[0].args[0]
            expect(models.length).toBe 1
            expect(models[0].id).toBe 'b'

    describe "when updating models", ->
      Message = require '../src/flux/models/message'
      beforeEach ->
        @json = [
          {id: 'a', object: 'draft', unread: true}
          {id: 'b', object: 'draft', starred: true}
        ]
        @existing = new Message(id: 'b', unread: true)
        stubDB models: [@existing]

      verifyUpdateHappened = (responseModels) ->
        changedModels = DatabaseStore.persistModels.calls[0].args[0]
        expect(changedModels.length).toBe 2
        expect(changedModels[1].id).toBe 'b'
        expect(changedModels[1].starred).toBe true
        # Doesn't override existing values
        expect(changedModels[1].unread).toBe true
        expect(responseModels.length).toBe 2
        expect(responseModels[0].id).toBe 'a'
        expect(responseModels[0].unread).toBe true

      it "updates found models with new data", ->
        waitsForPromise =>
          NylasAPI._handleModelResponse(@json).then verifyUpdateHappened

      it "updates if the json version is newer", ->
        @existing.version = 9
        @json[1].version = 10
        waitsForPromise =>
          NylasAPI._handleModelResponse(@json).then verifyUpdateHappened

      verifyUpdateStopped = (responseModels) ->
        changedModels = DatabaseStore.persistModels.calls[0].args[0]
        expect(changedModels.length).toBe 1
        expect(changedModels[0].id).toBe 'a'
        expect(changedModels[0].unread).toBe true
        expect(responseModels.length).toBe 2
        expect(responseModels[1].id).toBe 'b'
        expect(responseModels[1].starred).toBeUndefined()

      it "doesn't update if the json version is older", ->
        @existing.version = 10
        @json[1].version = 9
        waitsForPromise =>
          NylasAPI._handleModelResponse(@json).then verifyUpdateStopped

      it "doesn't update if it's already sent", ->
        @existing.draft = false
        @json[1].draft = true
        waitsForPromise =>
          NylasAPI._handleModelResponse(@json).then verifyUpdateStopped

    describe "handling all types of objects", ->
      apiObjectToClassMap =
        "file": require('../src/flux/models/file')
        "event": require('../src/flux/models/event')
        "label": require('../src/flux/models/label')
        "folder": require('../src/flux/models/folder')
        "thread": require('../src/flux/models/thread')
        "draft": require('../src/flux/models/message')
        "account": require('../src/flux/models/account')
        "message": require('../src/flux/models/message')
        "contact": require('../src/flux/models/contact')
        "calendar": require('../src/flux/models/calendar')
        "metadata": require('../src/flux/models/metadata')

      verifyUpdateHappened = (klass, responseModels) ->
        changedModels = DatabaseStore.persistModels.calls[0].args[0]
        expect(changedModels.length).toBe 2
        expect(changedModels[0].id).toBe 'a'
        expect(changedModels[1].id).toBe 'b'
        expect(changedModels[0] instanceof klass).toBe true
        expect(changedModels[1] instanceof klass).toBe true
        expect(responseModels.length).toBe 2
        expect(responseModels[0].id).toBe 'a'
        expect(responseModels[1].id).toBe 'b'
        expect(responseModels[0] instanceof klass).toBe true
        expect(responseModels[1] instanceof klass).toBe true

      _.forEach apiObjectToClassMap, (klass, type) ->
        it "properly handle the '#{type}' type", ->
          json = [
            {id: 'a', object: type}
            {id: 'b', object: type}
          ]
          stubDB models: [new klass(id: 'b')]

          verifyUpdate = _.partial(verifyUpdateHappened, klass)
          waitsForPromise =>
            NylasAPI._handleModelResponse(json).then verifyUpdate
