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

ADOBE CONFIDENTIAL
------------------
Copyright 2020 Adobe Inc.
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.

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

	-- WF.core.os
local WFPlatformInfo = require 'WFPlatformInfo'

if WFPlatformInfo.isDesktop then
       return {}
end

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

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

	-- WF.oz_client.managed_catalog
local WFCatAssetTestUtils = require "WFCatAssetTestUtils"	
local WFCatVersions = require 'WFCatVersions'

	-- lrmobile
local LrmConfig = require "LrmConfig"
local LrmCatalogTestHelper = require 'LrmCatalogTestHelper'

local LrmSyncStatusModel

local removeLocalBinaries, expectedAssetsPending, noAssetsPending

if WFPlatformInfo.isiOS then

	LrmSyncStatusModel = require "LriSyncStatusModel"
	
	removeLocalBinaries = function( docValue, ... )

		for _, renditionName in ipairs{ ... } do

			if renditionName == 'original' then
				docValue._localOnly.files.master = nil
			else
				docValue._localOnly.files[ renditionName ] = nil
			end

		end

	end
	
	expectedAssetsPending = function( albumId, name, count )
		return { 
			[ albumId ] = {  
				albumName = name, 
				count = count 
			}
		}
	end
	
	noAssetsPending = {}
	
else

	LrmSyncStatusModel = require "LrmSyncStatusModel"
	
	removeLocalBinaries = function( docValue, ... )

		for _, renditionName in ipairs{ ... } do

			if renditionName == 'original' then
				docValue._localOnly.originalFilePath = nil
			else
				docValue._localOnly[ renditionName ] = nil
			end

		end

	end
	
	 expectedAssetsPending = function( albumId, name, count )
		return {
			album = name,
			count = count
		}
	end
	
	noAssetsPending = { count = 0 }

end

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

local LrmSyncStatusModelTest = {}

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

local TEST_TIMEOUT = 5

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

local function getSubmodel( model, submodelName )

	return model.models[submodelName]

end

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

local function makeSubmodel( model, submodelName, ... )

	return getSubmodel( model, submodelName )( model, ... )

end

--------------------------------------------------------------------------------
--[[!
	\brief Create an offline album with a single asset and one unsynced binary

	\param rxTest
	\param wfCatalog
	\param optArgs an optional table with:
		* \c unsyncedRenditions: a list of unsynced rendition names (default is { 'proxy' })
		* \c spaceId (string): set to a UUID if you want the album to be part of a space
		* \c role (string): set to the role of the current user of the album (spaceId needs to be set for this)

	\return a table with:
		* \c albumId (string): the album Id
		* \c assetDocument (string): the docValue for the asset
		* \c spaceUserId (string)
		* \c ownerId (string)
		* \c simulateDownloadOverlay (table) and overlay that simulates the download of the missing binary

]]--

local function createOfflineAlbumWithUnsyncedAsset( rxTest, wfCatalog, optArgs )

	optArgs = optArgs or {}

	local ownerId
	local spaceUserId

	-- add a space and a space_user if optArgs.spaceId is set
	if optArgs.spaceId then

		ownerId = KSIdGenerator.uuidInLowercaseHex()
		spaceUserId = KSIdGenerator.uuidInLowercaseHex()

		local spaceDoc = {
			_id = optArgs.spaceId,
			type = 'space',
			subtype = 'shared',
			download = true,
			private = false,
			location = false,
			metadata = false,
			_serverProvidedMetadata = {
				owner = {
					id = ownerId
				},
				this_space_user = {
					id = spaceUserId
				}
			}
		}

		rxTest:waitFor( wfCatalog:docStore():setDocValue( spaceDoc ) )

		-- Add a space_user to the space

		local spaceUserDoc = {
			_id = spaceUserId,
			type = 'space_user',
			space = { id = optArgs.spaceId },
			user = { id = KSIdGenerator.uuidInLowercaseHex() },
			role = optArgs.role or 'viewer'
		}

		rxTest:waitFor( wfCatalog:docStore():setDocValue( spaceUserDoc ) )

	end

	local albumId = KSIdGenerator.uuidInLowercaseHex()

	local albumDoc = {
		_id = albumId,
		_spaceId = optArgs.spaceId,
		type = 'album',
		subtype = 'collection',
		name = 'offline'
	}

	rxTest:waitFor( wfCatalog:docStore():setDocValue( albumDoc ) )

	local docValue = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, KSIdGenerator.uuidInLowercaseHex() )
	KSTableUtils.setNested( docValue, nil, '_serverProvidedMetadata', 'revision_ids' )
	
    -- docValue with missing binaries	
    local docValueMissingBinaries = KSTableUtils.deepCopy( docValue )
	local unsyncedRenditions = optArgs.unsyncedRenditions or { 'proxy' }

	removeLocalBinaries( docValueMissingBinaries, unpack( unsyncedRenditions ) )

	-- set the album for offline use
 	rxTest:waitFor( wfCatalog:docStore():applyOverlay {
			_id = albumId,
			_localOnly = {
				useOffline = true,
			}
		},
		':ignore:' )

	rxTest:waitFor( wfCatalog:docStore():applyOverlay( docValueMissingBinaries ) )

	rxTest:waitFor( wfCatalog:docStore():applyOverlay {
			type = 'album_asset',
			_id = KSIdGenerator.uuidInLowercaseHex(),
			album = {
				id = albumId,
			},
			asset = {
				id = docValue._id,
			}
		} )

	local overlay = WFTableMerge.createDiff( docValueMissingBinaries, docValue )
	overlay._id = docValue._id

	return {
		albumId = albumId,
		ownerId = ownerId,
		spaceUserId = spaceUserId,
		assetDocument = docValue,
		simulateDownloadOverlay = overlay
	}

