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

ADOBE CONFIDENTIAL
------------------
Copyright 2019 Adobe
All Rights Reserved.

NOTICE: All information contained herein is, and remains
the property of Adobe and its suppliers, if any. The intellectual
and technical concepts contained herein are proprietary to Adobe
and its suppliers and are protected by all applicable intellectual
property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Adobe.

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

	-- RxLua
local Rx = require 'Rx'
local Observable = Rx.Observable

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

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

	-- WF.oz_client.managed_catalog.sync
local WFCatSyncClientBase = require 'WFCatSyncClientBase'
local WFOzUserTestSession = require 'WFOzUserTestSession'
local WFCatUser = require 'WFCatUser'
local WFCatalog = require 'WFCatalog'
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFCatSpecMock = require 'WFCatSpecMock'
local WFCatOzTestSession = require 'WFCatOzTestSession'
local WFPlatformInfo = require 'WFPlatformInfo'

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

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

local check = assert( WFArguments.check )

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

-- Common setup function for space prioritization tests
local function setupSpacePrioritizationTest( rxTest )
	
	-- Create a simple mock catalog without complex HTTP responses
	local wfCatalog = WFCatMockCatalog.createFakeWFCatalog( rxTest )
	local optIndexSpecScript = wfCatalog:indexStore():indexSpecScript() or 'WFCatIndexDefinitions.lua'
	
	local syncClientOptions = {
		serverBase = wfCatalog:ozUser():serverBaseUrl(),
		userId = wfCatalog:ozUserId(),
		userInfoCacheFolder = wfCatalog:ozUser():userInfoCacheFolder(),
		catalogId = wfCatalog:ozCatalogId(),
		rootCatalogPath = wfCatalog:fileSpec():rootPath(),
		docStorePath = wfCatalog:fileSpec():docStorePath(),
		indexSpecScript = optIndexSpecScript,
		testMode = true -- Only used by unit tests
	}

	local syncClientBase = WFCatSyncClientBase( syncClientOptions )

	-- Access the space feeds manager and catalog puller for verification
	local spaceFeedsManager = syncClientBase:getSpaceFeedsManager()
	local catalogPuller = syncClientBase:getCatalogPuller()

	-- Verify that space feeds manager is available
	rxTest:expectNotEqual( spaceFeedsManager, false )
	rxTest:expectNotEqual( catalogPuller, false )

	-- Test space IDs
	local spaceId1 = KSIdGenerator.uuidInLowercaseHex()
	local spaceId2 = KSIdGenerator.uuidInLowercaseHex()
	
	-- Return all the objects needed for testing
	return {
		syncClientBase = syncClientBase,
		spaceFeedsManager = spaceFeedsManager,
		catalogPuller = catalogPuller,
		spaceId1 = spaceId1,
		spaceId2 = spaceId2
	}
end

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

-- @mark WFCatSyncClientBaseTest namespace

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

