SandboxedModule = require('sandboxed-module')
sinon = require('sinon')
require('chai').should()
modulePath = require('path').join __dirname, '../../../../app/js/Features/Editor/EditorUpdatesController'
MockClient = require "../helpers/MockClient"
assert = require('assert')

describe "EditorUpdatesController", ->
	beforeEach ->
		@project_id = "project-id-123"
		@doc_id = "doc-id-123"
		@client = new MockClient()
		@callback = sinon.stub()
		@EditorUpdatesController = SandboxedModule.require modulePath, requires:
			"logger-sharelatex": @logger = { error: sinon.stub(), log: sinon.stub() }
			"./EditorRealTimeController" : @EditorRealTimeController = {}
			"../DocumentUpdater/DocumentUpdaterHandler" : @DocumentUpdaterHandler = {}
			"../Versioning/AutomaticSnapshotManager" : @AutomaticSnapshotManager = {}
			"../../infrastructure/Metrics" : @metrics = { set: sinon.stub(), inc: sinon.stub() }
			"../../infrastructure/Server" : io: @io = {}
			"redis" : 
				createClient: ()=> 
					@rclient = {auth:->}

	describe "_applyUpdate", ->
		beforeEach ->
			@update = {op: {p: 12, t: "foo"}}
			@client.set("user_id", @user_id = "user-id-123")
			@DocumentUpdaterHandler.queueChange = sinon.stub().callsArg(3)

		describe "succesfully", ->
			beforeEach ->
				@EditorUpdatesController._applyUpdate @client, @project_id, @doc_id, @update, @callback

			it "should queue the update", ->
				@DocumentUpdaterHandler.queueChange
					.calledWith(@project_id, @doc_id, @update)
					.should.equal true

			it "should call the callback", ->
				@callback.called.should.equal true

			it "should update the active users metric", ->
				@metrics.set.calledWith("editor.active-users", @user_id).should.equal true

			it "should update the active projects metric", ->
				@metrics.set.calledWith("editor.active-projects", @project_id).should.equal true

			it "should increment the doc updates", ->
				@metrics.inc.calledWith("editor.doc-update").should.equal true

		describe "unsuccessfully", ->
			beforeEach ->
				@client.disconnect = sinon.stub()
				@DocumentUpdaterHandler.queueChange = sinon.stub().callsArgWith(3, new Error("Something went wrong"))
				@EditorUpdatesController._applyUpdate @client, @project_id, @doc_id, @update, @callback

			it "should disconnect the client", ->
				@client.disconnect.called.should.equal true

			it "should log an error", ->
				@logger.error.called.should.equal true

		describe "when client.take_snapshot is true", ->
			beforeEach ->
				@client.set("take_snapshots", true)
				@AutomaticSnapshotManager.markProjectAsUpdated = sinon.stub()
				@EditorUpdatesController._applyUpdate(@client, @project_id, @doc_id, @update)

			it "should call AutomaticSnapshotManager.markProjectAsUpdated", ->
				@AutomaticSnapshotManager.markProjectAsUpdated.calledWith(@project_id)
					.should.equal true

	describe "applyOtUpdate", ->
		beforeEach ->
			@client.id = "client-id"
			@client.set("user_id", @user_id = "user-id-123")
			@update = {op: {p: 12, t: "foo"}}
			@EditorUpdatesController._applyUpdate = sinon.stub()
			@EditorUpdatesController.applyOtUpdate @client, @project_id, @doc_id, @update

		it "should set the source of the update to the client id", ->
			@update.meta.source.should.equal @client.id

		it "should set the user_id of the update to the user id", ->
			@update.meta.user_id.should.equal @user_id

		it "should apply the update", ->
			@EditorUpdatesController._applyUpdate
				.calledWith(@client, @project_id, @doc_id, @update)
				.should.equal true

	describe "listenForUpdatesFromDocumentUpdater", ->
		beforeEach ->
			@rclient.subscribe = sinon.stub()
			@rclient.on = sinon.stub()
			@EditorUpdatesController.listenForUpdatesFromDocumentUpdater()
		
		it "should subscribe to the doc-updater stream", ->
			@rclient.subscribe.calledWith("applied-ops").should.equal true

		it "should register a callback to handle updates", ->
			@rclient.on.calledWith("message").should.equal true

	describe "_processMessageFromDocumentUpdater", ->
		describe "with update", ->
			beforeEach ->
				@message =
					doc_id: @doc_id
					op: {t: "foo", p: 12}
				@EditorUpdatesController._applyUpdateFromDocumentUpdater = sinon.stub()
				@EditorUpdatesController._processMessageFromDocumentUpdater "applied-ops", JSON.stringify(@message)

			it "should apply the update", ->
				@EditorUpdatesController._applyUpdateFromDocumentUpdater
					.calledWith(@doc_id, @message.op)
					.should.equal true

		describe "with error", ->
			beforeEach ->
				@message =
					doc_id: @doc_id
					error: "Something went wrong"
				@EditorUpdatesController._processErrorFromDocumentUpdater = sinon.stub()
				@EditorUpdatesController._processMessageFromDocumentUpdater "applied-ops", JSON.stringify(@message)

			it "should process the error", ->
				@EditorUpdatesController._processErrorFromDocumentUpdater
					.calledWith(@doc_id, @message.error)
					.should.equal true

	describe "_applyUpdateFromDocumentUpdater", ->
		beforeEach ->
			@sourceClient = new MockClient()
			@otherClients = [new MockClient(), new MockClient()]
			@update =
				op: [ t: "foo", p: 12 ]
				meta: source: @sourceClient.id
				v: @version = 42
				doc: @doc_id
			@io.sockets =
				clients: sinon.stub().returns([@sourceClient, @otherClients...])
			@EditorUpdatesController._applyUpdateFromDocumentUpdater @doc_id, @update

		it "should send a version bump to the source client", ->
			@sourceClient.emit
				.calledWith("otUpdateApplied", v: @version, doc: @doc_id)
				.should.equal true

		it "should get the clients connected to the document", ->
			@io.sockets.clients
				.calledWith(@doc_id)
				.should.equal true

		it "should send the full update to the other clients", ->
			for client in @otherClients
				client.emit
					.calledWith("otUpdateApplied", @update)
					.should.equal true

	describe "_processErrorFromDocumentUpdater", ->
		beforeEach ->
			@clients = [new MockClient(), new MockClient()]
			@io.sockets =
				clients: sinon.stub().returns(@clients)
			@EditorUpdatesController._processErrorFromDocumentUpdater @doc_id, "Something went wrong"

		it "should log out an error", ->
			@logger.error.called.should.equal true

		it "should disconnect all clients in that document", ->
			@io.sockets.clients.calledWith(@doc_id).should.equal true
			for client in @clients
				client.disconnect.called.should.equal true