end

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

local function createCatalogService( rxTest )

	local catalogService, wfCatalog = LrmCatalogTestHelper.makeCatalogService( rxTest )
	
	rxTest:finally( function()
		catalogService:close()
	end )

	return catalogService, wfCatalog
	
end

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

function LrmSyncStatusModelTest.rxTestEditorOfflineAlbum( rxTest )

	rxTest:setTimeLimit( TEST_TIMEOUT )

	local catalogService, wfCatalog = createCatalogService( rxTest )
	
	-- create a shared with you album with an editor role	
	local albumInfo = createOfflineAlbumWithUnsyncedAsset( rxTest, wfCatalog, {
		unsyncedRenditions = { 'original', 'latestOriginal' },
		spaceId = KSIdGenerator.uuidInLowercaseHex(),
		role = 'editor'
	} )
	
	local syncStatusModel = LrmSyncStatusModel.makeModel( catalogService )
	rxTest:finally( function()
		syncStatusModel:close()
	end )

	local binaryDownloadProgressAndAlbumModel = makeSubmodel( syncStatusModel, 
		'makeBinaryDownloadProgressAndAlbumModel', 
		LrmConfig.StrategyPreferMaster ).value
		
	local obs = rxTest:observe( binaryDownloadProgressAndAlbumModel )
	rxTest:finally( function()
		obs:dispose()
	end )

	-- The model does not use quiesceUntilUpToDate (rightly so), so we may get initial wrong results  
	obs:expectDataWithinTime( TEST_TIMEOUT / 2, expectedAssetsPending( albumInfo.albumId, 'offline', 1 ) )
	
	-- simulate the download of the proxy
	
    rxTest:waitFor( wfCatalog:docStore():applyOverlay( albumInfo.simulateDownloadOverlay ) )

	obs:expectDataWithinTime( TEST_TIMEOUT / 2, noAssetsPending )

end

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

function LrmSyncStatusModelTest.rxTestContributorOfflineAlbum( rxTest )

	rxTest:setTimeLimit( TEST_TIMEOUT )

	local catalogService, wfCatalog = createCatalogService( rxTest )
	
	-- create a shared with you album as a viewer
	-- viewers never get originals, so remove them from the asset
	local albumInfo = createOfflineAlbumWithUnsyncedAsset( rxTest, wfCatalog, {
		unsyncedRenditions = { 'original', 'latestOriginal', 'proxy' },
		spaceId = KSIdGenerator.uuidInLowercaseHex()
	} )
	
	local syncStatusModel = LrmSyncStatusModel.makeModel( catalogService )
	rxTest:finally( function()
		syncStatusModel:close()
	end )

	local binaryDownloadProgressAndAlbumModel = makeSubmodel( syncStatusModel, 
		'makeBinaryDownloadProgressAndAlbumModel', 
		LrmConfig.StrategyPreferMaster ).value
		
	local obs = rxTest:observe( binaryDownloadProgressAndAlbumModel )
	rxTest:finally( function()
		obs:dispose()
	end )

	-- The model does not use quiesceUntilUpToDate (rightly so), so we may get initial wrong results  
	obs:expectDataWithinTime( TEST_TIMEOUT / 2, expectedAssetsPending( albumInfo.albumId, 'offline', 1 ) )
	
	
    rxTest:waitFor( wfCatalog:docStore():applyOverlay( albumInfo.simulateDownloadOverlay ) )

	obs:expectDataWithinTime( TEST_TIMEOUT / 2, noAssetsPending )