local WFCatSyncClientBaseTest = {}

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

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 WFCatSyncClientBaseTest.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 = "WFCatSyncClientBaseTest 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 optIndexSpecScript = catalog:indexStore():indexSpecScript() 
								or 'WFCatIndexDefinitions.lua'
								
	local optSyncClientImpName = 'WFCatSyncClientImp'
	
	local options = {
		syncClientImpName = optSyncClientImpName,
		serverBase = catalog:ozUser():serverBaseUrl(),
		userId = catalog:ozUserId(),
		userInfoCacheFolder = catalog:ozUser():userInfoCacheFolder(),
		catalogId = catalog:ozCatalogId(),
		catalogDoc = catalog:ozCatalogSpec():catalogDoc(),
		indexSpecScript = optIndexSpecScript,
		rootCatalogPath = catalog:fileSpec():rootPath(),
		docStorePath = catalog:fileSpec():docStorePath(),
		testMode = true -- Only used by unit tests.
	}
	
	local syncClientBase = WFCatSyncClientBase( options )
	
	rxTest:expectOnlyCompleted( syncClientBase:startSync( true ) )
	
	-- 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 = "WFCatSyncClientBaseTest 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 = "WFCatSyncClientBaseTest 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 ),
		'WFCatSyncClientBaseTest album 2' )
	
	-- stop sync so local changes don't get up to Oz
	
	rxTest:expectOnlyCompleted( syncClientBase:stopSync() )
	
	rxTest:sleep( 5 )

	-- change local value
	
	rxTest:expectSingleValue(

		catalog:docStore():applyOverlay {
			_id = albumId,
			name = "WFCatSyncClientBaseTest 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 ),
		'WFCatSyncClientBaseTest album 2' )
		
	syncClientBase:close()
	
end

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

function WFCatSyncClientBaseTest.rxTestPrioritizeSpaceSync( rxTest )

	rxTest:setTimeLimit( 5 )

	-- Test the complete WFCatSyncClientBase:prioritizeSpaceSync functionality:
	-- 1. Pause catalog puller
	-- 2. Delegate to space feeds manager
	-- 3. Set up 2-second auto-resume timer
	-- 4. Verify prioritization state management
	-- 5. Test 2-second timeout behavior

	local testData = setupSpacePrioritizationTest( rxTest )
	local syncClientBase = testData.syncClientBase
	local spaceFeedsManager = testData.spaceFeedsManager
	local catalogPuller = testData.catalogPuller
	local spaceId1 = testData.spaceId1

	-- Note: We don't start the catalog puller with getUpdatesOnce() because it requires
	-- authentication tokens that aren't available in the mock catalog.
	-- Instead, we simulate the catalog puller running by manually starting it.

	syncClientBase:startSync()
	
	-- Verify catalog puller is running initially
	rxTest:expectEqual( catalogPuller._syncIsStopped, false )

	-- Test 1: Call WFCatSyncClientBase:prioritizeSpaceSync
	local prioritizeResult = syncClientBase:prioritizeSpaceSync( spaceId1 )

	-- Verify the method returns an Observable that emits true
	rxTest:expectSingleValue( prioritizeResult, true )

	-- Test 2: Verify catalog puller was paused immediately
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )

	-- Test 3: Verify space feeds manager prioritization state
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, spaceId1 )

	-- Test 4: Verify state persists for a short time
	rxTest:waitFor( Observable.timer( 1, WFRxScheduler.sharedScheduler() ) )
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, spaceId1 )
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )

	-- Test 5: Wait for 2-second timeout and verify auto-resume behavior
	-- Note: We use a slightly longer wait to account for timing variations
	rxTest:waitFor( Observable.timer( 2, WFRxScheduler.sharedScheduler() ) )

	-- After 2 seconds, the timer should have fired and:
	-- 1. Catalog puller should be resumed (since it was running before)
	-- 2. Space prioritization should be stopped
	rxTest:expectEqual( catalogPuller._syncIsStopped, false )  -- Should be resumed
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, false )  -- Should be cleared

	-- Clean up
	rxTest:finally(function() syncClientBase:close() end)

end

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

function WFCatSyncClientBaseTest.rxTestPrioritizeSpaceSyncFast( rxTest )

	rxTest:setTimeLimit( 5 )

	-- Test the immediate effects of WFCatSyncClientBase:prioritizeSpaceSync functionality
	-- without waiting for the 2-second timeout (for faster test execution)

	local testData = setupSpacePrioritizationTest( rxTest )
	local syncClientBase = testData.syncClientBase
	local spaceFeedsManager = testData.spaceFeedsManager
	local catalogPuller = testData.catalogPuller
	local spaceId1 = testData.spaceId1
	local spaceId2 = testData.spaceId2
	
	syncClientBase:startSync()
	rxTest:expectEqual( catalogPuller._syncIsStopped, false )
	
	-- Test 1: Call WFCatSyncClientBase:prioritizeSpaceSync
	local prioritizeResult = syncClientBase:prioritizeSpaceSync( spaceId1 )

	-- Verify the method returns an Observable that emits true
	rxTest:expectSingleValue( prioritizeResult, true )

	-- Test 2: Verify catalog puller was paused
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )

	-- Test 3: Verify space feeds manager prioritization state
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, spaceId1 )

	-- Test 4: Verify that timer is set up and state persists
	rxTest:waitFor( Observable.timer( 0.1, WFRxScheduler.sharedScheduler() ) )
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, spaceId1 )
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )

	-- Test 5: Test prioritization of a different space
	local prioritizeResult2 = syncClientBase:prioritizeSpaceSync( spaceId2 )
	rxTest:expectSingleValue( prioritizeResult2, true )

	-- Should switch to the new space
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, spaceId2 )

	-- Clean up
	rxTest:finally(function() syncClientBase:close() end)

