_ = require 'underscore'
Contact = require '../../../src/flux/models/contact'
Message = require '../../../src/flux/models/message'
Thread = require '../../../src/flux/models/thread'
Label = require '../../../src/flux/models/label'
CategoryStore = require '../../../src/flux/stores/category-store'
DatabaseStore = require '../../../src/flux/stores/database-store'
AccountStore = require '../../../src/flux/stores/account-store'
SoundRegistry = require '../../../src/sound-registry'
NativeNotifications = require '../../../src/native-notifications'
Main = require '../lib/main'

describe "UnreadNotifications", ->
  beforeEach ->
    Main.activate()

    inbox = new Label(id: "l1", name: "inbox", displayName: "Inbox")
    archive = new Label(id: "l2", name: "archive", displayName: "Archive")

    spyOn(CategoryStore, "getStandardCategory").andReturn inbox

    @threadA = new Thread
      labels: [inbox]
    @threadB = new Thread
      labels: [archive]

    @msg1 = new Message
      unread: true
      date: new Date()
      from: [new Contact(name: 'Ben', email: 'ben@example.com')]
      subject: "Hello World"
      threadId: "A"
    @msgNoSender = new Message
      unread: true
      date: new Date()
      from: []
      subject: "Hello World"
      threadId: "A"
    @msg2 = new Message
      unread: true
      date: new Date()
      from: [new Contact(name: 'Mark', email: 'mark@example.com')]
      subject: "Hello World 2"
      threadId: "A"
    @msg3 = new Message
      unread: true
      date: new Date()
      from: [new Contact(name: 'Ben', email: 'ben@example.com')]
      subject: "Hello World"
      threadId: "A"
    @msg4 = new Message
      unread: true
      date: new Date()
      from: [new Contact(name: 'Ben', email: 'ben@example.com')]
      subject: "Hello World"
      threadId: "A"
    @msg5 = new Message
      unread: true
      date: new Date()
      from: [new Contact(name: 'Ben', email: 'ben@example.com')]
      subject: "Hello World"
      threadId: "A"
    @msgUnreadButArchived = new Message
      unread: true
      date: new Date()
      from: [new Contact(name: 'Mark', email: 'mark@example.com')]
      subject: "Hello World 2"
      threadId: "B"
    @msgRead = new Message
      unread: false
      date: new Date()
      from: [new Contact(name: 'Mark', email: 'mark@example.com')]
      subject: "Hello World Read Already"
      threadId: "A"
    @msgOld = new Message
      unread: true
      date: new Date(2000,1,1)
      from: [new Contact(name: 'Mark', email: 'mark@example.com')]
      subject: "Hello World Old"
      threadId: "A"
    @msgFromMe = new Message
      unread: true
      date: new Date()
      from: [AccountStore.current().me()]
      subject: "A Sent Mail!"
      threadId: "A"

    spyOn(DatabaseStore, 'find').andCallFake (klass, id) =>
      return Promise.resolve(@threadA) if id is 'A'
      return Promise.resolve(@threadB) if id is 'B'
      return Promise.resolve(null)

    spyOn(NativeNotifications, 'displayNotification').andCallFake ->
    spyOn(Promise, 'props').andCallFake (dict) ->
      dictOut = {}
      for key, val of dict
        if val.value?
          dictOut[key] = val.value()
        else
          dictOut[key] = val
      Promise.resolve(dictOut)

  afterEach ->
    Main.deactivate()

  it "should create a Notification if there is one unread message", ->
    waitsForPromise =>
      Main._onNewMailReceived({message: [@msgRead, @msg1]})
      .then ->
        advanceClock(2000)
        expect(NativeNotifications.displayNotification).toHaveBeenCalled()
        options = NativeNotifications.displayNotification.mostRecentCall.args[0]
        delete options['onActivate']
        expect(options).toEqual({
          title: 'Ben',
          subtitle: 'Hello World',
          body: undefined,
          canReply: true,
          tag: 'unread-update'
        })

  it "should create multiple Notifications if there is more than one but less than five unread messages", ->
    waitsForPromise =>
      Main._onNewMailReceived({message: [@msg1, @msg2, @msg3]})
      .then ->
        #Need to call advance clock twice because we call setTimeout twice
        advanceClock(2000)
        advanceClock(2000)
        expect(NativeNotifications.displayNotification.callCount).toEqual(3)

  it "should create a Notification if there are five or more unread messages", ->
    waitsForPromise =>
      Main._onNewMailReceived({
        message: [@msg1, @msg2, @msg3, @msg4, @msg5]})
      .then ->
        advanceClock(2000)
        expect(NativeNotifications.displayNotification).toHaveBeenCalled()
        expect(NativeNotifications.displayNotification.mostRecentCall.args).toEqual([{
          title: '5 Unread Messages',
          tag: 'unread-update'
        }])

  it "should create a Notification correctly, even if new mail has no sender", ->
    waitsForPromise =>
      Main._onNewMailReceived({message: [@msgNoSender]})
      .then ->
        expect(NativeNotifications.displayNotification).toHaveBeenCalled()

        options = NativeNotifications.displayNotification.mostRecentCall.args[0]
        delete options['onActivate']
        expect(options).toEqual({
          title: 'Unknown',
          subtitle: 'Hello World',
          body: undefined,
          canReply : true,
          tag: 'unread-update'
        })

  it "should not create a Notification if there are no new messages", ->
    waitsForPromise ->
      Main._onNewMailReceived({message: []})
      .then ->
        expect(NativeNotifications.displayNotification).not.toHaveBeenCalled()

    waitsForPromise ->
      Main._onNewMailReceived({})
      .then ->
        expect(NativeNotifications.displayNotification).not.toHaveBeenCalled()

  it "should not notify about unread messages that are outside the inbox", ->
    waitsForPromise =>
      Main._onNewMailReceived({message: [@msgUnreadButArchived, @msg1]})
      .then ->
        expect(NativeNotifications.displayNotification).toHaveBeenCalled()
        options = NativeNotifications.displayNotification.mostRecentCall.args[0]
        delete options['onActivate']
        expect(options).toEqual({
          title: 'Ben',
          subtitle: 'Hello World',
          body: undefined,
          canReply : true,
          tag: 'unread-update'
        })

  it "should not create a Notification if the new messages are not unread", ->
    waitsForPromise =>
      Main._onNewMailReceived({message: [@msgRead]})
      .then ->
        expect(NativeNotifications.displayNotification).not.toHaveBeenCalled()

  it "should not create a Notification if the new messages are actually old ones", ->
    waitsForPromise =>
      Main._onNewMailReceived({message: [@msgOld]})
      .then ->
        expect(NativeNotifications.displayNotification).not.toHaveBeenCalled()

  it "should not create a Notification if the new message is one I sent", ->
    waitsForPromise =>
      Main._onNewMailReceived({message: [@msgFromMe]})
      .then ->
        expect(NativeNotifications.displayNotification).not.toHaveBeenCalled()

  it "should play a sound when it gets new mail", ->
    spyOn(atom.config, "get").andCallFake (config) ->
      if config is "core.notifications.enabled" then return true
      if config is "core.notifications.sounds" then return true

    spyOn(SoundRegistry, "playSound")
    waitsForPromise =>
      Main._onNewMailReceived({message: [@msg1]})
      .then ->
        expect(atom.config.get.calls[1].args[0]).toBe "core.notifications.sounds"
        expect(SoundRegistry.playSound).toHaveBeenCalledWith("new-mail")

  it "should not play a sound if the config is off", ->
    spyOn(atom.config, "get").andCallFake (config) ->
      if config is "core.notifications.enabled" then return true
      if config is "core.notifications.sounds" then return false
    spyOn(SoundRegistry, "playSound")
    waitsForPromise =>
      Main._onNewMailReceived({message: [@msg1]})
      .then ->
        expect(atom.config.get.calls[1].args[0]).toBe "core.notifications.sounds"
        expect(SoundRegistry.playSound).not.toHaveBeenCalled()

  describe "when the message has no matching thread", ->
    beforeEach ->
      @msgNoThread = new Message
        unread: true
        date: new Date()
        from: [new Contact(name: 'Ben', email: 'ben@example.com')]
        subject: "Hello World"
        threadId: "missing"

    it "should not create a Notification, since it cannot be determined whether the message is in the Inbox", ->
      waitsForPromise =>
        Main._onNewMailReceived({message: [@msgNoThread]})
        .then ->
          advanceClock(2000)
          expect(NativeNotifications.displayNotification).not.toHaveBeenCalled()

    it "should call _onNewMessagesMissingThreads to try displaying a notification again in 10 seconds", ->
      waitsForPromise =>
        spyOn(Main, '_onNewMessagesMissingThreads')
        Main._onNewMailReceived({message: [@msgNoThread]})
        .then =>
          advanceClock(2000)
          expect(Main._onNewMessagesMissingThreads).toHaveBeenCalledWith([@msgNoThread])

  describe "_onNewMessagesMissingThreads", ->
    beforeEach ->
      @msgNoThread = new Message
        unread: true
        date: new Date()
        from: [new Contact(name: 'Ben', email: 'ben@example.com')]
        subject: "Hello World"
        threadId: "missing"
      spyOn(Main, '_onNewMailReceived')
      Main._onNewMessagesMissingThreads([@msgNoThread])
      advanceClock(2000)

    it "should wait 10 seconds and then re-query for threads", ->
      expect(DatabaseStore.find).not.toHaveBeenCalled()
      @msgNoThread.threadId = "A"
      advanceClock(10000)
      expect(DatabaseStore.find).toHaveBeenCalled()
      advanceClock()
      expect(Main._onNewMailReceived).toHaveBeenCalledWith({message: [@msgNoThread], thread: [@threadA]})

    it "should do nothing if the threads still can't be found", ->
      expect(DatabaseStore.find).not.toHaveBeenCalled()
      advanceClock(10000)
      expect(DatabaseStore.find).toHaveBeenCalled()
      advanceClock()
      expect(Main._onNewMailReceived).not.toHaveBeenCalled()
