--[[----------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2016 Adobe Systems Incorporated
All Rights Reserved.

NOTICE: All information contained herein is, and remain the property of
Adobe Systems Incorporated and its suppliers, if any. The intellectual and
technical concepts contained herein are proprietary to Adobe Systems
Incorporated and its suppliers and may be covered by U.S. and Foreign
Patents, patents in process, and are protected by trade secret or copyright
law.  Dissemination of this information or reproduction of this material is
strictly forbidden unless prior written permission is obtained from
Adobe Systems Incorporated.

----------------------------------------------------------------------------]]--

	-- RxLua
local Rx = require 'Rx'

	-- WF.core.utils
local WFArguments = require 'WFArguments'
local KSIdGenerator = require 'KSIdGenerator'
local KSPathUtils = require 'KSPathUtils'
local KSTableUtils = require 'KSTableUtils'

	-- WF.core.self_test
local WFRxTestCase = require 'WFRxTestCase'

	-- WF.oz_client.managed_catalog.sync
local WFCatSyncClient = require 'WFCatSyncClient'
local WFOzUserTestSession = require 'WFOzUserTestSession'
local WFCatUser = require 'WFCatUser'
local WFCatalog = require 'WFCatalog'

	-- WF.web.json
local KSJsonEncoder = require 'KSJsonEncoder'
local KSJsonDecoder = require 'KSJsonDecoder'

--------------------------------------------------------------------------------

local check = assert( WFArguments.check )

--==============================================================================

-- @mark WFCatSyncClientTest namespace

--------------------------------------------------------------------------------

local WFCatSyncClientTest = {}

--------------------------------------------------------------------------------

local function stripWobblyBits( changedDocsResult )

	changedDocsResult.continueFrom = '<number>'

	for i, doc in ipairs( changedDocsResult ) do
	
		doc._serverProvidedMetadata = nil
		
		if doc.type ~= 'asset' then
			doc._revId = nil
		end
		
		if type( doc ) == 'table' then
		
			for key, value in pairs( doc ) do
			
				if key == '_changeCounter' then
				
					doc[ key ] = '<number>'
				
				end
			
			end
		
		end
		
	end

	return changedDocsResult

end

--------------------------------------------------------------------------------

local function sortByDocId( docs )
	table.sort( docs, function( a, b ) return a._id < b._id end )
	return docs
end

--------------------------------------------------------------------------------

local function observeIsOnOz( rxTest, catalog, docId )

	return rxTest:observe( catalog:docStore():ongoingDocValue( docId )
		:map( function( docValue )

			local isLabeledOz = not not ( docValue._labels and docValue._labels.oz )
			
			return isLabeledOz

		end )
	)

end

--------------------------------------------------------------------------------