end

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

function WFCatSyncClientBaseTest.rxTestPrioritizeSpaceSyncWhenCatalogPullerStopped( rxTest )

	rxTest:setTimeLimit( 5 )

	-- Test the prioritizeSpaceSync behavior when catalog puller is initially stopped
	-- In this case, the catalog puller should remain stopped after the timeout

	local testData = setupSpacePrioritizationTest( rxTest )
	local syncClientBase = testData.syncClientBase
	local spaceFeedsManager = testData.spaceFeedsManager
	local catalogPuller = testData.catalogPuller
	local spaceId1 = testData.spaceId1

	-- Ensure catalog puller is stopped initially (default state)
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )

	-- Test 1: Call WFCatSyncClientBase:prioritizeSpaceSync
	local prioritizeResult = syncClientBase:prioritizeSpaceSync( spaceId1 )

	-- Verify the method returns an Observable that emits true
	rxTest:expectSingleValue( prioritizeResult, true )

	-- Test 2: Verify catalog puller remains stopped (it was already stopped)
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )

	-- Test 3: Verify space feeds manager prioritization state
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, spaceId1 )

	-- Test 4: Wait for 2-second timeout and verify behavior
	rxTest:waitFor( Observable.timer( 2, WFRxScheduler.sharedScheduler() ) )

	-- After 2 seconds, the timer should have fired and:
	-- 1. Catalog puller should remain stopped (since it wasn't running before)
	-- 2. Space prioritization should be stopped
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )  -- Should remain stopped
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, false )  -- Should be cleared

	-- Clean up
	rxTest:finally(function() syncClientBase:close() end)

end

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

function WFCatSyncClientBaseTest.rxTestStartStopSyncNormalCase( rxTest )

	rxTest:setTimeLimit( 5 )

	-- Test that startSync and stopSync work normally when there is no
	-- space prioritization active (no harm from handleSyncCallDuringSpacePrioritization)

	local testData = setupSpacePrioritizationTest( rxTest )
	local syncClientBase = testData.syncClientBase
	local spaceFeedsManager = testData.spaceFeedsManager
	local catalogPuller = testData.catalogPuller
	
	-- Verify initial state
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, false )
	
	-- Verify initial state - no prioritization active
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )
	
	-- Test startSync works normally
	local startResult = syncClientBase:startSync()
	rxTest:expectEqual( catalogPuller._syncIsStopped, false )
	
	-- Verify flag state hasn't changed
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )
	
	-- Test stopSync works normally
	local stopResult = syncClientBase:stopSync()
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )
	
	-- Verify flag state still hasn't changed
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )
	
	-- Test multiple calls work normally
	syncClientBase:startSync()
	rxTest:expectEqual( catalogPuller._syncIsStopped, false )
	
	-- Verify flag state remains unchanged
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )
	
	syncClientBase:stopSync()
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )
	
	-- Verify flag state still remains unchanged
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )
	
	-- Clean up
	rxTest:finally(function() syncClientBase:close() end)

end

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

