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

WFCatStuckSyncUtilsTest.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2021 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'

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

	-- WF.core.utils
local KSIdGenerator = require 'KSIdGenerator'
local KSTableUtils = require 'KSTableUtils'
local WFPlatformInfo = require 'WFPlatformInfo'

	-- WF.managed_catalog
local WFCatAssetTestUtils = require 'WFCatAssetTestUtils'
local WFCatMetadataPusher = require 'WFCatMetadataPusher'
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFCatStuckSyncUtils = require 'WFCatStuckSyncUtils'
local WFCatSyncStatus = require 'WFCatSyncStatus'

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

local function createFakeWFCatalog( rxTest )

	local optArgs = {}
		
	if WFPlatformInfo.isMobile() then
		optArgs.indexDefinitions = 'LrmCatalogIndexDefinitions.lua'
	end

	return WFCatMockCatalog.createFakeWFCatalog( rxTest , nil, nil, optArgs )

end

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

local function sortStuckDocIds( assetIds )

	table.sort( assetIds )
	
	return assetIds

end

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

local WFCatStuckSyncUtilsTest = {}

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

function WFCatStuckSyncUtilsTest.rxTestGetDocsStuckSyncingMetadataStream( rxTest )

	rxTest:setTimeLimit( 10 )

	local wfCatalog = createFakeWFCatalog( rxTest )
	
	-- Start watching for stuck documents
	
	local isNormalSyncRunningStream = Rx.BehaviorSubject( true )
	
	local stuckSyncObserver = rxTest:observe( 
			WFCatStuckSyncUtils.getDocsStuckSyncingMetadataStream( wfCatalog, isNormalSyncRunningStream, 0.1, 0 )
			:map( sortStuckDocIds ) )

	-- Create an unsynced catalog asset
	
	local catalogAssetId = KSIdGenerator.uuidInLowercaseHex()
	local catalogDocValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, catalogAssetId )
	catalogDocValue._serverProvidedMetadata = nil
	
	rxTest:waitFor( wfCatalog:docStore():setDocValue( catalogDocValue ) )
	
	-- Create an unsynced space asset
	
	local spaceAssetId = KSIdGenerator.uuidInLowercaseHex()
	local spaceId = KSIdGenerator.uuidInLowercaseHex()
	
	local spaceDocValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, spaceAssetId )
	spaceDocValue._spaceId = spaceId
	spaceDocValue._serverProvidedMetadata = nil
	
	rxTest:waitFor( wfCatalog:docStore():setDocValue( spaceDocValue ) )
	
	-- Make sure the assets are in a needs metadata uploaded state:
	
	local assetsNeedingMetadataUploadSql
	
	if WFPlatformInfo.isMobile() then -- iOS & Android
		assetsNeedingMetadataUploadSql = [[SELECT docId FROM coreInfo WHERE syncStatus == 1]]
	else -- Desktop
	
		local syncStatusColumnName = WFCatSyncStatus.getSyncStatusColumnName()
		assetsNeedingMetadataUploadSql = string.format( [[SELECT assetId FROM asset_sync_status_ui WHERE %s == 1]], syncStatusColumnName )

	end

	rxTest:expectSingleValue( wfCatalog:indexStore():queryOnce( {
			sql = assetsNeedingMetadataUploadSql,
			quiesceUntilUpToDate = true,
			singleColumnQuery = true
		} ):map( sortStuckDocIds ), 
		sortStuckDocIds( { catalogAssetId, spaceAssetId } ) )
	
	-- And that there's no stuck sync.
	
	stuckSyncObserver:expectDataWithinTime( 5, {} )
	
	-- Now make it look like the catalog pusher skipped the catalog asset.
	
	isNormalSyncRunningStream:onNext( false ) -- disable sync

	local catalogPusherLocalDocValueId = WFCatMetadataPusher.getContinueFromLocalDocValueKey( false )

	local catalogPusherLocalDocValue = {
		_id = catalogPusherLocalDocValueId,
		continueFrom = 2
	}

	rxTest:waitFor( wfCatalog:docStore():setLocalDocValue( catalogPusherLocalDocValue ) )

	-- Verify there's no stuck sync because sync is disabled
	
	stuckSyncObserver:expectDataWithinTime( 5, {} )
	
	-- Enable sync, and verify the stuck catalog asset
	
	isNormalSyncRunningStream:onNext( true )
	
	stuckSyncObserver:expectDataWithinTime( 5, { catalogAssetId } )
	
	-- Do the same for the space pusher
	
	local spacePusherLocalDocValueId = WFCatMetadataPusher.getContinueFromLocalDocValueKey( true )
	
	local spacePusherLocalDocValue = {
		_id = spacePusherLocalDocValueId,
		continueFrom = 3
	}
	
	rxTest:waitFor( wfCatalog:docStore():setLocalDocValue( spacePusherLocalDocValue ) )
	
	-- Verify the stuck catalog and space asset
	
	stuckSyncObserver:expectDataWithinTime( 5, sortStuckDocIds( { catalogAssetId, spaceAssetId } ) )
	
	-- Now attempt to resolve the stuck sync by bumping up the change counters (AKA forcing a new
	-- revision ID):

	rxTest:waitFor( WFCatStuckSyncUtils.resolveStuckSync( wfCatalog, { catalogAssetId, spaceAssetId } ) )

	-- Verify change counters of documents:

	rxTest:expectSingleValue( wfCatalog:docStore():docValue( catalogAssetId ):pluck( '_changeCounter' ), 3 )
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( spaceAssetId ):pluck( '_changeCounter' ), 4 )

	-- Verify the new revision ID and looper-protection annotation:

	local catalogAssetRevId = rxTest:getFirstValue( wfCatalog:docStore():docValue( catalogAssetId ):pluck( '_revId' ) )
	local spaceAssetRevId = rxTest:getFirstValue( wfCatalog:docStore():docValue( spaceAssetId ):pluck( '_revId' ) )

	rxTest:expectSingleValue( wfCatalog:docStore():docValue( catalogAssetId ):pluck( '_localOnly', 'lastGeneratedRevIdToResolveStuckSync' ), catalogAssetRevId )
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( spaceAssetId ):pluck( '_localOnly', 'lastGeneratedRevIdToResolveStuckSync' ), spaceAssetRevId )

	-- Looper protection should prevent us from generating new revision IDs via resolveStuckSync:

	rxTest:waitFor( WFCatStuckSyncUtils.resolveStuckSync( wfCatalog, { catalogAssetId, spaceAssetId } ) )

	rxTest:expectSingleValue( wfCatalog:docStore():docValue( catalogAssetId ):pluck( '_revId' ), catalogAssetRevId )
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( spaceAssetId ):pluck( '_revId' ), spaceAssetRevId )

	rxTest:expectSingleValue( wfCatalog:docStore():docValue( catalogAssetId ):pluck( '_localOnly', 'lastGeneratedRevIdToResolveStuckSync' ), catalogAssetRevId )
	rxTest:expectSingleValue( wfCatalog:docStore():docValue( spaceAssetId ):pluck( '_localOnly', 'lastGeneratedRevIdToResolveStuckSync' ), spaceAssetRevId )

	-- Verify that they are no longer stuck:
	
	stuckSyncObserver:expectDataWithinTime( 5, {} )
	
	stuckSyncObserver:dispose()
	
	isNormalSyncRunningStream:dispose()
	