end

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

function LrmSyncStatusModelTest.rxTestOwnOfflineAlbum_StrategyMaster( rxTest )

	rxTest:setTimeLimit( TEST_TIMEOUT )

	local catalogService, wfCatalog = createCatalogService( rxTest )
	
	local albumInfo = createOfflineAlbumWithUnsyncedAsset( rxTest, wfCatalog, {
		unsyncedRenditions = { 'original', 'latestOriginal' }
	} )
	
	local syncStatusModel = LrmSyncStatusModel.makeModel( catalogService )
	rxTest:finally( function()
		syncStatusModel:close()
	end )

	local binaryDownloadProgressAndAlbumModel = makeSubmodel( syncStatusModel, 
		'makeBinaryDownloadProgressAndAlbumModel', 
		LrmConfig.StrategyPreferMaster ).value
		
	local obs = rxTest:observe( binaryDownloadProgressAndAlbumModel )
	rxTest:finally( function()
		obs:dispose()
	end )

	-- The model does not use quiesceUntilUpToDate (rightly so), so we may get initial wrong results  
	obs:expectDataWithinTime( TEST_TIMEOUT / 2, expectedAssetsPending( albumInfo.albumId, 'offline', 1 ) )
	
	-- simulate the download of the original
	
    rxTest:waitFor( wfCatalog:docStore():applyOverlay( albumInfo.simulateDownloadOverlay ) )

	obs:expectDataWithinTime( TEST_TIMEOUT / 2, noAssetsPending )

end

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

function LrmSyncStatusModelTest.rxTestOwnOfflineAlbum_StrategyProxy( rxTest )

	rxTest:setTimeLimit( TEST_TIMEOUT )

	local catalogService, wfCatalog = createCatalogService( rxTest )
	
	local albumInfo = createOfflineAlbumWithUnsyncedAsset( rxTest, wfCatalog, 'proxy' )

	local syncStatusModel = LrmSyncStatusModel.makeModel( catalogService )
	rxTest:finally( function()
		syncStatusModel:close()
	end )

	local binaryDownloadProgressAndAlbumModel = makeSubmodel( syncStatusModel, 
		'makeBinaryDownloadProgressAndAlbumModel', 
		LrmConfig.StrategyPreferProxy ).value
		
	local obs = rxTest:observe( binaryDownloadProgressAndAlbumModel )
	rxTest:finally( function()
		obs:dispose()
	end )

	-- The model does not use quiesceUntilUpToDate (rightly so), so we may get initial wrong results  
	obs:expectDataWithinTime( TEST_TIMEOUT / 2, expectedAssetsPending( albumInfo.albumId, 'offline', 1 ) )
	
	-- simulate the download of the proxy
	
    rxTest:waitFor( wfCatalog:docStore():applyOverlay( albumInfo.simulateDownloadOverlay ) )

	obs:expectDataWithinTime( TEST_TIMEOUT / 2, noAssetsPending )

end

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

local function createDocumentWithOzMetadataUpdate( catalogId, assetId, revisionId, sha256, originalFilePath )

	local baseAssetDoc = WFCatAssetTestUtils.createBaseAssetDocument( catalogId, assetId, revisionId, sha256, originalFilePath )
	
	WFCatAssetTestUtils.addBasicOzMetadataUpdate( baseAssetDoc, catalogId, assetId, revisionId )
	
	WFCatAssetTestUtils.addOriginalPathToDocument( baseAssetDoc, sha256, originalFilePath )

	return baseAssetDoc

end

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