function WFCatSyncClientBaseTest.rxTestStartSyncInterruptsPrioritization( rxTest )

	rxTest:setTimeLimit( 5 )

	-- Test that startSync during space prioritization interrupts the prioritization
	-- and calls stopPrioritizeSpaceSync without calling resumeNormalSyncBehavior

	local testData = setupSpacePrioritizationTest( rxTest )
	local syncClientBase = testData.syncClientBase
	local spaceFeedsManager = testData.spaceFeedsManager
	local catalogPuller = testData.catalogPuller
	local spaceId1 = testData.spaceId1
	
	-- Start with catalog puller stopped
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )
	
	-- Verify initial state
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )
	
	-- Start space prioritization
	local prioritizeResult = syncClientBase:prioritizeSpaceSync( spaceId1 )
	rxTest:expectSingleValue( prioritizeResult, true )
	
	-- Verify prioritization is active
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, spaceId1 )
	
	-- Verify prioritization state was properly set
	rxTest:expectNotEqual( syncClientBase._spacePrioritizationTimeoutSubscription, nil )
	rxTest:expectNotEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )
	
	-- Call startSync during prioritization - this should interrupt
	syncClientBase:startSync()
	
	-- Verify prioritization was interrupted
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, false )  -- Cleared
	rxTest:expectEqual( catalogPuller._syncIsStopped, false )  -- Resumed by startSync
	
	-- Verify prioritization was interrupted and flags were cleared
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, nil )
	
	-- Wait to ensure timeout doesn't trigger
	rxTest:waitFor( Observable.timer( 3, WFRxScheduler.sharedScheduler() ) )
	
	-- Verify state remains as expected (no additional changes from timeout)
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, false )
	rxTest:expectEqual( catalogPuller._syncIsStopped, false )
	
	-- Clean up
	rxTest:finally(function() syncClientBase:close() end)

end

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

function WFCatSyncClientBaseTest.rxTestStopSyncInterruptsPrioritization( rxTest )

	rxTest:setTimeLimit( 5 )

	-- Test that stopSync during space prioritization interrupts the prioritization
	-- and calls stopPrioritizeSpaceSync without calling resumeNormalSyncBehavior

	local testData = setupSpacePrioritizationTest( rxTest )
	local syncClientBase = testData.syncClientBase
	local spaceFeedsManager = testData.spaceFeedsManager
	local catalogPuller = testData.catalogPuller
	local spaceId1 = testData.spaceId1
	
	-- Start with catalog puller running
	syncClientBase:startSync()
	rxTest:expectEqual( catalogPuller._syncIsStopped, false )
	
	-- Verify initial state
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )
	
	-- Start space prioritization
	local prioritizeResult = syncClientBase:prioritizeSpaceSync( spaceId1 )
	rxTest:expectSingleValue( prioritizeResult, true )
	
	-- Verify prioritization is active and puller is stopped
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, spaceId1 )
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )
	
	-- Verify prioritization state was properly set
	rxTest:expectNotEqual( syncClientBase._spacePrioritizationTimeoutSubscription, nil )
	rxTest:expectNotEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )
	
	-- Call stopSync during prioritization - this should interrupt
	syncClientBase:stopSync()
	
	-- Verify prioritization was interrupted
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, false )  -- Cleared
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )  -- Stopped by stopSync
	
	-- Verify prioritization was interrupted and flags were cleared
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, nil )
	
	-- Wait to ensure timeout doesn't trigger
	rxTest:waitFor( Observable.timer( 3, WFRxScheduler.sharedScheduler() ) )
	
	-- Verify state remains as expected (no additional changes from timeout)
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, false )
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )
	
	-- Verify flags remain cleared
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, nil )
	
	-- Clean up
	rxTest:finally(function() syncClientBase:close() end)

end

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

function WFCatSyncClientBaseTest.rxTestPrioritizationTimeoutCompletesNaturally( rxTest )

	rxTest:setTimeLimit( 5 )

	-- Test that when no sync calls interrupt prioritization, the timeout
	-- completes naturally and calls resumeNormalSyncBehavior

	local testData = setupSpacePrioritizationTest( rxTest )
	local syncClientBase = testData.syncClientBase
	local spaceFeedsManager = testData.spaceFeedsManager
	local catalogPuller = testData.catalogPuller
	local spaceId1 = testData.spaceId1
	
	-- Start with catalog puller running
	syncClientBase:startSync()
	rxTest:expectEqual( catalogPuller._syncIsStopped, false )
	
	-- Verify initial state
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )
	
	-- Start space prioritization
	local prioritizeResult = syncClientBase:prioritizeSpaceSync( spaceId1 )
	rxTest:expectSingleValue( prioritizeResult, true )
	
	-- Verify prioritization is active and puller is stopped
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, spaceId1 )
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )
	
	-- Verify prioritization state was properly set
	rxTest:expectNotEqual( syncClientBase._spacePrioritizationTimeoutSubscription, nil )
	rxTest:expectNotEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )
	
	-- Wait for timeout to complete naturally (2 seconds in unit tests)
	rxTest:waitFor( Observable.timer( 3, WFRxScheduler.sharedScheduler() ) )
	
	-- Verify timeout completed and normal sync behavior resumed
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, false )  -- Cleared
	rxTest:expectEqual( catalogPuller._syncIsStopped, false )  -- Resumed to original state
	
	-- Verify flags were cleared after natural timeout completion
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, nil )
	
	-- Clean up
	rxTest:finally(function() syncClientBase:close() end)