end

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

function WFCatStuckSyncUtilsTest.rxTestGetDocsStuckUploadingOriginalStream( rxTest )

	rxTest:setTimeLimit( 10 )

	local wfCatalog = createFakeWFCatalog( rxTest )
	
	-- Start watching for stuck documents
	
	local isNormalSyncRunningStream = Rx.BehaviorSubject( true )
	
	local stuckSyncObserver = rxTest:observe( 
			WFCatStuckSyncUtils.getDocsStuckUploadingOriginalStream( wfCatalog, isNormalSyncRunningStream, 0.1, 0 )
				:map( sortStuckDocIds )
				:tap( function( stuckDocIds )
					WFCatStuckSyncUtils.trackStuckBinaryDocuments( stuckDocIds )
				end ) )
	
	-- Create a catalog asset with an unsynced original
	
	local optOverrides = {
		noMasterLink = true,
		internalXmpCameraRaw = true
	}
	
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local catalogDocValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )
	catalogDocValue._spaceId = KSIdGenerator.uuidInLowercaseHex()
	-- Make it look like the document has it's metadata synced
	
	KSTableUtils.setNested( catalogDocValue, { catalogDocValue._revId }, 
			'_serverProvidedMetadata', 'revision_ids' )
	
	rxTest:waitFor( wfCatalog:docStore():setDocValue( catalogDocValue ) )
	
	-- Make sure the asset is in the needs original uploaded state:
	
	local assetsNeedingOriginalUploadSql
	
	if WFPlatformInfo.isMobile() then -- iOS & Android
		assetsNeedingOriginalUploadSql = [[SELECT docId FROM coreInfo WHERE syncStatus == 2]]
	else -- Desktop
		
		local syncStatusColumnName = WFCatSyncStatus.getSyncStatusColumnName()
		assetsNeedingOriginalUploadSql = string.format( [[SELECT assetId FROM asset_sync_status_ui WHERE %s == 2]], syncStatusColumnName )

	end

	rxTest:expectSingleValue( wfCatalog:indexStore():queryOnce( {
			sql = assetsNeedingOriginalUploadSql,
			quiesceUntilUpToDate = true,
			singleColumnQuery = true
		} ), { assetId } )
		
	-- And that there's no stuck sync
	
	stuckSyncObserver:expectDataWithinTime( 5, {} )
	
	if WFPlatformInfo.isDesktop then

		-- The following only works on Desktop; the invalid state we're using to detect
		-- the stuck sync is only a bug on LrD. If we knew of a similar disparity between
		-- the sync status field and the needs upload status produced by the needsBinaryUpload
		-- index store table for mobile, we could write a test for mobile as well.

		-- Now add an asset that we know causes a sync mismatch. This is from Bug:
		-- https://jira.corp.adobe.com/browse/WF-1531
		
		local stuckDocId = KSIdGenerator.uuidInLowercaseHex()
		local spaceId = KSIdGenerator.uuidInLowercaseHex()
		
		local docValue = {
			type = "asset",
			subtype = "image",
			_id = stuckDocId,
			_spaceId = spaceId,
			userCreated = "2021-03-10T18:50:29.444Z",
			changedOnDevice = "Lightroom",
			develop = {
				croppedWidth = 5160,
				croppedHeight = 3523,
				processingModel = "lightroom",
				userOrientation = 1,
				crsVersion = "13.1",
				xmpCameraRaw = "<xmp></xmp>",
			},
			changedAtTime = "2019-01-28T05:28:38Z",
			userUpdated = "2021-03-10T18:50:29.444Z",
			captureDate = "2018-12-01T19:47:49",
			importSource = {
				originalHeight = 3523,
				importedOnDevice = "Lightroom Web {0363f1a98b0c507a237977267358886724194e58198ae5efc58c659238ee8fc3}",
				importTimestamp = "2021-03-10T18:50:29.444Z",
				contentType = "image/jpeg",
				sha256 = "008ca8642062816ced4dbb9715ca168e67e80206a27a967a2a6de7ed3134a846",
				fileSize = 14046953,
				status = {
					error = {
						all = {
							reason = {
								sha256 = "does not match",
							},
						},
					},
				},
				originalWidth = 5160,
				fileName = "DSC_7417.jpg",
				importedBy = "6279fb5ae349d1d87ab938404f6361f7",
			},
			_localOnly = {
				cameraProfileInfo = {
					profileDigest = "NA",
					xmpCameraRawProfileDigest = "ce875a3d38d776236e5b315e39833a41",
					isStandard = false,
				},
				files = {
					original = {
						master = {
							lastSaveTimestamp = "2021-03-15T20:21:18.155Z",
							volumeGuid = "4F09E031-8BA8-4FA8-B4E8-6B6CD4D5BBC0",
							size = 8778964,
							sha256 = "invalid",
							relativePath = "Users/albright/Pictures/Lightroom Library.lrlibrary/13e613bfedaf415c8ad7e60083047a72/originals/2019/2019-10-13/DSC_0787.jpg",
						},
					},
				},
			},
			_serverProvidedMetadata = {
				updated = "2021-03-10T18:49:03.452378Z",
				importedByUser = {
					id = "42ab77e2141a925355419c8a993e00c3",
				},
				links = {
					["self"] = {
						href = "/v2/spaces/0c752662b104440ba5f47e51faea95ea/updates/596b4285864f47048fea1c12a291623c",
					},
					["/rels/master_create"] = {
						href = "/v2/spaces/0c752662b104440ba5f47e51faea95ea/assets/596b4285864f47048fea1c12a291623c/revisions/{revision_id}/master",
						templated = true,
					},
					["/rels/web_abuse_report"] = {
						href = "https://lightroom.adobe.com/abuse_reports?asset-href=/v2/spaces/0c752662b104440ba5f47e51faea95ea/assets/596b4285864f47048fea1c12a291623c",
					},
					["/rels/version_create"] = {
						href = "/v2/spaces/0c752662b104440ba5f47e51faea95ea/assets/596b4285864f47048fea1c12a291623c/versions/{version_id}",
						templated = true,
					},
					["/rels/xmp_create"] = {
						href = "/v2/spaces/0c752662b104440ba5f47e51faea95ea/assets/596b4285864f47048fea1c12a291623c/revisions/{revision_id}/xmp",
						templated = true,
					},
					["/rels/xmp_develop_create"] = {
						href = "/v2/spaces/0c752662b104440ba5f47e51faea95ea/assets/596b4285864f47048fea1c12a291623c/revisions/{revision_id}/xmp/develop",
						templated = true,
					},
					["/rels/xmp"] = {
						href = "/v2/spaces/0c752662b104440ba5f47e51faea95ea/assets/596b4285864f47048fea1c12a291623c/revisions/5615565b9ab44351a3b63db57138c5d0/xmp",
					},
					["/rels/master"] = {
						href = "/v2/spaces/0c752662b104440ba5f47e51faea95ea/assets/596b4285864f47048fea1c12a291623c/revisions/5615565b9ab44351a3b63db57138c5d0/master",
						content_type = "image/jpeg",
					},
					["/rels/versions"] = {
						href = "/v2/spaces/0c752662b104440ba5f47e51faea95ea/assets/596b4285864f47048fea1c12a291623c/versions",
					},
					["/rels/proxy_create"] = {
						href = "/v2/spaces/0c752662b104440ba5f47e51faea95ea/assets/596b4285864f47048fea1c12a291623c/revisions/{revision_id}/proxies/{proxy_id}",
						templated = true,
					},
					["/rels/rendition_generate/fullsize"] = {
						href = "/v2/spaces/0c752662b104440ba5f47e51faea95ea/assets/596b4285864f47048fea1c12a291623c/revisions/5dfa0ac9de4523ea11b08d3f544fdbaf/renditions/{rendition_id}?rendition_type=fullsize",
						templated = true,
					},
				},
				created = "2021-03-10T18:48:13.370400Z",
				catalog_doc = {
					id = "596b4285864f47048fea1c12a291623c",
				},
				editable = true,
				base = "https://photos.adobe.io/v2/spaces/0c752662b104440ba5f47e51faea95ea/",
			}
		}
		
		isNormalSyncRunningStream:onNext( false ) -- disable sync
		
		local revId = rxTest:getFirstValue( wfCatalog:docStore():setDocValue( docValue ) )
		
		rxTest:waitFor( wfCatalog:docStore():applyOverlay( {
				_id = stuckDocId,
				_serverProvidedMetadata = {
					revision_ids = { revId }
				}
			} ) )

		-- Verify there's no stuck sync because sync is disabled
		
		stuckSyncObserver:expectDataWithinTime( 5, {} )
		
		-- Enable sync, and verify the stuck sync
		
		isNormalSyncRunningStream:onNext( true )
		
		stuckSyncObserver:expectDataWithinTime( 5, { stuckDocId } )
	
	end
	
	stuckSyncObserver:dispose()

	isNormalSyncRunningStream:dispose()

end

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

return WFRxTestCase.wrapSuite( WFCatStuckSyncUtilsTest )