local function addXmpDevelopSettingsToDocument( catalog, assetId, xmpDevelopSha256, xmpDevelopFilePath, xmpDevelopFileSize )
	
	local newXmpDevelopFilePath = xmpDevelopFilePath
	if WFPlatformInfo.isAndroid then
		-- Android doesn't store full file path here, so remove the beginning if we have a full file path
		local basePath = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
		newXmpDevelopFilePath = string.replace( xmpDevelopFilePath, basePath .. '/', '' )
	end
	
	local contentIdOrSha256 = WFPlatformInfo.isiOS and 'contentId' or 'sha256'
	return catalog:docStore():applyOverlay {
			_id = assetId,
			develop = {
				processingModel = 'lightroom',
				xmpCameraRaw = {
					sha256 = xmpDevelopSha256,
				},
			},
			_localOnly = {
				files = {
						xmp_develop = {
							path = newXmpDevelopFilePath,
							fileSize = xmpDevelopFileSize,
							[contentIdOrSha256] = xmpDevelopSha256,
						},
				},
			},
		}

end

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

function LrmSyncStatusModelTest.rxTestUploadProgress( rxTest )

	rxTest:setTimeLimit( TEST_TIMEOUT )

	local catalogService, wfCatalog = createCatalogService( rxTest )
	
	local syncStatusModel = LrmSyncStatusModel.makeModel( catalogService )
	
	rxTest:finally( function()
		syncStatusModel:close()
	end )

	local binaryUploadCountStream = syncStatusModel.value.pendingUploadsToOz
		
	local obs = rxTest:observe( binaryUploadCountStream )
	
	rxTest:finally( function()
		obs:dispose()
	end )

	-- Nothing should need to be uploaded.
	
	obs:expectDataWithinTime( TEST_TIMEOUT / 2, 0 )

	-- Add a document that needs it's original uploaded.
	
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	local sha256 =  '43406490d179b2f3d734749996734efc5834f9045e3e91b55d9ec628894fa653'

	local testDataPath = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'testdata' ), "import-samples" )
	local testDataFolder1 = KSPathUtils.child( testDataPath, "folder-1" )
	local originalFilePath = KSPathUtils.child( testDataFolder1, "IMG_7763.JPG" )
	
	local assetDoc = createDocumentWithOzMetadataUpdate( wfCatalog:ozCatalogId(), assetId, revisionId, sha256, originalFilePath )
	
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc ) )
	
	obs:expectDataWithinTime( TEST_TIMEOUT / 2, 1 )
		-- The count should now be 1
		
	-- Make it look like the original has been uploaded.
	
	local overlay = {
		_id = assetId
	}
	
	local links = {
		['/rels/master'] = {
			href = 'foo'
		}
	}

	KSTableUtils.setNested( overlay, links, '_serverProvidedMetadata', 'links' )
	
	rxTest:waitFor( wfCatalog:docStore():applyOverlay( overlay ) )
	
	obs:expectDataWithinTime( TEST_TIMEOUT / 2, 0 )
		
	-- Make it look like the document needs both the original and external settings uploaded.
	
	overlay = {
		_id = assetId
	}
	
	links = {
		['/rels/master'] = {
			href = { _delete = true }
		}
	}

	KSTableUtils.setNested( overlay, links, '_serverProvidedMetadata', 'links' )
	
	rxTest:waitFor( wfCatalog:docStore():applyOverlay( overlay ) )
	
	local xmpDevelopFileSize = 1000
	local xmpDevelopFilePath = KSPathUtils.child( KSPathUtils.getStandardFilePath( 'carouselDocuments' ), 'IMG_7763.XMP' )

	rxTest:waitFor( addXmpDevelopSettingsToDocument( wfCatalog, assetId, sha256, xmpDevelopFilePath, xmpDevelopFileSize ) )
	
	obs:expectDataWithinTime( TEST_TIMEOUT / 2, 1 )
		-- The count should be 1 because we shouldn't count the same asset twice.
	
	-- Make it look like the original has been uploaded.. again.
	
	overlay = {
		_id = assetId
	}
	
	links = {
		['/rels/master'] = {
			href = 'foo'
		}
	}

	KSTableUtils.setNested( overlay, links, '_serverProvidedMetadata', 'links' )
	
	rxTest:waitFor( wfCatalog:docStore():applyOverlay( overlay ) )
	
	obs:expectDataWithinTime( TEST_TIMEOUT / 2, 1 )
		-- The count should still be 1, because now the develop settings need to be uploaded.
	
	-- Add a version that needs to be uploaded. It should be ignored.

	rxTest:waitFor( WFCatVersions.createVersionForAsset( wfCatalog, assetId, false, 'foo' ) )

	obs:expectDataWithinTime( TEST_TIMEOUT / 2, 1 )

end

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

return WFRxTestCase.wrapSuite( 'LrmSyncStatusModelTest', LrmSyncStatusModelTest )