end

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

function WFCatSyncClientBaseTest.rxTestPrioritizeSpaceSyncRepeatedlyResumesProperly( rxTest )

	rxTest:setTimeLimit( 8 )

	-- Test that calling prioritizeSpaceSync() twice with the same space ID
	-- correctly preserves the original catalog running state and resumes sync properly.
	-- This tests the fix for the issue where repeated calls would always result in
	-- catalogWasRunning being false, preventing normal sync from resuming.

	local testData = setupSpacePrioritizationTest( rxTest )
	local syncClientBase = testData.syncClientBase
	local spaceFeedsManager = testData.spaceFeedsManager
	local catalogPuller = testData.catalogPuller
	local spaceId1 = testData.spaceId1

	-- Start with catalog puller running
	syncClientBase:startSync()
	rxTest:expectEqual( catalogPuller._syncIsStopped, false )

	-- Verify initial state - no prioritization active
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )
	rxTest:expectEqual( syncClientBase._originalCatalogWasRunning, false )

	-- Test 1: First call to prioritizeSpaceSync
	local prioritizeResult1 = syncClientBase:prioritizeSpaceSync( spaceId1 )
	rxTest:expectSingleValue( prioritizeResult1, true )

	-- Verify catalog puller was paused and original state was captured
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )  -- Paused
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, spaceId1 )
	rxTest:expectEqual( syncClientBase._originalCatalogWasRunning, true )  -- Original state preserved
	rxTest:expectNotEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )

	-- Test 2: Second call to prioritizeSpaceSync with the same space ID
	-- This should preserve the original catalog running state
	local prioritizeResult2 = syncClientBase:prioritizeSpaceSync( spaceId1 )
	rxTest:expectSingleValue( prioritizeResult2, true )

	-- Verify that:
	-- 1. Catalog puller remains paused
	-- 2. Space prioritization continues with the same space
	-- 3. Original catalog running state is still preserved (this is the key fix)
	rxTest:expectEqual( catalogPuller._syncIsStopped, true )  -- Still paused
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, spaceId1 )  -- Same space
	rxTest:expectEqual( syncClientBase._originalCatalogWasRunning, true )  -- CRITICAL: Still true
	rxTest:expectNotEqual( syncClientBase._spacePrioritizationTimeoutSubscription, false )

	-- Test 3: Wait for natural timeout completion (2 seconds in unit tests)
	rxTest:waitFor( Observable.timer( 3, WFRxScheduler.sharedScheduler() ) )

	-- Test 4: Verify that normal sync behavior is properly resumed
	-- This is the key verification - because we preserved _originalCatalogWasRunning = true,
	-- the catalog puller should be resumed even after multiple prioritization calls
	rxTest:expectEqual( catalogPuller._syncIsStopped, false )  -- CRITICAL: Should be resumed
	rxTest:expectEqual( spaceFeedsManager._prioritizedSpaceId, false )  -- Prioritization cleared
	rxTest:expectEqual( syncClientBase._spacePrioritizationTimeoutSubscription, nil )  -- Timeout cleared
	rxTest:expectEqual( syncClientBase._originalCatalogWasRunning, nil )  -- Original state cleared

end

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

return WFRxTestCase.wrapSuite( 'WFCatSyncClientBaseTest', WFCatSyncClientBaseTest )