function WFCatSyncClientTest.rxTestSyncInfrastructureSetup_testNightly( rxTest )

	rxTest:setTimeLimit( 180 )
	rxTest:makeVerbose()
	
	local userTestSession = WFOzUserTestSession( rxTest, { 
											suppressStoreTokens = true,
											v2 = true 
								} )
	
	local user
	local catSpec
	local catalog
	
	local userInfoCachePath = KSPathUtils.child( KSPathUtils.getStandardFilePath 'temp', 'userCache' )
	local docStoreRootPath = KSPathUtils.getStandardFilePath 'temp'
	
	rxTest:expectSingleValue( userTestSession:getCleanOzAccount()
		
		:flatMapLatest( function( userId )
		
			check( 'uuid' )
			
			user = WFCatUser( userTestSession:ozServerBaseUrl(), userId, userInfoCachePath )
			
			rxTest:finally( function()
			
				local userDocStore = user:userInfoCache()
				
				userDocStore:haltAndDelete()
				
			end )
			
			local accessToken, refreshToken = userTestSession:accessAndRefreshTokens()
			
			return user:storeTokens( accessToken, refreshToken )
				:concat( Rx.Observable.just( true ) )
				:flatMapLatest( function()
				
					return user:createCatalog( 'lightroom' )
					
				end )
			
		end )
		
		:map( function( catalogSpec )
		
			check( 'WFCatSpec' )
			
			catSpec = catalogSpec
			
			catalog = WFCatalog( catalogSpec, docStoreRootPath, { } )
			
			rxTest:finally( function()
			
				catalog:docStore():haltAndDelete()
				catalog:indexStore():haltAndDelete()
				
			end )
			
			return true
			
		end ), true )
		
	
	rxTest:expectSingleValue(

		catalog:docStore():changedDocs { fromChangeCounter = 0 },

		{
			continueFrom = 1,
		} )
		
	-- push album doc ...

	local albumId = KSIdGenerator.uuidInLowercaseHex()

	local albumDocUrl = string.format( '%s/albums/%s',
										  catSpec:catalogHref(),
										  albumId )

	local albumDocContent = KSJsonEncoder.encodeOzUploadJson {
		subtype = 'collection',
		payload = {
			name = "WFCatSyncClientTest album",
			
		},
	}

	local albumDocPutArgs = {
		requestHeaders = {
			{ header = 'Content-Type', value = 'application/json' },
			{ header = 'Accept', value = 'application/json' },
		},
	}

	rxTest:expectOnlyCompleted(
		catSpec:put( albumDocUrl, albumDocContent, albumDocPutArgs )
			:filter( function() return false end ) )
	
	local optArgs = {
		testMode = true -- Only used by unit tests.
	}
	
	local syncClient = WFCatSyncClient( catalog, optArgs )
	
	rxTest:expectOnlyCompleted( syncClient:startSync() )
	
	-- check that we got the value from Oz
	
	local expectedDocUpdates = 
		{
			{
				_changeCounter = "<number>",
				_id = catalog:ozCatalogId(),
				_labels = {
					oz = true,
				},
				_localId = 2,
				name = "lightroom",
				subtype = "lightroom",
				type = "catalog",
			},
			{
				_changeCounter = '<number>',
				_id = albumId,
				_labels = {
					oz = true,
				},
				_localId = 1,
				name = "WFCatSyncClientTest album",
				subtype = "collection",
				type = "album",
			},
			continueFrom = '<number>',
		}
	
	local docUpdates = {}
	
	local changedDocsObserver = rxTest:observe(
			catalog:docStore():scannedFilteredMappedChangesFeedSince( "WFCatMetadataPullerTest" , "reportAllChanges", {}, 2 )
		:map( function( doc )
			docUpdates[ #docUpdates + 1 ] = KSTableUtils.shallowcopy( doc )
			return sortByDocId( docUpdates )
		end )
		:map( stripWobblyBits ) )

	changedDocsObserver:expectDataWithinTime( 40, sortByDocId( expectedDocUpdates ) )

	changedDocsObserver:dispose()
	
	-- update album locally so it can sync
	
	rxTest:expectSingleValue(

		catalog:docStore():applyOverlay {
			_id = albumId,
			name = "WFCatSyncClientTest album 2",
		},
		':ignore:' )
		
	-- verify local change is up on Oz
	
	local observeAlbumIsOnOz = observeIsOnOz( rxTest, catalog, albumId )
	observeAlbumIsOnOz:expectDataWithinTime( 40, true )
	
	rxTest:expectSingleValue(
		catSpec:get( albumDocUrl )
			:map( KSJsonDecoder.decodeWebSafeJson )
			:map( function( docValue )
			
				return KSTableUtils.derefOpt( docValue, 'payload', 'name' )
				
			end ),
		'WFCatSyncClientTest album 2' )
	
	-- stop sync so local changes don't get up to Oz
	
	rxTest:expectOnlyCompleted( syncClient:stopSync() )
	
	rxTest:sleep( 5 )

	-- change local value
	
	rxTest:expectSingleValue(

		catalog:docStore():applyOverlay {
			_id = albumId,
			name = "WFCatSyncClientTest reverted",
		},
		':ignore:' )
		
	rxTest:sleep( 5 )
	
	-- run request again to verify that the local change hasn't made it to Oz
	
	rxTest:expectSingleValue(
		catSpec:get( albumDocUrl )
			:map( KSJsonDecoder.decodeWebSafeJson )
			:map( function( docValue )
			
				return KSTableUtils.derefOpt( docValue, 'payload', 'name' )
				
			end ),
		'WFCatSyncClientTest album 2' )
		
	syncClient:close()
	
end

--------------------------------------------------------------------------------

function WFCatSyncClientTest.rxTestCallAllSyncClientBaseMethods_testNightly( rxTest )

	rxTest:setTimeLimit( 30 )
	rxTest:makeVerbose()
	
	local userTestSession = WFOzUserTestSession( rxTest, { 
				suppressStoreTokens = true,
				v2 = true 
			} )
	
	local user
	local catSpec
	local catalog
	
	local userInfoCachePath = KSPathUtils.child( KSPathUtils.getStandardFilePath 'temp', 'userCache' )
	local docStoreRootPath = KSPathUtils.getStandardFilePath 'temp'
	
	rxTest:expectSingleValue( userTestSession:getCleanOzAccount()
		
		:flatMapLatest( function( userId )
		
			check( 'uuid' )
			
			user = WFCatUser( userTestSession:ozServerBaseUrl(), userId, userInfoCachePath )
			
			rxTest:finally( function()
			
				local userDocStore = user:userInfoCache()
				
				userDocStore:haltAndDelete()
				
			end )
			
			local accessToken, refreshToken = userTestSession:accessAndRefreshTokens()
			
			return user:storeTokens( accessToken, refreshToken )
				:concat( Rx.Observable.just( true ) )
				:flatMapLatest( function()
				
					return user:createCatalog( 'lightroom' )
					
				end )
			
		end )
		
		:map( function( catalogSpec )
		
			check( 'WFCatSpec' )
			
			catSpec = catalogSpec
			
			catalog = WFCatalog( catalogSpec, docStoreRootPath, { } )
			
			rxTest:finally( function()
			
				catalog:docStore():haltAndDelete()
				catalog:indexStore():haltAndDelete()
				
			end )
			
			return true
			
		end ), true )

	local optArgs = {
		testMode = true -- Only used by unit tests.
	}
	
	-- The following code verifies that all of the methods within WFCatSyncClientBase 
	-- are exposed as methods on the client-facing WFCatSyncClient object. The code
	-- doesn't verify the methods work as expected, just that they're exposed.
	
	local syncClient = WFCatSyncClient( catalog, optArgs )

	local subscription = syncClient:startSync():subscribe()
	subscription:dispose()
	
	subscription = syncClient:stopSync():subscribe()
	subscription:dispose()
	
	subscription = syncClient:startSyncInResidualMode():subscribe()
	subscription:dispose()
	
	subscription = syncClient:stopSyncInResidualMode():subscribe()
	subscription:dispose()
	
	subscription = syncClient:startSpaceMetadataSyncInFreemiumMode():subscribe()
	subscription:dispose()
	
	subscription = syncClient:stopSpaceMetadataSyncInFreemiumMode():subscribe()
	subscription:dispose()
	
	subscription = syncClient:spaceFeedsMaintenanceNotificationStream():subscribe()
	subscription:dispose()
	
	subscription = syncClient:pausePusherReturningCounter():subscribe()
	subscription:dispose()
	
	subscription = syncClient:resumePusher():subscribe()
	subscription:dispose()
	
	subscription = syncClient:_DEBUG_startMetadataPusher():subscribe()
	subscription:dispose()
	
	subscription = syncClient:_DEBUG_stopMetadataPusher():subscribe()
	subscription:dispose()

	subscription = syncClient:setUserIsOverQuota():subscribe()
	subscription:dispose()
	
	subscription = syncClient:setUserIsExpired():subscribe()
	subscription:dispose()
	
	subscription = syncClient:isUploadingMetadata():subscribe()
	subscription:dispose()
	
	subscription = syncClient:isDownloadingMetadata():subscribe()
	subscription:dispose()
	
	subscription = syncClient:getLatestDownloadedAssetIdsStream():subscribe()
	subscription:dispose()
	
	subscription = syncClient:isOzCatalogDeleted():subscribe()
	subscription:dispose()
	
	subscription = syncClient:getRequiredUpdateStream():subscribe()
	subscription:dispose()
	
	subscription = syncClient:checkOzForUpdates():subscribe()
	subscription:dispose()

	subscription = syncClient:setReadNotification():subscribe()
	subscription:dispose()
	
	subscription = syncClient:setVisitedNotification():subscribe()
	subscription:dispose()
	
	subscription = syncClient:notificationUnreadCount():subscribe()
	subscription:dispose()
	
	subscription = syncClient:notificationList():subscribe()
	subscription:dispose()
	
	subscription = syncClient:notificationKillSwitch():subscribe()
	subscription:dispose()
	
	subscription = syncClient:haltAndDeleteNotificationDb():subscribe()
	subscription:dispose()
	
	subscription = syncClient:getSQLiteFatalErrorCodeStream():subscribe()
	subscription:dispose()

	subscription = syncClient:_DEBUG_simulateCorruptionNotificationDb():subscribe()
	subscription:dispose()
	
	-- Verify return values of debug methods:
	----------------------------------------------------------------------------
	
	rxTest:expectSingleValue( syncClient:_DEBUG_SyncClientReturnsFoo(), 'foo' )
	
	rxTest:expectSingleValue( syncClient:_DEBUG_SyncClientBaseReturnsTrue(), true )
	
	-- Test space sync prioritization method
	subscription = syncClient:prioritizeSpaceSync( "test-space-id" ):subscribe()
	subscription:dispose()
	
	-- Verify non existent methods cause an error:
	----------------------------------------------------------------------------
	
	rxTest:expectOnlyError( syncClient:foo(), ':ignore:' )

	syncClient:close()
	
end

--------------------------------------------------------------------------------

return WFRxTestCase.wrapSuite( 'WFCatSyncClientTest', WFCatSyncClientTest )