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

WFCatCopiesTest.lua

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

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

NOTICE: All information contained herein is, and remains 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 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 Systems Incorporated.

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

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

	-- WF.core.lua
local KSData = require 'KSData'

	-- WF.core.os
local KSPathUtils = require 'KSPathUtils'
local WFFileUtils = require 'WFFileUtils'

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

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

	-- WF.oz_client.managed_catalog
local WFCatAssetUtils = require 'WFCatAssetUtils'
local WFCatAssetTestUtils = require 'WFCatAssetTestUtils'
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFCatCopies = require 'WFCatCopies'
local WFCatFileSpec = require "WFCatFileSpec"
local WFCatAlbums = require "WFCatAlbums"
local WFCatVersionTestUtils = require 'WFCatVersionTestUtils'

local OzFilePaths

if WFPlatformInfo.isiOS then
	OzFilePaths = require 'OzFilePaths'
end

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

local WFCatMockCatalog_createFakeWFCatalog = WFCatMockCatalog.createFakeWFCatalog

local KSTableUtils_derefOpt = KSTableUtils.derefOpt


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

local WFCatCopiesTest = {}

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

local function addDoc( docStore, docId )

	local volumeGuid = 'bar'

	local docValue = {
			_id = docId,
			type = 'asset',
			subtype = 'image',
			_localOnly = {
				files = {
					proxy = {
						pano = {
							relativePath = 'foo',
							volumeGuid = volumeGuid
						}
					}
				}
			},
			importSource = {
				filePath = '/path/',
				originalHeight = 150,
				originalWidth = 100,
			}
		}

	return docStore:setDocValue( docValue )
end

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

local function mapPluckCopyIds( docs )

	local copies = {}

	for i, doc in ipairs( docs ) do
		copies[ i ] = doc.copy and doc.copy.copy_id
	end

	return copies

end

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

local function mapArrayHasIdenticalEntriesFn( num )

	return function( array )

		if #array ~= num then
			return false
		end

		local first = array[ 1 ]
		for _, val in ipairs( array ) do
			if val ~= first then
				return false
			end
		end

		return true
	end

end

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

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 createAsset( rxTest, wfCatalog, docStore, assetId, optOverrides )

	local docValue, assetInfo = WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, optOverrides )

	rxTest:logf( "Asset: %s", docValue )

	rxTest:waitFor( docStore:setDocValue( docValue ) )

	return assetInfo, docValue

end

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

local function createVersion( rxTest, wfCatalog, docStore, versionId, optOverrides )

	local docValue, versionInfo = WFCatVersionTestUtils.createVersionDocument( rxTest, wfCatalog, versionId, optOverrides )

	rxTest:logf( "Version: %s", docValue )

	rxTest:waitFor( docStore:setDocValue( docValue ) )

	return versionInfo, docValue

end

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

local function absolutePath( catalogId, relativePath )

	local absolutePath

	if WFPlatformInfo.isiOS then

		if relativePath then

			if string.sub( relativePath, 1, 1 ) == '/' then

				absolutePath = relativePath

			else

				if not OzFilePaths then
					OzFilePaths = require 'OzFilePaths'
				end

				local basePath = OzFilePaths.getCatalogBasePath( catalogId )
				absolutePath = KSPathUtils.child( basePath, relativePath )

			end

		end

	elseif WFPlatformInfo.isAndroid then

		local OzCatalogUploadStatus = require 'OzCatalogUploadStatus'

		if relativePath then

			if string.sub( relativePath, 1, 1 ) == '/' then
				absolutePath = relativePath
			else
				-- the relative path might have the catalogid already prepended or not
				if string.sub( relativePath, 1, catalogId:len() ) == catalogId then
					local documentsDir = KSPathUtils.getStandardFilePath( 'carouselDocuments' )
					absolutePath = KSPathUtils.child( documentsDir, relativePath )
				else
					absolutePath = OzFilePaths.getAbsolutePathInCatalog( catalogId, relativePath )
				end
			end

		end

		absolutePath = OzCatalogUploadStatus.checkAndCorrectLocalPath( absolutePath )

	end

	return absolutePath

end

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

local function mapAwayPaths( rxTest, wfCatalog, docValue, fileInfo, performFileCheck )

	docValue.captureDate = nil

	performFileCheck = performFileCheck ~= false

	if WFPlatformInfo.isDesktop then

		local copy_originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
		local copy_proxyPath = WFCatAssetUtils.getFullProxyPathFromDocValue( docValue )
		local copy_externalXmpPath = WFCatAssetUtils.getLocalExternalDevelopSettingsPath( docValue )

		rxTest:logf( "copy_originalPath %s", copy_originalPath )
		rxTest:logf( "copy_proxyPath %s", copy_proxyPath )
		rxTest:logf( "copy_externalXmpPath %s", copy_externalXmpPath )

		if performFileCheck then
			rxTest:expectEqual( fileInfo.originalFileSize, WFFileUtils.fileAttributes( copy_originalPath ).fileSize )
			rxTest:expectEqual( fileInfo.proxyFileSize, WFFileUtils.fileAttributes( copy_proxyPath ).fileSize )
			rxTest:expectEqual( fileInfo.xmpDevelopSettingsFileSize, WFFileUtils.fileAttributes( copy_externalXmpPath ).fileSize )
		end

		-- check the file paths are changed and switch to a known value if they are.

		local copy_xmpDevelopPath = WFCatFileSpec.getXmpDevelopInfoFromDocValue( docValue).path
		local copy_xmpSidecarPath = WFCatFileSpec.getXmpSidecarPathFromDocValue( docValue )

		if performFileCheck then
			rxTest:expectEqual( fileInfo.xmpSidecarFileSize, WFFileUtils.fileAttributes( copy_xmpSidecarPath ).fileSize )
			rxTest:expectEqual( fileInfo.xmpDevelopSettingsFileSize, WFFileUtils.fileAttributes( copy_xmpDevelopPath ).fileSize )
		end

		if copy_originalPath ~= fileInfo.originalFilePath then
			docValue._localOnly.files.original.master.relativePath = '<newOriginalPath>'
			docValue._localOnly.files.original.master.volumeGuid = '<newOriginalGuid>'
		end

		if copy_proxyPath ~= fileInfo.proxyFilePath then
			docValue._localOnly.files.proxy['2560'].relativePath = '<newProxyPath>'
			docValue._localOnly.files.proxy['2560'].volumeGuid = '<newProxyVolumeGuid>'
		end

		if copy_xmpSidecarPath ~= fileInfo.xmpSidecarFilePath then
			docValue._localOnly.files.original.xmp.path = '<newXmpSidecarPath>'
		end

		-- xmpDevelopSettingsFilePath currently is shared by all files with same settings
		-- sha256 hash so it's path doesn't change when copied.

	elseif WFPlatformInfo.isiOS then

		local copy_originalRelativePath = WFCatAssetUtils.getOriginalRelativePathFromDocValue( docValue )
		local copy_proxyRelativePath = WFCatAssetUtils.getProxyRelativePathFromDocValue( docValue )
		local copy_externalXmpRelativePath = WFCatAssetUtils.getLocalExternalDevelopSettingsPath( docValue )

		local copy_originalPath = absolutePath( wfCatalog:ozCatalogId(), copy_originalRelativePath )
		local copy_proxyPath = absolutePath( wfCatalog:ozCatalogId(), copy_proxyRelativePath )
		local copy_externalXmpPath = absolutePath( wfCatalog:ozCatalogId(), copy_externalXmpRelativePath )

		rxTest:logf( "copy_originalPath %s", copy_originalPath )
		rxTest:logf( "copy_proxyPath %s", copy_proxyPath )
		rxTest:logf( "copy_externalXmpPath %s", copy_externalXmpPath )

		if performFileCheck then
			rxTest:expectEqual( fileInfo.originalFileSize, WFFileUtils.fileAttributes( copy_originalPath ).fileSize )
			rxTest:expectEqual( fileInfo.proxyFileSize, WFFileUtils.fileAttributes( copy_proxyPath ).fileSize )
			rxTest:expectEqual( fileInfo.xmpDevelopSettingsFileSize, WFFileUtils.fileAttributes( copy_externalXmpPath ).fileSize )
		end

		local copy_previewPath = KSTableUtils_derefOpt( docValue, '_localOnly', 'files', 'preview', 'path' )
		local copy_thumbnailPath = KSTableUtils_derefOpt( docValue, '_localOnly', 'files', 'thumbnail2x', 'path' )
		local copy_fullsizePath = KSTableUtils_derefOpt( docValue, '_localOnly', 'files', 'fullsize', 'path' )

		if performFileCheck then

			local absolutePreviewPath = absolutePath( wfCatalog:ozCatalogId(), copy_previewPath )
			local copy_previewData = KSData.memoryMappedContentsOfFile( absolutePreviewPath )
			rxTest:expectEqual( fileInfo.previewData, tostring( copy_previewData ) )
			copy_previewData:close()

			local absoluteThumbnailPath = absolutePath( wfCatalog:ozCatalogId(), copy_thumbnailPath )
			local copy_thumbnailData = KSData.memoryMappedContentsOfFile( absoluteThumbnailPath )
			rxTest:expectEqual( fileInfo.thumbnailData, tostring( copy_thumbnailData ) )
			copy_thumbnailData:close()

			local absoluteFullsizePath = absolutePath( wfCatalog:ozCatalogId(), copy_fullsizePath )
			local copy_fullsizeData = KSData.memoryMappedContentsOfFile( absoluteFullsizePath )
			rxTest:expectEqual( fileInfo.fullsizeData, tostring( copy_fullsizeData ) )
			copy_fullsizeData:close()

		end

		docValue._localOnly.files.preview.path = '<previewPath>'
		docValue._localOnly.files.thumbnail2x.path = '<thumbnailPath>'
		docValue._localOnly.files.fullsize.path = '<fullsizePath>'

		if copy_originalPath ~= fileInfo.originalFilePath then
			docValue._localOnly.files.master.path = '<newOriginalPath>'
		end

		if copy_proxyPath ~= fileInfo.proxyFilePath then
			docValue._localOnly.files.proxy.path = '<newProxyPath>'
		end

		if copy_externalXmpPath ~= fileInfo.externalXmpPath then
			docValue._localOnly.files.xmp_develop.path = '<newExternalXmpPath>'
		end

	elseif WFPlatformInfo.isAndroid then

		local copy_originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
		local copy_proxyPath = WFCatAssetUtils.getFullProxyPathFromDocValue( docValue )
		local copy_externalXmpRelativePath = WFCatAssetUtils.getLocalExternalDevelopSettingsPath( docValue )

		local copy_externalXmpPath = absolutePath( wfCatalog:ozCatalogId(), copy_externalXmpRelativePath )

		rxTest:logf( "copy_originalPath %s", copy_originalPath )
		rxTest:logf( "copy_proxyPath %s", copy_proxyPath )
		rxTest:logf( "copy_externalXmpPath %s", copy_externalXmpPath )

		if performFileCheck then
			rxTest:expectEqual( fileInfo.originalFileSize, WFFileUtils.fileAttributes( copy_originalPath ).fileSize )
			rxTest:expectEqual( fileInfo.proxyFileSize, WFFileUtils.fileAttributes( copy_proxyPath ).fileSize )
			rxTest:expectEqual( fileInfo.xmpDevelopSettingsFileSize, WFFileUtils.fileAttributes( copy_externalXmpPath ).fileSize )
		end

		if copy_originalPath ~= fileInfo.originalFilePath then
			docValue._localOnly.originalFilePath = '<newOriginalPath>'
		end

		if copy_proxyPath ~= fileInfo.proxyFilePath then
			docValue._localOnly.proxy.filePath = '<newProxyPath>'
		end

		if copy_externalXmpPath ~= fileInfo.externalXmpPath then
			docValue._localOnly.files.xmp_develop.path = '<newExternalXmpPath>'
		end

		local copy_previewMd5 = KSTableUtils_derefOpt( docValue, '_localOnly', 'preview', 'md5' )
		local copy_thumbnailMd5 = KSTableUtils_derefOpt( docValue, '_localOnly', 'thumbnail2x', 'md5' )
		local copy_fullsizeMd5 = KSTableUtils_derefOpt( docValue, '_localOnly', 'fullsize', 'md5' )

		if performFileCheck then
			rxTest:expectEqual( fileInfo.previewMd5, copy_previewMd5 )
			rxTest:expectEqual( fileInfo.thumbMd5, copy_thumbnailMd5 )
			rxTest:expectEqual( fileInfo.fullsizeMd5, copy_fullsizeMd5 )
		end

		docValue._localOnly.preview.md5 = '<previewMd5>'
		docValue._localOnly.thumbnail2x.md5 = '<thumbnailMd5>'
		docValue._localOnly.fullsize.md5 = '<fullsizeMd5>'

		docValue._localOnly.preview.epoch = '<epoch>'
		docValue._localOnly.thumbnail2x.epoch = '<epoch>'
		docValue._localOnly.fullsize.epoch = '<epoch>'

		docValue._localOnly.preview.revId = '<revId>'
		docValue._localOnly.thumbnail2x.revId = '<revId>'
		docValue._localOnly.fullsize.revId = '<revId>'

	end

	docValue.xmp = nil

	docValue.develop.device = nil
	docValue.develop.userUpdated = nil

	return docValue

end

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

local function getExpectedCopiedDocValue( fileInfo, sourceAssetId, copiedAssetId, copiedAssetRevId, copyId, maybeXmpCameraRawSha256, maybeAux )

	local xmpDevelopSha256 = maybeXmpCameraRawSha256 or fileInfo.developSettingsSha256

	local docValue = {
		_changeCounter = '<changeCounter>',
		_id = copiedAssetId,
		_localId = '<localId>',
		_revId = copiedAssetRevId,
		copy = {
			copy_id = copyId,
		},
		develop = {
			processingModel = 'lightroom',
			croppedHeight = 1000,
			croppedWidth = 1000,
			userOrientation = 1,
			xmpCameraRaw = {
				sha256 = xmpDevelopSha256,
			},
		},
		importSource = {
			sha256 = fileInfo.originalSha256,
			fileSize = fileInfo.originalFileSize,
			fileName = 'Image01.jpg',
			importedBy = '9cb9fb43d26c4691a9fd32e633434796',
			importTimestamp = '2023-12-20T17:19:16.448Z',
		},
		subtype = "image",
		type = "asset",
	}

	if maybeAux then

		-- this shadows the same logic in the asset generator.

		local auxSection = {
			[ fileInfo.mask1AuxId ] = {
                type = 'mask',
                fileSize = fileInfo.mask1FileSize,
                digest = fileInfo.mask1Fingerprint,
                sha256 = fileInfo.mask1Sha256,
            },

  			[ fileInfo.mask2AuxId ] = {
                type = 'mask',
                fileSize = fileInfo.mask2FileSize,
                digest = fileInfo.mask2Fingerprint,
                sha256 = fileInfo.mask2Sha256,
            }
		}

		local developMasks = {
				[ fileInfo.mask1AuxId ] = {
					digest = fileInfo.mask1Fingerprint
				},
				[ fileInfo.mask2AuxId ] = {
					digest = fileInfo.mask2Fingerprint
				}
			}

		KSTableUtils.setNested( docValue, auxSection, 'aux' )
		KSTableUtils.setNested( docValue, developMasks, 'develop', 'masks' )

	end

	if WFPlatformInfo.isDesktop then

		docValue._localOnly = {
			files = {
				original = {
					master = {
						size = fileInfo.originalFileSize,
						relativePath = '<newOriginalPath>',
						volumeGuid = '<newOriginalGuid>',
					},
					xmp = {
						path = '<newXmpSidecarPath>',
						fileSize = fileInfo.xmpSidecarFileSize,
					},
				},
				proxy = {
					[ '2560' ] = {
						relativePath = '<newProxyPath>',
						volumeGuid = '<newProxyVolumeGuid>',
						size = fileInfo.proxyFileSize,
					}
				},
				xmp_develop = {
					fileSize = fileInfo.xmpDevelopSettingsFileSize,
					path = fileInfo.xmpDevelopSettingsFilePath,
					sha256 = fileInfo.developSettingsSha256
				},
			},
			sourceAssetId = sourceAssetId,
			sourceOriginalLink = "/href/bogus/testing",
			sourceProxyLink = "/href/bogus/proxy",
			sourceXmpDevelopLink = "/href/bogus/xmp_develop",
			sourceXmpDevelopSha256 = xmpDevelopSha256,
			sourceXmpLink = "/href/bogus/xmp",
			xmpSidecarExists = true,
		}

	elseif WFPlatformInfo.isiOS then

		docValue._localOnly = {
			files = {
				fullsize = {
					path = '<fullsizePath>',
					contentId = fileInfo.fullsizeContentId,
					fileSize = fileInfo.fullsizeFileSize,
				},
				master = {
					path = '<newOriginalPath>',
					contentId = fileInfo.originalContentId,
					fileSize = fileInfo.originalFileSize,
				},
				proxy = {
					path = '<newProxyPath>',
					contentId = fileInfo.proxyContentId,
					fileSize = fileInfo.proxyFileSize,
				},
				preview = {
					path = '<previewPath>',
					contentId = fileInfo.previewContentId,
					fileSize = fileInfo.previewFileSize,
				},
				thumbnail2x = {
					path = '<thumbnailPath>',
					contentId = fileInfo.thumbnailContentId,
					fileSize = fileInfo.thumbnailFileSize,
				},
				xmp_develop = {
					path = '<newExternalXmpPath>',
					fileSize = fileInfo.xmpDevelopSettingsFileSize,
					contentId = fileInfo.developSettingsSha256
				},
			},
			sourceAssetId = sourceAssetId,
			sourceOriginalLink = "/href/bogus/testing",
			sourceProxyLink = "/href/bogus/proxy",
			sourceXmpDevelopLink = "/href/bogus/xmp_develop",
			sourceXmpDevelopSha256 = xmpDevelopSha256,
			sourceXmpLink = "/href/bogus/xmp",
			xmpSidecarExists = true,
		}


	elseif WFPlatformInfo.isAndroid then

		docValue._localOnly = {
			originalFilePath = '<newOriginalPath>',
			original = {
				size = fileInfo.originalFileSize,
			},
			proxy = {
				filePath = '<newProxyPath>',
				size = fileInfo.proxyFileSize,
			},
			preview = {
				revId = '<revId>',
				epoch = '<epoch>',
				md5 = '<previewMd5>',
				referenceHeight = 960,
				referenceWidth = 1280,
				size = fileInfo.previewFileSize,
			},
			fullsize = {
				revId = '<revId>',
				epoch = '<epoch>',
				md5 = '<fullsizeMd5>',
				referenceHeight = 960,
				referenceWidth = 1280,
				size = fileInfo.fullsizeFileSize,
			},
			thumbnail2x = {
				revId = '<revId>',
				epoch = '<epoch>',
				md5 = '<thumbnailMd5>',
				referenceHeight = 960,
				referenceWidth = 1280,
				size = fileInfo.thumbnailFileSize,
			},
			files = {
				xmp_develop = {
					path = '<newExternalXmpPath>',
					sha256 = fileInfo.developSettingsSha256,
					fileSize = fileInfo.xmpDevelopSettingsFileSize
				},
			},
			sourceAssetId = sourceAssetId,
			sourceOriginalLink = "/href/bogus/testing",
			sourceXmpDevelopLink = "/href/bogus/xmp_develop",
			sourceXmpDevelopSha256 = xmpDevelopSha256,
			sourceProxyLink = "/href/bogus/proxy",
			sourceXmpLink = "/href/bogus/xmp",
			xmpSidecarExists = true,
		}

	end

	if maybeAux then

		KSTableUtils.setNested( docValue, {

			[ fileInfo.mask1Fingerprint ] = {
				relativePath = fileInfo.mask1RelativeFilePath,
			},

			[ fileInfo.mask2Fingerprint ] = {
				relativePath = fileInfo.mask2RelativeFilePath
			}

		} , '_localOnly', 'files', 'aux' )

	end

	return docValue

end


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

function WFCatCopiesTest.rxTestSetAssetsAsCopies( rxTest )

	rxTest:setTimeLimit( 50 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	-- Test making assets are copies ( none of them are previously copies )
	local docIds = {
		'doc1',
		'doc2',
		'doc3',
	}
	rxTest:expectSingleValue( addDoc( docStore, docIds[ 1 ] ), ':ignore:' )
	rxTest:expectSingleValue( addDoc( docStore, docIds[ 2 ] ), ':ignore:' )
	rxTest:expectSingleValue( addDoc( docStore, docIds[ 3 ] ), ':ignore:' )

	rxTest:expectSingleValue( WFCatCopies.setAssetsAsCopies( fakeWFCatalog, docIds ), ':ignore:' )

	rxTest:expectSingleValue(
		docStore:bulkDocValue( docIds )
			:map( mapPluckCopyIds )
			:map( mapArrayHasIdenticalEntriesFn( 3 ) ), true )


	-- Test making assets are copies ( one of them is already a copy - propagate the copy_id )
	local additionalDocIds = {
		'doc3',
		'doc4',
		'doc5',
	}
	rxTest:expectSingleValue( addDoc( docStore, additionalDocIds[ 2 ] ), ':ignore:' )
	rxTest:expectSingleValue( addDoc( docStore, additionalDocIds[ 3 ] ), ':ignore:' )

	rxTest:expectSingleValue( WFCatCopies.setAssetsAsCopies( fakeWFCatalog, additionalDocIds ), ':ignore:' )


	local allDocIds = {
		'doc1',
		'doc2',
		'doc3',
		'doc4',
		'doc5',
	}

	rxTest:expectSingleValue(
		docStore:bulkDocValue( allDocIds )
			:map( mapPluckCopyIds )
			:map( mapArrayHasIdenticalEntriesFn( 5 ) ), true )


	-- Test making assets are copies ( error: input assets already belong to different copy sets )
	local secondSetDocIds = {
		'doc10',
		'doc11',
	}
	rxTest:expectSingleValue( addDoc( docStore, secondSetDocIds[ 1 ] ), ':ignore:' )
	rxTest:expectSingleValue( addDoc( docStore, secondSetDocIds[ 2 ] ), ':ignore:' )

	rxTest:expectSingleValue( WFCatCopies.setAssetsAsCopies( fakeWFCatalog, secondSetDocIds ), ':ignore:' )

	-- Test error if we are trying to make
	local secondSetAdditionalDocIds = {
		'doc1',
		'doc2',
		'doc10',
	}

	local obs = rxTest:observe( WFCatCopies.setAssetsAsCopies( fakeWFCatalog, secondSetAdditionalDocIds ) )

	obs:expectErrorWithinTime( 2, "Trying to change the copy-id of an asset" )

end

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

function WFCatCopiesTest.rxTestIgnoresSetAssetsAsCopiesWithOneOrZeroDocs( rxTest )

	rxTest:setTimeLimit( 50 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	local docIds = { 'doc1' }

	rxTest:expectSingleValue( addDoc( docStore, docIds[ 1 ] ), ':ignore:' )

	rxTest:expectOnlyCompleted( WFCatCopies.setAssetsAsCopies( fakeWFCatalog, docIds ) )

	rxTest:expectOnlyCompleted( WFCatCopies.setAssetsAsCopies( fakeWFCatalog, {} ) )

end

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

function WFCatCopiesTest.rxTestCreateCopiesOfAssetsOne( rxTest )

	rxTest:setTimeLimit( 50 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	local assetId1 = 'e0be4f816aa149a7a3e8b148145d5135'

	local assetInfo = createAsset( rxTest, fakeWFCatalog, docStore, assetId1 )

	-- Copy a single asset.
	local copiedAssetId

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( fakeWFCatalog, { assetId1 } )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return copiedAssetIds and #copiedAssetIds == 1
		end ),
	true )

	-- get some info from the copied asset.

	local copiedAssetRevId, copyId

	rxTest:expectSingleValue( fakeWFCatalog:docStore():docValue( copiedAssetId )
		:map( function( docValue )
			copiedAssetRevId = docValue._revId
			copyId = docValue.copy.copy_id
			return true
		end ), true )

	-- Compare the new copied doc value (ignoring the _id and _revId values fished out above)
	-- This also verifies that there is no _serverProvidedMetadata in the copied asset.

	rxTest:expectSingleValue(
		fakeWFCatalog:docStore():docValue( copiedAssetId )
			:map( function( docValue )

				docValue._changeCounter = "<changeCounter>"
				docValue._localId = "<localId>"

				return mapAwayPaths( rxTest, fakeWFCatalog, docValue, assetInfo )

			end ), getExpectedCopiedDocValue( assetInfo, assetId1, copiedAssetId, copiedAssetRevId, copyId ) )

	-- compare the original doc value to see that it has the copy_id set now as well

	rxTest:expectSingleValue(
		fakeWFCatalog:docStore():docValue( assetId1 )
			:map( function( docValue )
				return docValue.copy
			end ), { copy_id = copyId } )

end

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

local function testCreateCopiesOfAssetsXmpDevelop( rxTest, fromTrash )

	rxTest:setTimeLimit( 10 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )
	local docStore = fakeWFCatalog:docStore()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local _, assetDoc = createAsset( rxTest, fakeWFCatalog, docStore, assetId )
	local copiedAssetId

	local function makeACopy()

		rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( fakeWFCatalog, { assetId } )
			:map( function( copiedAssetIds )
				copiedAssetId = copiedAssetIds[ 1 ]
				return copiedAssetIds and #copiedAssetIds == 1
			end ), true )

	end

	local function checkSourceXmpDevelopValues( expectedXmpDevelopLink, expectedXmpDevelopSha256 )

		rxTest:expectSingleValue( fakeWFCatalog:docStore():docValue( copiedAssetId )
			:map( function( docValue )
				return docValue._localOnly.sourceXmpDevelopLink == expectedXmpDevelopLink and
					docValue._localOnly.sourceXmpDevelopSha256 == expectedXmpDevelopSha256
			end ), true )

	end

	-- set up overlay for getting data from _serverProvidedMetadata

	local overlay = {
		_id = assetId,
		_serverProvidedMetadata = { links = { [ '/rels/xmp/develop' ] = { } } },
	}
	local linksTable = overlay._serverProvidedMetadata.links
	local xmpDevelopLinksTable = linksTable[ '/rels/xmp/develop' ]

	if fromTrash then

		-- set up overlay for getting data from _localOnly.universal.trash
		overlay._localOnly = { universal = { trash = { createFrom = {
			links = { [ '/rels/xmp/develop' ] = KSTableUtils_derefOpt( assetDoc, '_serverProvidedMetadata', 'links', '/rels/xmp/develop' ), },
		} } } }

		linksTable = overlay._localOnly.universal.trash.createFrom.links

		xmpDevelopLinksTable._delete = true
			-- also need to remove the /rels/xmp/develop form _serverProvidedMetadata

		rxTest:waitFor( docStore:applyOverlay( overlay ) )
			-- fix the assetDoc so data will come from local trash section instead of server provided links

		overlay._serverProvidedMetadata = nil
		linksTable[ '/rels/xmp/develop' ] = {}
		xmpDevelopLinksTable = linksTable[ '/rels/xmp/develop' ]

	end

	-- confirm that sourceXmpDevelopLink and sourceXmpDevelopSha256 are both populated

	makeACopy()
	checkSourceXmpDevelopValues( '/href/bogus/xmp_develop', assetDoc.develop.xmpCameraRaw.sha256 )

	-- set invalid = true and confirm that this means neither field is populated

	xmpDevelopLinksTable.invalid = true

	rxTest:waitFor( docStore:applyOverlay( overlay ) )
	makeACopy()
	checkSourceXmpDevelopValues( nil, nil )

	-- clear invalid = true and apply a binary download error for the xmpDevelop link
	-- and confirm that this means neither field is populated

	xmpDevelopLinksTable.invalid = { _delete = true }
	KSTableUtils.setNested( overlay, 'whatever', '_localOnly', 'binaryDownloadErrors', '/rels/xmp/develop',
		'/href/bogus/xmp_develop', 'SomeBadError', 'data' )

	rxTest:waitFor( docStore:applyOverlay( overlay ) )
	makeACopy()
	checkSourceXmpDevelopValues( nil, nil )

	-- apply a binary download error for a different xmpDevelop link and confirm that both values are populated

	KSTableUtils.setNested( overlay, true, '_localOnly', 'binaryDownloadErrors', '/rels/xmp/develop',
		'/href/bogus/xmp_develop', '_delete' )
	KSTableUtils.setNested( overlay, 'it was bad', '_localOnly', 'binaryDownloadErrors', '/rels/xmp/develop',
		'/href/bogussss/xmp_develop', 'AnotherError', 'errors', 1 )

	rxTest:waitFor( docStore:applyOverlay( overlay ) )
	makeACopy()
	checkSourceXmpDevelopValues( '/href/bogus/xmp_develop', assetDoc.develop.xmpCameraRaw.sha256 )

	-- clear binary download error and remove sha256 from link and confirm that sourceXmpDevelopLink
	-- is populated and sourceXmpDevelopSha256 is nil

	overlay._localOnly.binaryDownloadErrors = { _delete = true }
	xmpDevelopLinksTable.sha256 = { _delete = true }

	rxTest:waitFor( docStore:applyOverlay( overlay ) )
	makeACopy()
	checkSourceXmpDevelopValues( '/href/bogus/xmp_develop', nil )

	-- set link sha256 to a different value than value in develop section and confirm both values are nil

	local newSha256 = KSIdGenerator.uuidInLowercaseHex() .. KSIdGenerator.uuidInLowercaseHex()
	xmpDevelopLinksTable.sha256 = newSha256

	rxTest:waitFor( docStore:applyOverlay( overlay ) )
	makeACopy()
	checkSourceXmpDevelopValues( nil, nil )

	-- make xmpCameraRaw inline and confirm that both values are nil

	rxTest:waitFor( docStore:applyOverlay {
		_id = assetId,
		develop = { xmpCameraRaw = 'foo' },
	} )
	makeACopy()
	checkSourceXmpDevelopValues( nil, nil )

	-- set xmpCameraRaw.sha256 to newSha256 and confirm that both values are populated

	rxTest:waitFor( docStore:applyOverlay {
		_id = assetId,
		develop = { xmpCameraRaw = { sha256 = newSha256 } },
	} )
	makeACopy()
	checkSourceXmpDevelopValues( '/href/bogus/xmp_develop', newSha256 )

	--  and remove /rels/xmp/develop link and confirm both values are nil

	linksTable[ '/rels/xmp/develop' ] = { _delete = true }

	rxTest:waitFor( docStore:applyOverlay( overlay ) )
	makeACopy()
	checkSourceXmpDevelopValues( nil, nil )

end

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

function WFCatCopiesTest.rxTestCreateCopiesOfAssetsC2pa( rxTest )

	rxTest:setTimeLimit( 10 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )
	local docStore = fakeWFCatalog:docStore()
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local optOverrides = {
		hasC2pa = true,
	}
	
	local _, assetDoc = createAsset( rxTest, fakeWFCatalog, docStore, assetId, optOverrides )
	local copiedAssetId

	local function makeACopy()

		rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( fakeWFCatalog, { assetId } )
			:map( function( copiedAssetIds )
				copiedAssetId = copiedAssetIds[ 1 ]
				return copiedAssetIds and #copiedAssetIds == 1
			end ), true )

	end
	
	makeACopy()
	
	rxTest:expectSingleValue( fakeWFCatalog:docStore():docValue( copiedAssetId )
		:map( function( docValue )
			return KSTableUtils.deepEquals( docValue._localOnly.files.aux,assetDoc._localOnly.files.aux )
			and KSTableUtils.deepEquals( docValue.aux,assetDoc.aux ) 
			and  KSTableUtils.deepEquals( docValue.importSource.c2paIngredients,assetDoc.importSource.c2paIngredients ) 
		end ), true )
end

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

function WFCatCopiesTest.rxTestCreateCopiesOfAssetsXmpDevelop( rxTest )

	testCreateCopiesOfAssetsXmpDevelop( rxTest )

end

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

function WFCatCopiesTest.rxTestCreateCopiesOfAssetsXmpDevelopFromTrash( rxTest )

	local fromTrash = true
	testCreateCopiesOfAssetsXmpDevelop( rxTest, fromTrash )

end

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

function WFCatCopiesTest.rxTestCreateCopiesOfAssetsInAlbum( rxTest )

	rxTest:setTimeLimit( 50 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	local assetId1 = 'e0be4f816aa149a7a3e8b148145d5135'

	local assetInfo = createAsset( rxTest, fakeWFCatalog, docStore, assetId1 )

	local localAssetId = rxTest:getFirstValue( fakeWFCatalog:docStore():docIdToLocalId( assetId1 ) )

	local albumObservable = WFCatAlbums.createAlbum( fakeWFCatalog, 'test album' )

	local _, albumId = rxTest:getFirstValue( albumObservable )

	rxTest:getFirstValue( fakeWFCatalog:docStore():setDocValue {
		_id = KSIdGenerator.uuidInLowercaseHex(),
		type = 'album_asset',
		album = {
			id = albumId,
		},
		asset = {
			id = assetId1,
		},
	} )

	-- Copy a single asset.
	local copiedAssetId

	local platformAssetId
	if WFPlatformInfo.isDesktop then
		platformAssetId = localAssetId
	else
		platformAssetId = assetId1
	end

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( fakeWFCatalog, { platformAssetId }, { albumId } )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return copiedAssetIds and #copiedAssetIds == 1
		end ),
	true )

	-- get some info from the copied asset.

	local copiedAssetRevId, copyId

	rxTest:expectSingleValue( fakeWFCatalog:docStore():docValue( copiedAssetId )
		:map( function( docValue )
			copiedAssetRevId = docValue._revId
			copyId = docValue.copy.copy_id
			return true
		end ), true )

	-- Compare the new copied doc value (ignoring the _id and _revId values fished out above)
	-- This also verifies that there is no _serverProvidedMetadata in the copied asset.

	rxTest:expectSingleValue(
		fakeWFCatalog:docStore():docValue( copiedAssetId )
			:map( function( docValue )

				docValue._changeCounter = "<changeCounter>"
				docValue._localId = "<localId>"

				return mapAwayPaths( rxTest, fakeWFCatalog, docValue, assetInfo )

			end ), getExpectedCopiedDocValue( assetInfo, assetId1, copiedAssetId, copiedAssetRevId, copyId ) )


	local albumAssetTableName
	if WFPlatformInfo.isDesktop then
		albumAssetTableName = 'album_asset_v2'
	else
		albumAssetTableName = 'album_asset'
	end

	rxTest:expectSingleValue(
		fakeWFCatalog:indexStore():queryOnce {
			sql = 'SELECT COUNT( docId ) FROM ' .. albumAssetTableName .. ' WHERE albumId == ?',
			quiesceUntilUpToDate = true,
			albumId }, { { 2 } } )

end

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

function WFCatCopiesTest.rxTestCreateCopiesOfAssetsNoLocalProxyOrOriginal( rxTest )

	rxTest:setTimeLimit( 50 )

	local mockThumb = KSData.make( 'proxy_data' )

	local testFileName = "IMG_7706.PNG"

	local tempFolder = WFTestFileUtils.createTestFolder( rxTest )

	local filePath = KSPathUtils.child( tempFolder, testFileName )
	KSData.saveToFile( mockThumb, filePath )

	rxTest:finally( function()
		WFFileUtils.delete( filePath )
		mockThumb:close()
	end )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	local assetId1 = 'e0be4f816aa149a7a3e8b148145d5135'

	local revId = KSIdGenerator.uuidInLowercaseHex()

	local srcDocValue = {
			_id = assetId1,
			type = 'asset',
			subtype = 'image',
			_newRevId = revId,
			_serverProvidedMetadata = {
				links = {
					[ '/rels/master' ] = { href = '/href/bogus/testing' },
					[ '/rels/xmp' ] = { href = '/href/bogus/testing' }
				}
			},
		}

	-- Create an asset with no local files

	rxTest:expectSingleValue( docStore:setDocValue( srcDocValue ), ':ignore:' )

	-- Copy a thumbnail to the proper location.

	local NmbLocalRenditionDigest, srcThumbnailPath, thumbnailContentId, thumbnailFileSize
	local srcThumbnailMd5

	if WFPlatformInfo.isDesktop then

		NmbLocalRenditionDigest = require 'NmbLocalRenditionDigest'
		local md5XmpCameraRawSrc = NmbLocalRenditionDigest.maybeComputeFromDocValue( srcDocValue)
		srcThumbnailPath = fakeWFCatalog:fileSpec():pathForPreviewDigestAndLongEdge( srcDocValue._id, md5XmpCameraRawSrc, 320, true )
		KSData.saveToFile( mockThumb, srcThumbnailPath )

		rxTest:finally( function()
			WFFileUtils.delete( srcThumbnailPath )
		end )

	elseif WFPlatformInfo.isiOS then


		thumbnailContentId = KSIdGenerator.uuidInLowercaseHex()

		srcThumbnailPath = KSPathUtils.child( tempFolder, KSIdGenerator.uuidInLowercaseHex() )
		KSData.saveToFile( mockThumb, srcThumbnailPath )

		rxTest:finally( function()
			WFFileUtils.delete( srcThumbnailPath )
		end )

		thumbnailFileSize = WFFileUtils.fileAttributes( srcThumbnailPath ).fileSize

		rxTest:expectSingleValue( docStore:applyOverlay( {

			_id = assetId1,

			_localOnly = {
				files = {
					thumbnail2x = {
						path = srcThumbnailPath,
						contentId = thumbnailContentId,
						fileSize = thumbnailFileSize,
					}
				}
			}

		} ), ':ignore:' )

	elseif WFPlatformInfo.isAndroid then

		srcThumbnailPath = KSPathUtils.child( tempFolder, KSIdGenerator.uuidInLowercaseHex() )
		KSData.saveToFile( mockThumb, srcThumbnailPath )

		rxTest:finally( function()
			WFFileUtils.delete( srcThumbnailPath )
		end )

		thumbnailFileSize = WFFileUtils.fileAttributes( srcThumbnailPath ).fileSize

		srcThumbnailMd5 = mockThumb:digest( 'MD5' )

		rxTest:expectSingleValue( docStore:applyOverlay( {

			_id = assetId1,

			_localOnly = {
				thumbnail2x = {
					md5 = srcThumbnailMd5,
					revId = revId,
					size = thumbnailFileSize,
				}
			}

		} ), ':ignore:' )

	end

	-- Copy a single asset.

	local copiedAssetId

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( fakeWFCatalog, { assetId1 } )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return copiedAssetIds and #copiedAssetIds == 1
		end ),
	true )

	-- get some info from the copied asset.

	local copiedAssetRevId, copyId

	if WFPlatformInfo.isDesktop then

		rxTest:expectSingleValue( fakeWFCatalog:docStore():docValue( copiedAssetId )
			:map( function( copyDocValue )
				copiedAssetRevId = copyDocValue._revId
				copyId = copyDocValue.copy.copy_id

				local md5XmpCameraRawCpy = NmbLocalRenditionDigest.maybeComputeFromDocValue( copyDocValue )
				local cpyThumbnailPath = fakeWFCatalog:fileSpec():pathForPreviewDigestAndLongEdge( copyDocValue._id, md5XmpCameraRawCpy, 320 )

				-- Make sure the thumbnail file was copied to the correct location.
				rxTest:expectNotEqual( srcThumbnailPath, cpyThumbnailPath )
				rxTest:expectEqual( 'file', WFFileUtils.exists( cpyThumbnailPath ) )

				return true
			end ), true )

		-- Compare the new copied doc value (ignoring the _id and _revId values fished out above)
		-- This also verifies that there is no _serverProvidedMetadata in the copied asset.
		rxTest:expectSingleValue(
			fakeWFCatalog:docStore():docValue( copiedAssetId ),
			{
				_changeCounter = 3,
				_id = copiedAssetId,
				_localId = 2,
				_localOnly = {
					sourceAssetId = assetId1,
					sourceOriginalLink = "/href/bogus/testing",
					sourceXmpLink = "/href/bogus/testing",
					xmpSidecarExists = true,
				},
				_revId = copiedAssetRevId,
				copy = {
					copy_id = copyId,
				},
				subtype = "image",
				type = "asset",
			}
		)

	elseif WFPlatformInfo.isiOS then

		rxTest:expectSingleValue( fakeWFCatalog:docStore():docValue( copiedAssetId )
			:map( function( copyDocValue )

				copiedAssetRevId = copyDocValue._revId
				copyId = copyDocValue.copy.copy_id

				local cpyThumbnail = KSTableUtils_derefOpt( copyDocValue, '_localOnly', 'files', 'thumbnail2x', 'path' )

				local basePath = OzFilePaths.getCatalogBasePath( fakeWFCatalog:ozCatalogId() )
				local absolutePath = KSPathUtils.child( basePath, cpyThumbnail )

				-- Make sure the thumbnail data was copied
				rxTest:expectEqual( 'file', WFFileUtils.exists( absolutePath ) )

				return true

			end ), true )

		-- Compare the new copied doc value (ignoring the _id and _revId values fished out above)
		-- This also verifies that there is no _serverProvidedMetadata in the copied asset.
		rxTest:expectSingleValue(
			fakeWFCatalog:docStore():docValue( copiedAssetId )
				:map( function( copyDocValue )

					copyDocValue._localOnly.files.thumbnail2x.path = '<newThumbnailPath>'

					return copyDocValue

				end ),
			{
				_changeCounter = 4,
				_id = copiedAssetId,
				_localId = 2,
				_localOnly = {
					files = {
						thumbnail2x = {
							path = '<newThumbnailPath>',
							contentId = thumbnailContentId,
							fileSize = thumbnailFileSize,
						}
					},
					sourceAssetId = assetId1,
					sourceOriginalLink = "/href/bogus/testing",
					sourceXmpLink = "/href/bogus/testing",
					xmpSidecarExists = true,
				},
				_revId = copiedAssetRevId,
				copy = {
					copy_id = copyId,
				},
				subtype = "image",
				type = "asset",
			}
		)

	elseif WFPlatformInfo.isAndroid then

		rxTest:expectSingleValue( fakeWFCatalog:docStore():docValue( copiedAssetId )
			:map( function( copyDocValue )

				copiedAssetRevId = copyDocValue._revId
				copyId = copyDocValue.copy.copy_id

				local cpyThumbnailMd5 = KSTableUtils_derefOpt( copyDocValue, '_localOnly', 'thumbnail2x', 'md5' )

				return cpyThumbnailMd5

			end ), srcThumbnailMd5 )

		-- Compare the new copied doc value (ignoring the _id and _revId values fished out above)
		-- This also verifies that there is no _serverProvidedMetadata in the copied asset.

		local expectedDocValue = {
			_changeCounter = 4,
			_id = copiedAssetId,
			_localId = 2,
			_localOnly = {
				thumbnail2x = {
					md5 = srcThumbnailMd5,
					revId = copiedAssetRevId,
					size = thumbnailFileSize,
				},
				sourceAssetId = assetId1,
				sourceOriginalLink = "/href/bogus/testing",
				sourceXmpLink = "/href/bogus/testing",
				xmpSidecarExists = true,
			},
			_revId = copiedAssetRevId,
			copy = {
				copy_id = copyId,
			},
			subtype = "image",
			type = "asset",
		}

		rxTest:logf( "expectedDocValue %s", expectedDocValue )

		rxTest:expectSingleValue(
			fakeWFCatalog:docStore():docValue( copiedAssetId )
				:map( function( copyDocValue )

					rxTest:logf( "copyDocValue %s", copyDocValue )

					return copyDocValue

				end ), expectedDocValue
		)

	end

	-- compare the original doc value to see that it has the copy_id set now as well

	rxTest:expectSingleValue(
		fakeWFCatalog:docStore():docValue( assetId1 )
			:map( function( docValue )
				return docValue.copy
			end ), { copy_id = copyId } )

end

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

function WFCatCopiesTest.reportAllChanges( scanState, docValue )
	return scanState, docValue
end

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

function WFCatCopiesTest.rxTestCreateCopiesOfAssetsMultiple( rxTest )

	rxTest:setTimeLimit( 300 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	local assetId1 = '11114f816aa149a7a3e8b148145d1111'
	local assetId2 = '22224f816aa149a7a3e8b148145d2222'

	local assetIdsToCopy = { assetId1, assetId2 }
	local copyId1
	local copyId2
	local numCopiesAsset1 = 0
	local numCopiesAsset2 = 0
	local numDocUpdates = 0
	local asset1UpdateCount = 0
	local asset2UpdateCount = 0

	local changedDocsObserver = rxTest:observe(
		fakeWFCatalog:docStore():scannedFilteredMappedChangesFeedSince( "WFCatCopiesTest" , "reportAllChanges", {}, 1 )
		:map( function( doc )

			numDocUpdates = numDocUpdates + 1

			if doc._id == assetId1 then
				asset1UpdateCount = asset1UpdateCount + 1
			elseif doc._id == assetId2 then
				asset2UpdateCount = asset2UpdateCount + 1
			else
				table.insert( assetIdsToCopy, doc._id )
			end

			if doc.copy then
				if copyId1 == nil then
					copyId1 = doc.copy.copy_id
				end

				if doc.copy.copy_id ~= copyId1 then
					if copyId2 == nil then
						copyId2 = doc.copy.copy_id
					end
				end

				if doc.copy.copy_id == copyId1 then
					numCopiesAsset1 = numCopiesAsset1 + 1
				end

				if doc.copy.copy_id == copyId2 then
					numCopiesAsset2 = numCopiesAsset2 + 1
				end

			end

			return numDocUpdates, numCopiesAsset1, numCopiesAsset2

		end )
	)

	-- Create an asset with no local files
	rxTest:expectSingleValue( docStore:setDocValue {
			_id = assetId1,
			type = 'asset',
			subtype = 'image',
			_serverProvidedMetadata = {
				links = {
					[ '/rels/master' ] = { href = '/href/bogus/testing' }
				}
			},
		}, ':ignore:' )

	-- Create another asset with no local files
	rxTest:expectSingleValue( docStore:setDocValue {
			_id = assetId2,
			type = 'asset',
			subtype = 'image',
			_serverProvidedMetadata = {
				links = {
					[ '/rels/master' ] = { href = '/href/bogus/testing2' }
				}
			},
		}, ':ignore:' )


	-- Wait for the two assets to show up, there will be no copies.
	changedDocsObserver:expectDataWithinTime( 45, 2, 0, 0 )

	-- reset numDocUpdates to ignore the doc updates made to create the assets

	numDocUpdates = 0

	local numDocUpdatesExpected = 4

	while #assetIdsToCopy < 256 do

		rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( fakeWFCatalog, assetIdsToCopy )
			:map( function( copiedAssetIds )
				return #copiedAssetIds
			end )
		, #assetIdsToCopy )

		changedDocsObserver:expectDataWithinTime( 45, numDocUpdatesExpected, numDocUpdatesExpected / 2, numDocUpdatesExpected / 2 )

		numDocUpdatesExpected = numDocUpdatesExpected * 2

	end

	rxTest:expectEqual( #assetIdsToCopy, 256 )

end

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

function WFCatCopiesTest.rxTestCopiedDocModifierFn( rxTest )

	rxTest:setTimeLimit( 50 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	local assetId1 = '11114f816aa149a7a3e8b148145d1111'

	rxTest:expectSingleValue( docStore:setDocValue {
			_id = assetId1,
			type = 'asset',
			subtype = 'image',
			_serverProvidedMetadata = {
				links = {
					[ '/rels/master' ] = { href = '/href/bogus/testing' }
				}
			},
		}, ':ignore:' )

	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local copiedDocModifierFn = function( copiedDocValue )

		copiedDocValue.subtype = 'image'
		copiedDocValue._localOnly = {
			foo = true
		}
		copiedDocValue.spaceId = spaceId
		return copiedDocValue

	end

	local copiedAssetId

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( fakeWFCatalog, { assetId1 }, nil, nil, copiedDocModifierFn )
		:map( function( copiedAssetIds )

			copiedAssetId = copiedAssetIds[ 1 ]

			return #copiedAssetIds == 1
					and type( copiedAssetId ) == 'string'
					and copiedAssetId ~= assetId1

		end ), true )

	rxTest:expectSingleValue( fakeWFCatalog:docStore()
			:docValue( copiedAssetId )
			:map( function( docValue )
				docValue._changeCounter = nil
				docValue._localId = nil
				docValue._revId = nil
				docValue.copy.copy_id = "<copy_id>"
				return docValue
			end ),
			{
				_id = copiedAssetId,
				_localOnly = {
					foo = true,
				},
				spaceId = spaceId,
				copy = {
					copy_id = "<copy_id>",
				},
				subtype = "image",
				type = "asset",
			} )

end

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

function WFCatCopiesTest.rxTestCalculateCopyPathsAndUpdateDocValue( rxTest )

	rxTest:setTimeLimit( 50 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	local assetId1 = '123e4f816aa149a7a3e8b148145d5135'
	local assetId2 = '234e4f816aa149a7a3e8b148145d5135'

	local assetInfo, srcDocValue = createAsset( rxTest, fakeWFCatalog, docStore, assetId1 )

	local dstAssetInfo, dstDocValue = createAsset( rxTest, fakeWFCatalog, docStore, assetId2 )

	if WFPlatformInfo.isDesktop then
		KSTableUtils.setNested( dstDocValue, nil, '_localOnly', 'files', 'original', 'master', 'sha256' )
		KSTableUtils.setNested( dstDocValue, nil, '_localOnly', 'files', 'proxy', '2560', 'sha256' )
	elseif WFPlatformInfo.isiOS then
		KSTableUtils.setNested( dstDocValue, nil, '_localOnly', 'files', 'latestMaster' )
	elseif WFPlatformInfo.isAndroid then
		KSTableUtils.setNested( dstDocValue, nil, '_localOnly', 'original', 'epoch' )
		KSTableUtils.setNested( dstDocValue, nil, '_localOnly', 'proxy', 'epoch' )
		KSTableUtils.setNested( dstDocValue, nil, '_localOnly', 'latestOriginal' )
	end

	local expectedDstDocValue = getExpectedCopiedDocValue( assetInfo, assetId1, assetId2, nil, nil, dstAssetInfo.developSettingsSha256 )
	expectedDstDocValue.importSource = nil
	expectedDstDocValue._changeCounter = nil
	expectedDstDocValue._localId = nil
	expectedDstDocValue._serverProvidedMetadata = nil
	expectedDstDocValue._revId = nil
	expectedDstDocValue.copy = nil
	expectedDstDocValue._localOnly.sourceAssetId = nil
	expectedDstDocValue._localOnly.sourceXmpDevelopLink = nil
	expectedDstDocValue._localOnly.sourceXmpDevelopSha256 = nil
	expectedDstDocValue._localOnly.sourceOriginalLink = nil
	expectedDstDocValue._localOnly.sourceProxyLink = nil
	expectedDstDocValue._localOnly.sourceXmpLink = nil

	local expectedFilesToCopy = {}

	if WFPlatformInfo.isDesktop then

		expectedFilesToCopy = {
				{
					srcFilePath = assetInfo.thumbnailFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.previewFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.originalFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.proxyFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.xmpSidecarFilePath,
					dstFilePath = '<newPath>',
				}
			}

	elseif WFPlatformInfo.isiOS then
		expectedDstDocValue._localOnly.xmpSidecarExists = nil

		expectedFilesToCopy = {
				{
					srcFilePath = assetInfo.thumbnailFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.previewFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.fullsizeFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.originalFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.proxyFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.xmpDevelopSettingsFilePath,
					dstFilePath = '<newPath>',
				},
			}

	elseif WFPlatformInfo.isAndroid then
		expectedDstDocValue._localOnly.xmpSidecarExists = nil

		expectedFilesToCopy = {
				{
					srcFilePath = assetInfo.originalFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.proxyFilePath,
					dstFilePath = '<newPath>',
				}
			}

	end

	rxTest:expectSingleValue( Observable.just(
			WFCatCopies.calculateCopyPathsAndUpdateDocValue( fakeWFCatalog, srcDocValue, dstDocValue ) )
				:map( function( newDstDocValue, filesToCopy )

					for _, fileToCopy in ipairs( filesToCopy ) do

						local srcFilePath = fileToCopy.srcFilePath
						local dstFilePath = fileToCopy.dstFilePath

						rxTest:expectNotEqual( srcFilePath, dstFilePath )

						fileToCopy.dstFilePath = '<newPath>'

					end

					newDstDocValue.importSource = nil
					newDstDocValue._serverProvidedMetadata = nil
					newDstDocValue._newRevId = nil

					return mapAwayPaths( rxTest, fakeWFCatalog, newDstDocValue, assetInfo, false ), filesToCopy

				end ),
			expectedDstDocValue,
			expectedFilesToCopy )

end

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

function WFCatCopiesTest.rxTestCopyAsContributor( rxTest )

	rxTest:setTimeLimit( 50 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	-- Test 1: Original And Proxy on Oz.

	local assetId1 = KSIdGenerator.uuidInLowercaseHex()

	createAsset( rxTest, fakeWFCatalog, docStore, assetId1 )

	local copiedAssetId

	local optArgs = {
		isContributorCopyingToSharedAlbum = true
			-- if isContributorCopyingToSharedAlbum is set to true, the original/proxy
			-- should only be copied if the source asset hasn't been uploaded or if
			-- there are no renditions. This is because contributors should not have
			-- access to proxies or originals, even for those images that they
			-- contributed.
	}

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets(
			fakeWFCatalog, { assetId1 }, nil, nil, nil, nil, nil, optArgs )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return copiedAssetIds and #copiedAssetIds == 1
		end ),
	true )

	-- Compare the source asset to the copied asset, verifying the binaries were not copied.

	rxTest:expectSingleValue(
		fakeWFCatalog:docStore():docValue( copiedAssetId )
			:map( function( docValue )

				local copy_originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
				local copy_proxyPath = WFCatAssetUtils.getFullProxyPathFromDocValue( docValue )

				return not not copy_originalPath, not not copy_proxyPath

			end ), false, false )

	-- Test 2: No Original or Proxy on Oz

	local assetId2 = KSIdGenerator.uuidInLowercaseHex()

	local optOverrides = {
		noMasterLink = true,
		noProxyLink = true
	}

	createAsset( rxTest, fakeWFCatalog, docStore, assetId2, optOverrides )

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets(
			fakeWFCatalog, { assetId2 }, nil, nil, nil, nil, nil, optArgs )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return copiedAssetIds and #copiedAssetIds == 1
		end ),
	true )

	-- Compare the source asset to the copied asset, verifying the binaries were copied.

	rxTest:expectSingleValue(
		fakeWFCatalog:docStore():docValue( copiedAssetId )
			:map( function( docValue )

				local copy_originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
				local copy_proxyPath = WFCatAssetUtils.getFullProxyPathFromDocValue( docValue )

				return not not copy_originalPath, not not copy_proxyPath

			end ), true, true )
end

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

if WFPlatformInfo.isMobile() then

function WFCatCopiesTest.rxTestCopyXmpDevelopToNewPathArg( rxTest )

	rxTest:setTimeLimit( 240 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	-- Test 1: Original And Proxy on Oz.

	local assetId1 = KSIdGenerator.uuidInLowercaseHex()

	local assetInfo = createAsset( rxTest, fakeWFCatalog, docStore, assetId1 )

	local copiedAssetId

	local optArgs = {
		isXmpDevelopCopyingToNewPath = true
			-- if isXmpDevelopCopyingToNewPath is set to true, the xmp_develop file
			-- should be copied to a new path
	}

	local spaceId = KSIdGenerator.uuidInLowercaseHex()

	local copiedDocModifierFn = function( copiedDocValue )
		copiedDocValue._spaceId = spaceId
		return copiedDocValue
	end

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets(
			fakeWFCatalog, { assetId1 }, nil, nil, copiedDocModifierFn, nil, nil, optArgs )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return copiedAssetIds and #copiedAssetIds == 1
		end ),
	true )

	-- Compare the source asset to the copied asset, verifying the xmp file is copied

	rxTest:expectSingleValue(
		fakeWFCatalog:docStore():docValue( copiedAssetId )
			:map( function( docValue )

				local xmpPath = WFCatAssetUtils.getLocalExternalDevelopSettingsPath( docValue )

				return xmpPath ~= assetInfo.xmpDevelopSettingsFilePath

			end ), true )

end

end

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

-- FIXME [jandrew 2025-03-11]: Disabled due to instability.

function WFCatCopiesTest.DISABLED_rxTestCopyAsset_willNotUploadMaster( rxTest )

	rxTest:setTimeLimit( 10 )

	local wfCatalog = createFakeWFCatalog( rxTest )

	-- Insert 3 assets into the doc store
	-- assetDoc1: willNotUploadMaster = true, no master link, or local master
	-- assetDoc2: willNotUploadMaster = true, master link, no local master
	-- assetDoc3: willNotUploadMaster = true, no master link, local master

	local function buildDocValue( rxTest, wfCatalog, assetId, overrides )

		assetId = assetId or KSIdGenerator.uuidInLowercaseHex()
		return WFCatAssetTestUtils.createAssetDocument( rxTest, wfCatalog, assetId, overrides )

	end

	local assetDoc1 = buildDocValue( rxTest, wfCatalog, nil, { noLocalOriginal = true, noMasterLink = true, willNotUploadMaster = true } )
	local assetDoc2 = buildDocValue( rxTest, wfCatalog, nil, { noLocalOriginal = true, willNotUploadMaster = true }  )
	local assetDoc3 = buildDocValue( rxTest, wfCatalog, nil, { noMasterLink = true, willNotUploadMaster = true }  )

	local assetIds = {}
	table.insert( assetIds, assetDoc1._id )
	table.insert( assetIds, assetDoc2._id )
	table.insert( assetIds, assetDoc3._id )

	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc1 ) )
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc2 ) )
	rxTest:waitFor( wfCatalog:docStore():setDocValue( assetDoc3 ) )

	-- Copy the assets.

	local copiedAssetIds =
			rxTest:getFirstValue( WFCatCopies.createCopiesOfAssets( wfCatalog, { assetDoc1._id, assetDoc2._id, assetDoc3._id } ) )

	local copiedDoc1FromDocStore = rxTest:getFirstValue( wfCatalog:docStore():docValue( copiedAssetIds[ 1 ] ) )

	local copiedDoc2FromDocStore = rxTest:getFirstValue( wfCatalog:docStore():docValue( copiedAssetIds[ 2 ] ) )

	local copiedDoc3FromDocStore = rxTest:getFirstValue( wfCatalog:docStore():docValue( copiedAssetIds[ 3 ] ) )

	assert( KSTableUtils.derefOpt( copiedDoc1FromDocStore, 'importSource', 'willNotUploadMaster' ), "copied doc 1 should be willNotUploadMaster = true" )
	assert( KSTableUtils.derefOpt( copiedDoc2FromDocStore, 'importSource', 'willNotUploadMaster' ) == nil, "copied doc 2 should be willNotUploadMaster nil" )
	assert( KSTableUtils.derefOpt( copiedDoc3FromDocStore, 'importSource', 'willNotUploadMaster' ) == nil, "copied doc 3 should be willNotUploadMaster nil" )

end

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

-- @mark Video assets

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

local function getLocalProxiesTypeFromOptOverrides( optOverrides )
	local localProxiesType = {}

	if KSTableUtils.derefOpt( optOverrides, 'hasLocal1080p' ) then
		local localProxyType = '1080p'
		localProxiesType[ #localProxiesType + 1 ] = localProxyType
	end
	
	if KSTableUtils.derefOpt( optOverrides, 'hasLocal1080p709' ) then
		local localProxyType = '1080p709'
		localProxiesType[ #localProxiesType + 1 ] = localProxyType
	end
	
	if KSTableUtils.derefOpt( optOverrides, 'hasLocal360p' ) then
		local localProxyType = '360p'
		localProxiesType[ #localProxiesType + 1 ] = localProxyType
	end

	if KSTableUtils.derefOpt( optOverrides, 'hasLocal720p' ) then
		local localProxyType = '720p'
		localProxiesType[ #localProxiesType + 1 ] = localProxyType
	end

	return localProxiesType
end

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

local function videoMapAwayPaths( rxTest, wfCatalog, docValue, fileInfo, performFileCheck, optOverrides )

	docValue.captureDate = nil

	performFileCheck = performFileCheck ~= false

	local localProxiesType = getLocalProxiesTypeFromOptOverrides( optOverrides )

	if WFPlatformInfo.isDesktop then

		for _, localProxyType in ipairs( localProxiesType ) do
			local proxyFilePath = 'proxy' .. localProxyType .. 'FilePath'
			local proxyFileSize = 'proxy' .. localProxyType .. 'FileSize'
			local copy_proxyPath = WFCatAssetUtils.getFullProxyPathFromDocValue( docValue, localProxyType )
			rxTest:logf( "copy_proxyPath %s", copy_proxyPath )

			if performFileCheck then
				rxTest:expectEqual( fileInfo[proxyFileSize], WFFileUtils.fileAttributes( copy_proxyPath ).fileSize )
			end

			if copy_proxyPath and copy_proxyPath ~= fileInfo[proxyFilePath] then

				docValue._localOnly.files.proxy[localProxyType].relativePath = '<newProxyPath>'
				docValue._localOnly.files.proxy[localProxyType].volumeGuid = '<newProxyVolumeGuid>'
				docValue._localOnly.files.proxy[localProxyType].size = fileInfo[proxyFileSize]

			end
		end

		local copy_originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )

		rxTest:logf( "copy_originalPath %s", copy_originalPath )

		if performFileCheck then
			rxTest:expectEqual( fileInfo.originalFileSize, WFFileUtils.fileAttributes( copy_originalPath ).fileSize )
		end

		if copy_originalPath ~= fileInfo.originalFilePath then
			docValue._localOnly.files.original.master.relativePath = '<newOriginalPath>'
			docValue._localOnly.files.original.master.volumeGuid = '<newOriginalGuid>'
		end

		if not fileInfo.xmpSidecarPath then
			KSTableUtils.setNested( docValue, nil, '_localOnly', 'files', 'original', 'xmp' )
		end

	elseif WFPlatformInfo.isiOS then

		for _, localProxyType in ipairs( localProxiesType ) do
			local proxyContentId = 'proxy' .. localProxyType .. 'ContentId'
			local proxyFileSize = 'proxy' .. localProxyType .. 'FileSize'
			local proxyFilePath = 'proxy' .. localProxyType .. 'FilePath'
			local copy_proxyRelativePath = WFCatAssetUtils.getProxyRelativePathFromDocValue( docValue, localProxyType )
			local copy_proxyPath = absolutePath( wfCatalog:ozCatalogId(), copy_proxyRelativePath )

			rxTest:logf( "copy_proxyPath %s", copy_proxyPath )

			if performFileCheck then
				rxTest:expectEqual( fileInfo[proxyFileSize], WFFileUtils.fileAttributes( copy_proxyPath ).fileSize )
			end

			if copy_proxyPath ~= fileInfo[proxyFilePath] then

				docValue._localOnly.files.proxies[ localProxyType ].relativePath = '<newProxyPath>'
				docValue._localOnly.files.proxies[ localProxyType ].contentId = fileInfo[proxyContentId]
				docValue._localOnly.files.proxies[ localProxyType ].fileSize = fileInfo[proxyFileSize]

			end

		end

		local copy_originalRelativePath = WFCatAssetUtils.getOriginalRelativePathFromDocValue( docValue )
		local copy_originalPath = absolutePath( wfCatalog:ozCatalogId(), copy_originalRelativePath )

		rxTest:logf( "copy_originalPath %s", copy_originalPath )

		if performFileCheck then
			rxTest:expectEqual( fileInfo.originalFileSize, WFFileUtils.fileAttributes( copy_originalPath ).fileSize )
		end

		local copy_previewPath = KSTableUtils_derefOpt( docValue, '_localOnly', 'files', 'preview', 'path' )
		local copy_thumbnailPath = KSTableUtils_derefOpt( docValue, '_localOnly', 'files', 'thumbnail2x', 'path' )
		local copy_fullsizePath = KSTableUtils_derefOpt( docValue, '_localOnly', 'files', 'fullsize', 'path' )

		if performFileCheck then

			local absolutePreviewPath = absolutePath( wfCatalog:ozCatalogId(), copy_previewPath )
			local copy_previewData = KSData.memoryMappedContentsOfFile( absolutePreviewPath )
			rxTest:expectEqual( fileInfo.previewData, tostring( copy_previewData ) )
			copy_previewData:close()

			local absoluteThumbnailPath = absolutePath( wfCatalog:ozCatalogId(), copy_thumbnailPath )
			local copy_thumbnailData = KSData.memoryMappedContentsOfFile( absoluteThumbnailPath )
			rxTest:expectEqual( fileInfo.thumbnailData, tostring( copy_thumbnailData ) )
			copy_thumbnailData:close()

			local absoluteFullsizePath = absolutePath( wfCatalog:ozCatalogId(), copy_fullsizePath )
			local copy_fullsizeData = KSData.memoryMappedContentsOfFile( absoluteFullsizePath )
			rxTest:expectEqual( fileInfo.fullsizeData, tostring( copy_fullsizeData ) )
			copy_fullsizeData:close()

		end

		docValue._localOnly.files.preview.path = '<previewPath>'
		docValue._localOnly.files.thumbnail2x.path = '<thumbnailPath>'
		docValue._localOnly.files.fullsize.path = '<fullsizePath>'

		if copy_originalPath ~= fileInfo.originalFilePath then
			docValue._localOnly.files.master.path = '<newOriginalPath>'
		end

		if not fileInfo.xmpSidecarPath then
			KSTableUtils.setNested( docValue, nil, '_localOnly', 'files', 'xmp_develop' )
		end

	elseif WFPlatformInfo.isAndroid then

		for _, localProxyType in ipairs( localProxiesType ) do
			local proxyContentId = 'proxy' .. localProxyType .. 'ContentId'
			local proxyFileSize = 'proxy' .. localProxyType .. 'FileSize'
			local proxyFilePath = 'proxy' .. localProxyType .. 'FilePath'

			local copy_proxyRelativePath = WFCatAssetUtils.getProxyRelativePathFromDocValue( docValue, localProxyType )
			local copy_proxyPath = absolutePath( wfCatalog:ozCatalogId(), copy_proxyRelativePath )
			rxTest:logf( "copy_proxyPath %s", copy_proxyPath )

			if performFileCheck then
				rxTest:expectEqual( fileInfo[proxyFileSize], WFFileUtils.fileAttributes( copy_proxyPath ).fileSize )
			end

			if copy_proxyPath ~= fileInfo[proxyFilePath] then

				docValue._localOnly.files.proxies[ localProxyType ].relativePath = '<newProxyPath>'
				docValue._localOnly.files.proxies[ localProxyType ].contentId = fileInfo[proxyContentId]
				docValue._localOnly.files.proxies[ localProxyType ].fileSize = fileInfo[proxyFileSize]

			end

		end

		local copy_originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )

		rxTest:logf( "copy_originalPath %s", copy_originalPath )


		if performFileCheck then
			rxTest:expectEqual( fileInfo.originalFileSize, WFFileUtils.fileAttributes( copy_originalPath ).fileSize )
		end

		if copy_originalPath ~= fileInfo.originalFilePath then
			docValue._localOnly.originalFilePath = '<newOriginalPath>'
		end

		local copy_previewMd5 = KSTableUtils_derefOpt( docValue, '_localOnly', 'preview', 'md5' )
		local copy_thumbnailMd5 = KSTableUtils_derefOpt( docValue, '_localOnly', 'thumbnail2x', 'md5' )
		local copy_fullsizeMd5 = KSTableUtils_derefOpt( docValue, '_localOnly', 'fullsize', 'md5' )

		if performFileCheck then
			rxTest:expectEqual( fileInfo.previewMd5, copy_previewMd5 )
			rxTest:expectEqual( fileInfo.thumbMd5, copy_thumbnailMd5 )
			rxTest:expectEqual( fileInfo.fullsizeMd5, copy_fullsizeMd5 )
		end

		docValue._localOnly.preview.md5 = '<previewMd5>'
		docValue._localOnly.thumbnail2x.md5 = '<thumbnailMd5>'
		docValue._localOnly.fullsize.md5 = '<fullsizeMd5>'

		docValue._localOnly.preview.epoch = '<epoch>'
		docValue._localOnly.thumbnail2x.epoch = '<epoch>'
		docValue._localOnly.fullsize.epoch = '<epoch>'

		docValue._localOnly.preview.revId = '<revId>'
		docValue._localOnly.thumbnail2x.revId = '<revId>'
		docValue._localOnly.fullsize.revId = '<revId>'

		if not fileInfo.xmpSidecarPath then
			KSTableUtils.setNested( docValue, nil, '_localOnly', 'files', 'xmp_develop' )
		end

	end

	docValue.xmp = nil

	docValue.develop.device = nil
	docValue.develop.userUpdated = nil

	return docValue

end

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

local function getExpectedCopiedDocValue_video( fileInfo, sourceAssetId, copiedAssetId, copiedAssetRevId, copyId, maybeXmpCameraRawSha256, optOverrides )

	local localProxiesType = getLocalProxiesTypeFromOptOverrides( optOverrides )

	local docValue = {
		_changeCounter = '<changeCounter>',
		_id = copiedAssetId,
		_localId = '<localId>',
		_revId = copiedAssetRevId,
		copy = {
			copy_id = copyId,
		},
		develop = {
			processingModel = 'lightroom',
			croppedHeight = 1000,
			croppedWidth = 1000,
			userOrientation = 1,
		},
		importSource = {
			sha256 = fileInfo.originalSha256,
			fileSize = fileInfo.originalFileSize,
			fileName = "Image01.jpg",
			importedBy = '9cb9fb43d26c4691a9fd32e633434796',
			importTimestamp = '2023-12-20T17:19:16.448Z',
		},
		subtype = "video",
		type = "asset",
	}

	if WFPlatformInfo.isDesktop then

		docValue._localOnly = {
			files = {
				original = {
					master = {
						size = fileInfo.originalFileSize,
						relativePath = '<newOriginalPath>',
						volumeGuid = '<newOriginalGuid>',
					},

				},

				xmp_develop = {
					fileSize = fileInfo.xmpDevelopSettingsFileSize,
					path = fileInfo.xmpDevelopSettingsFilePath,
					sha256 = fileInfo.developSettingsSha256
				},
			},
			sourceAssetId = sourceAssetId,
			sourceOriginalLink = "/href/bogus/testing",
			sourceProxyLink = "/href/bogus/1080p",
			sourceXmpLink = "/href/bogus/xmp",
			xmpSidecarExists = true,
		}

		-- Handle special proxy in Windows
		if WFPlatformInfo.isWindows then
			docValue._localOnly.sourceProxyLink = "/href/bogus/1080p709"
		end

		local proxy = {}

		for _, localProxyType in ipairs( localProxiesType ) do

			local proxyFileSize = 'proxy' .. localProxyType .. 'FileSize'

			proxy[ localProxyType ] = {
							relativePath = '<newProxyPath>',
							volumeGuid = '<newProxyVolumeGuid>',
							size = fileInfo[proxyFileSize],
						}
		end

		docValue._localOnly.files.proxy = proxy

	elseif WFPlatformInfo.isiOS then

		docValue._localOnly = {
			files = {
				fullsize = {
					path = '<fullsizePath>',
					contentId = fileInfo.fullsizeContentId,
					fileSize = fileInfo.fullsizeFileSize,
				},
				master = {
					path = '<newOriginalPath>',
					contentId = fileInfo.originalContentId,
					fileSize = fileInfo.originalFileSize,
				},

				preview = {
					path = '<previewPath>',
					contentId = fileInfo.previewContentId,
					fileSize = fileInfo.previewFileSize,
				},
				thumbnail2x = {
					path = '<thumbnailPath>',
					contentId = fileInfo.thumbnailContentId,
					fileSize = fileInfo.thumbnailFileSize,
				},
			},
			sourceAssetId = sourceAssetId,
			sourceOriginalLink = "/href/bogus/testing",
			sourceProxyLink = "/href/bogus/1080p",
			sourceXmpLink = "/href/bogus/xmp",
			xmpSidecarExists = true,
		}

		local proxies = {}
		for _, localProxyType in ipairs( localProxiesType ) do

			local proxyFileSize = 'proxy' .. localProxyType .. 'FileSize'
			local proxyContentId = 'proxy' .. localProxyType .. 'ContentId'

			proxies[ localProxyType ] = {
				relativePath = '<newProxyPath>',
				contentId = fileInfo[proxyContentId],
				fileSize = fileInfo[proxyFileSize],
			}

		end

		docValue._localOnly.files.proxies = proxies

	elseif WFPlatformInfo.isAndroid then

		docValue._localOnly = {
			originalFilePath = '<newOriginalPath>',
			original = {
				size = fileInfo.originalFileSize,
			},

			preview = {
				revId = '<revId>',
				epoch = '<epoch>',
				md5 = '<previewMd5>',
				referenceHeight = 960,
				referenceWidth = 1280,
				size = fileInfo.previewFileSize,
			},
			fullsize = {
				revId = '<revId>',
				epoch = '<epoch>',
				md5 = '<fullsizeMd5>',
				referenceHeight = 960,
				referenceWidth = 1280,
				size = fileInfo.fullsizeFileSize,
			},
			thumbnail2x = {
				revId = '<revId>',
				epoch = '<epoch>',
				md5 = '<thumbnailMd5>',
				referenceHeight = 960,
				referenceWidth = 1280,
				size = fileInfo.thumbnailFileSize,
			},
			sourceAssetId = sourceAssetId,
			sourceOriginalLink = "/href/bogus/testing",
			sourceProxyLink = "/href/bogus/1080p",
			sourceXmpLink = "/href/bogus/xmp",
			xmpSidecarExists = true,
		}

		local proxies = {}
		for _, localProxyType in ipairs( localProxiesType ) do

			local proxyFileSize = 'proxy' .. localProxyType .. 'FileSize'
			local proxyContentId = 'proxy' .. localProxyType .. 'ContentId'

			proxies[ localProxyType ] = {

						relativePath = '<newProxyPath>',
							contentId = fileInfo[proxyContentId],
							fileSize = fileInfo[proxyFileSize],
						}
		end

		KSTableUtils.setNested( docValue, proxies, '_localOnly', 'files', 'proxies' )

	end

	return docValue

end

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

local function testCreateCopiesOfVideoAssetsOne( rxTest, optOverrides )

	rxTest:setTimeLimit( 50 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	local assetId = KSIdGenerator.uuidInLowercaseHex()

	-- assetInfo include all
	local assetInfo = createAsset( rxTest, fakeWFCatalog, docStore, assetId, optOverrides )

	-- Copy a single asset.
	local copiedAssetId
	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( fakeWFCatalog, { assetId } )
		:map( function( copiedAssetIds )
			   copiedAssetId = copiedAssetIds[ 1 ]
			   return copiedAssetIds and #copiedAssetIds == 1
		end ),
	true )

	-- get some info from the copied asset.

	local copiedAssetRevId, copyId

	rxTest:expectSingleValue( fakeWFCatalog:docStore():docValue( copiedAssetId )
		:map( function( docValue )
			copiedAssetRevId = docValue._revId
			copyId = docValue.copy.copy_id
			return true
		end ), true )

	-- Compare the new copied doc value (ignoring the _id and _revId values fished out above)
	-- This also verifies that there is no _serverProvidedMetadata in the copied asset.

	rxTest:expectSingleValue(
		fakeWFCatalog:docStore():docValue( copiedAssetId )
			:map( function( docValue )

				docValue._changeCounter = "<changeCounter>"
				docValue._localId = "<localId>"

				return videoMapAwayPaths( rxTest, fakeWFCatalog, docValue, assetInfo, nil, optOverrides )

			end ), getExpectedCopiedDocValue_video( assetInfo, assetId, copiedAssetId, copiedAssetRevId, copyId, nil, optOverrides ) )

	-- compare the original doc value to see that it has the copy_id set now as well

	rxTest:expectSingleValue(
		fakeWFCatalog:docStore():docValue( assetId )
			:map( function( docValue )
				return docValue.copy
			end ), { copy_id = copyId } )

end

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

function WFCatCopiesTest.rxTestCreateCopiesOfVideoAssetsOne_Local1080pProxy( rxTest )

	local optOverrides = {
		docSubtype = 'video',
		hasLocal1080p = 'true',
	   }

	testCreateCopiesOfVideoAssetsOne( rxTest, optOverrides )

end

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

function WFCatCopiesTest.rxTestCreateCopiesOfVideoAssetsOne_Local360pProxy( rxTest )

	local optOverrides = {
		docSubtype = 'video',
		hasLocal360p = 'true',
	   }

	testCreateCopiesOfVideoAssetsOne( rxTest, optOverrides )

end

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

function WFCatCopiesTest.rxTestCreateCopiesOfVideoAssetsOne_Local1080pAnd360pProxy( rxTest )

	local optOverrides = {
		docSubtype = 'video',
		hasLocal1080p = 'true',
		hasLocal360p = 'true',
	   }

	testCreateCopiesOfVideoAssetsOne( rxTest, optOverrides )

end

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

if WFPlatformInfo.isDesktop then
	-- 720p proxy is actually the rendition
	function WFCatCopiesTest.rxTestCreateCopiesOfVideoAssetsOne_Local720pProxy( rxTest )

		local optOverrides = {
			docSubtype = 'video',
			hasLocal720p = 'true',
		   }

		testCreateCopiesOfVideoAssetsOne( rxTest, optOverrides )

	end


	function WFCatCopiesTest.rxTestCreateCopiesOfVideoAssetsOne_Local1080pAnd720pProxy( rxTest )

		local optOverrides = {
			docSubtype = 'video',
			hasLocal1080p = 'true',
			hasLocal720p = 'true',
		   }

		testCreateCopiesOfVideoAssetsOne( rxTest, optOverrides )

	end


	function WFCatCopiesTest.rxTestCreateCopiesOfVideoAssetsOne_Local720pAnd360pProxy( rxTest )

		local optOverrides = {
			docSubtype = 'video',
			hasLocal720p = 'true',
			hasLocal360p = 'true',
		   }

		testCreateCopiesOfVideoAssetsOne( rxTest, optOverrides )

	end
	
	function WFCatCopiesTest.rxTestCreateCopiesOfVideoAssetsOne_Local1080p709Proxy( rxTest )

		local optOverrides = {
			docSubtype = 'video',
			hasLocal1080p709 = 'true',
		   }
		   
		testCreateCopiesOfVideoAssetsOne( rxTest, optOverrides )
		
	end
	
end

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

local function testCalculateCopyPathsAndUpdateDocValue_Video( rxTest, optOverrides )

	rxTest:setTimeLimit( 50 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	local assetId1 = '123e4f816aa149a7a3e8b148145d5135'
	local assetId2 = '234e4f816aa149a7a3e8b148145d5135'

	local localProxiesType = getLocalProxiesTypeFromOptOverrides( optOverrides )

	local assetInfo, srcDocValue = createAsset( rxTest, fakeWFCatalog, docStore, assetId1, optOverrides )

	local dstAssetInfo, dstDocValue = createAsset( rxTest, fakeWFCatalog, docStore, assetId2, optOverrides )

	if WFPlatformInfo.isDesktop then
		KSTableUtils.setNested( dstDocValue, nil, '_localOnly', 'files', 'original', 'master', 'sha256' )

		for _, localProxyType in ipairs( localProxiesType ) do
			KSTableUtils.setNested( dstDocValue, nil, '_localOnly', 'files', 'proxy', localProxyType, 'sha256' )
		end

	elseif WFPlatformInfo.isiOS then

		KSTableUtils.setNested( dstDocValue, nil, '_localOnly', 'files', 'latestMaster' )
		for _, localProxyType in ipairs( localProxiesType ) do
			KSTableUtils.setNested( dstDocValue, nil, '_localOnly', 'files', 'proxies', localProxyType, 'sha256' )
		end

	elseif WFPlatformInfo.isAndroid then

		KSTableUtils.setNested( dstDocValue, nil, '_localOnly', 'original', 'epoch' )

		for _, localProxyType in ipairs( localProxiesType ) do
			KSTableUtils.setNested( dstDocValue, nil, '_localOnly', 'files', 'proxies', localProxyType, 'sha256' )
		end

		KSTableUtils.setNested( dstDocValue, nil, '_localOnly', 'latestOriginal' )

	end

	local expectedDstDocValue = getExpectedCopiedDocValue_video( assetInfo, assetId1, assetId2, nil, nil, dstAssetInfo.developSettingsSha256, optOverrides )
	expectedDstDocValue.importSource = nil
	expectedDstDocValue._changeCounter = nil
	expectedDstDocValue._localId = nil
	expectedDstDocValue._serverProvidedMetadata = nil
	expectedDstDocValue._revId = nil
	expectedDstDocValue.copy = nil
	expectedDstDocValue._localOnly.sourceAssetId = nil
	expectedDstDocValue._localOnly.sourceXmpDevelopLink = nil
	expectedDstDocValue._localOnly.sourceXmpDevelopSha256 = nil
	expectedDstDocValue._localOnly.sourceOriginalLink = nil
	expectedDstDocValue._localOnly.sourceProxyLink = nil
	expectedDstDocValue._localOnly.sourceXmpLink = nil

	local expectedFilesToCopy = {}

	if WFPlatformInfo.isDesktop then

		expectedFilesToCopy = {
				{
					srcFilePath = assetInfo.thumbnailFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.previewFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.originalFilePath,
					dstFilePath = '<newPath>',
				},
			}

		for _, localProxyType in ipairs( localProxiesType ) do

			local proxyToCopy = {}
			local proxyFilePath = 'proxy' .. localProxyType .. 'FilePath'
			proxyToCopy = {
					srcFilePath = assetInfo[proxyFilePath],
					dstFilePath = '<newPath>',
			}

			expectedFilesToCopy [ #expectedFilesToCopy + 1 ] = proxyToCopy

		end

	elseif WFPlatformInfo.isiOS then
		expectedDstDocValue._localOnly.xmpSidecarExists = nil

		expectedFilesToCopy = {
				{
					srcFilePath = assetInfo.thumbnailFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.previewFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.fullsizeFilePath,
					dstFilePath = '<newPath>',
				},
				{
					srcFilePath = assetInfo.originalFilePath,
					dstFilePath = '<newPath>',
				},

			}

		for _, localProxyType in ipairs( localProxiesType ) do

			local proxyToCopy = {}
			local proxyFilePath = 'proxy' .. localProxyType .. 'FilePath'
			proxyToCopy = {
					srcFilePath = assetInfo[proxyFilePath],
					dstFilePath = '<newPath>',
			}

			expectedFilesToCopy [ #expectedFilesToCopy + 1 ] = proxyToCopy

		end

	elseif WFPlatformInfo.isAndroid then
		expectedDstDocValue._localOnly.xmpSidecarExists = nil

		expectedFilesToCopy = {
				{
					srcFilePath = assetInfo.originalFilePath,
					dstFilePath = '<newPath>',
				}
			}

		for _, localProxyType in ipairs( localProxiesType ) do

			local proxyToCopy = {}
			local proxyFilePath = 'proxy' .. localProxyType .. 'FilePath'
			proxyToCopy = {
					srcFilePath = assetInfo[proxyFilePath],
					dstFilePath = '<newPath>',
			}

			expectedFilesToCopy [ #expectedFilesToCopy + 1 ] = proxyToCopy

		end

	end

	rxTest:expectSingleValue( Observable.just(
			WFCatCopies.calculateCopyPathsAndUpdateDocValue( fakeWFCatalog, srcDocValue, dstDocValue ) )
				:map( function( newDstDocValue, filesToCopy )

					for _, fileToCopy in ipairs( filesToCopy ) do

						local srcFilePath = fileToCopy.srcFilePath
						local dstFilePath = fileToCopy.dstFilePath

						rxTest:expectNotEqual( srcFilePath, dstFilePath )

						fileToCopy.dstFilePath = '<newPath>'

					end

					newDstDocValue.importSource = nil
					newDstDocValue._serverProvidedMetadata = nil
					newDstDocValue._newRevId = nil
					
					return videoMapAwayPaths( rxTest, fakeWFCatalog, newDstDocValue, assetInfo, false, optOverrides ), filesToCopy

				end ),
			expectedDstDocValue,
			expectedFilesToCopy )

end

--------------------------------------------------------------------------------
function WFCatCopiesTest.rxTestCalculateCopyPathsAndUpdateDocValue_Video_Local1080pProxy( rxTest )

	local optOverrides = {
		docSubtype = 'video',
		hasLocal1080p = 'true',
	   }

	testCreateCopiesOfVideoAssetsOne( rxTest, optOverrides )

end

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

function WFCatCopiesTest.rxTestCalculateCopyPathsAndUpdateDocValue_Video_Local360pProxy( rxTest )

	local optOverrides = {
		docSubtype = 'video',
		hasLocal360p = 'true',
	   }

	testCalculateCopyPathsAndUpdateDocValue_Video( rxTest, optOverrides )

end

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

function WFCatCopiesTest.rxTestCalculateCopyPathsAndUpdateDocValue_Video_Local1080pAnd360pProxy( rxTest )

	local optOverrides = {
		docSubtype = 'video',
		hasLocal1080p = 'true',
		hasLocal360p = 'true',
	   }

	testCalculateCopyPathsAndUpdateDocValue_Video( rxTest, optOverrides )

end

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

if WFPlatformInfo.isDesktop then
	-- 720p proxy is actually the rendition
	function WFCatCopiesTest.rxTestCalculateCopyPathsAndUpdateDocValue_Video_Local720pProxy( rxTest )

		local optOverrides = {
			docSubtype = 'video',
			hasLocal720p = 'true',
		   }

		testCalculateCopyPathsAndUpdateDocValue_Video( rxTest, optOverrides )

	end


	function WFCatCopiesTest.rxTestCalculateCopyPathsAndUpdateDocValue_Video_Local1080pAnd720pProxy( rxTest )

		local optOverrides = {
			docSubtype = 'video',
			hasLocal1080p = 'true',
			hasLocal720p = 'true',
		   }

		testCalculateCopyPathsAndUpdateDocValue_Video( rxTest, optOverrides )

	end


	function WFCatCopiesTest.rxTestCalculateCopyPathsAndUpdateDocValue_Video_Local720pAnd360pProxy( rxTest )

		local optOverrides = {
			docSubtype = 'video',
			hasLocal720p = 'true',
			hasLocal360p = 'true',
		   }

		testCalculateCopyPathsAndUpdateDocValue_Video( rxTest, optOverrides )

	end

end

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

function WFCatCopiesTest.rxTestCalculateCopyPathsAndUpdateDocValue_Aux( rxTest )

	local optOverrides = {
		hasMasks = true,
	}

	rxTest:setTimeLimit( 50 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	local assetId1 = KSIdGenerator.uuidInLowercaseHex()
	local assetId2 = KSIdGenerator.uuidInLowercaseHex()

	local assetInfo, srcDocValue = createAsset( rxTest, fakeWFCatalog, docStore, assetId1, optOverrides )
	local dstAssetInfo, dstDocValue = createAsset( rxTest, fakeWFCatalog, docStore, assetId2, optOverrides )

	local newDocValue, filesToCopy = WFCatCopies.calculateCopyPathsAndUpdateDocValue( fakeWFCatalog, srcDocValue, dstDocValue )

	-- Local Only Aux

	local localOnlyAux = KSTableUtils_derefOpt( newDocValue, '_localOnly', 'files', 'aux' )
	
	local expectedLocalOnlyAux = {
		[ assetInfo.mask1Fingerprint ] = {
			relativePath = assetInfo.mask1RelativeFilePath
		},
		[ assetInfo.mask2Fingerprint ] = {
			relativePath = assetInfo.mask2RelativeFilePath
		},
		[ dstAssetInfo.mask1Fingerprint ] = {
			relativePath = dstAssetInfo.mask1RelativeFilePath
		},
		[ dstAssetInfo.mask2Fingerprint ] = {
			relativePath = dstAssetInfo.mask2RelativeFilePath
		}
	}

	rxTest:expectEqual( expectedLocalOnlyAux, localOnlyAux )

	-- Aux Section
	
	local auxSection = KSTableUtils_derefOpt( newDocValue, 'aux' )

	local expectedAuxSection = {
		[ assetInfo.mask1AuxId ] = {
			digest = assetInfo.mask1Fingerprint,
			fileSize = assetInfo.mask1FileSize,
			sha256 = assetInfo.mask1Sha256,
			type = 'mask'
		},
		[ assetInfo.mask2AuxId ] = {
			digest = assetInfo.mask2Fingerprint,
			fileSize = assetInfo.mask2FileSize,
			sha256 = assetInfo.mask2Sha256,
			type = 'mask'
		},
		[ dstAssetInfo.mask1AuxId ] = {
			digest = dstAssetInfo.mask1Fingerprint,
			fileSize = dstAssetInfo.mask1FileSize,
			sha256 = dstAssetInfo.mask1Sha256,
			type = 'mask'
		},
		[ dstAssetInfo.mask2AuxId ] = {
			digest = dstAssetInfo.mask2Fingerprint,
			fileSize = dstAssetInfo.mask2FileSize,
			sha256 = dstAssetInfo.mask2Sha256,
			type = 'mask'
		}
	}

	rxTest:expectEqual( expectedAuxSection, auxSection )
	
	-- Develop Masks Section
	
	local masksSection = KSTableUtils_derefOpt( newDocValue, 'develop', 'masks' )

	local expectedMasksSection = {
		[ assetInfo.mask1AuxId ] = {
			digest = assetInfo.mask1Fingerprint
		},
		[ assetInfo.mask2AuxId ] = {
			digest = assetInfo.mask2Fingerprint
		},
		[ dstAssetInfo.mask1AuxId ] = {
			digest = dstAssetInfo.mask1Fingerprint
		},
		[ dstAssetInfo.mask2AuxId ] = {
			digest = dstAssetInfo.mask2Fingerprint
		}
	}

	rxTest:expectEqual( expectedMasksSection, masksSection )
	
	-- Make sure that the filesToCopy includes no auxiliary files
	
	local auxFilePaths = {
		assetInfo.mask1FilePath,
		assetInfo.mask2FilePath
	}
	
	for _, entry in ipairs( filesToCopy ) do
	
		rxTest:expectEqual( nil, auxFilePaths[ entry.srcFilePath ] )
	
	end

end

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

function WFCatCopiesTest.rxTestCalculateCopyPathsAndUpdateDocValue_Aux_CopyToVersion( rxTest )

	local optOverrides = {
		hasMasks = true,
	}

	rxTest:setTimeLimit( 50 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	
	local versionId = KSIdGenerator.uuidInLowercaseHex()

	local assetInfo, srcDocValue = createAsset( rxTest, fakeWFCatalog, docStore, assetId, optOverrides )
	
	local _, dstDocValue = createVersion( rxTest, fakeWFCatalog, docStore, versionId )

	local newDocValue, filesToCopy = WFCatCopies.calculateCopyPathsAndUpdateDocValue( fakeWFCatalog, srcDocValue, dstDocValue )

	-- Local Only Aux

	local localOnlyAux = KSTableUtils_derefOpt( newDocValue, '_localOnly', 'files', 'aux' )
	
	local expectedLocalOnlyAux = {
		[ assetInfo.mask1Fingerprint ] = {
			relativePath = assetInfo.mask1RelativeFilePath
		},
		[ assetInfo.mask2Fingerprint ] = {
			relativePath = assetInfo.mask2RelativeFilePath
		}
	}

	rxTest:expectEqual( expectedLocalOnlyAux, localOnlyAux )

	-- Aux Section
	
	local auxSection = KSTableUtils_derefOpt( newDocValue, 'aux' ) or {}

	local expectedAuxSection = {}

	rxTest:expectEqual( expectedAuxSection, auxSection )
	
	-- Develop Masks Section
	
	local masksSection = KSTableUtils_derefOpt( newDocValue, 'develop', 'masks' )

	local expectedMasksSection = {
		[ assetInfo.mask1AuxId ] = {
			digest = assetInfo.mask1Fingerprint
		},
		[ assetInfo.mask2AuxId ] = {
			digest = assetInfo.mask2Fingerprint
		}
	}

	rxTest:expectEqual( expectedMasksSection, masksSection )
	
	-- Make sure that the filesToCopy includes no auxiliary files
	
	local auxFilePaths = {
		assetInfo.mask1FilePath,
		assetInfo.mask2FilePath
	}
	
	for _, entry in ipairs( filesToCopy ) do
	
		rxTest:expectEqual( nil, auxFilePaths[ entry.srcFilePath ] )
	
	end

end

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

local function testCopyAsContributor_Video( rxTest, optOverrides )

	rxTest:setTimeLimit( 50 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	-- Test 1: Original And Proxy on Oz.

	local assetId1 = KSIdGenerator.uuidInLowercaseHex()

	createAsset( rxTest, fakeWFCatalog, docStore, assetId1, optOverrides )

	local copiedAssetId

	local optArgs = {
		isContributorCopyingToSharedAlbum = true
			-- if isContributorCopyingToSharedAlbum is set to true, the original/proxy
			-- should only be copied if the source asset hasn't been uploaded or if
			-- there are no renditions. This is because contributors should not have
			-- access to proxies or originals, even for those images that they
			-- contributed.
	}

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets(
			fakeWFCatalog, { assetId1 }, nil, nil, nil, nil, nil, optArgs )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return copiedAssetIds and #copiedAssetIds == 1
		end ),
	true )

	-- Compare the source asset to the copied asset, verifying the binaries were not copied.

	rxTest:expectSingleValue(
		fakeWFCatalog:docStore():docValue( copiedAssetId )
			:map( function( docValue )

				local copy_originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
				local copy_proxyPath = WFCatAssetUtils.getFullProxyPathFromDocValue( docValue )

				return not not copy_originalPath, not not copy_proxyPath

			end ), false, false )

	-- Test 2: No Original or Proxy on Oz

	local assetId2 = KSIdGenerator.uuidInLowercaseHex()

	optOverrides[ 'noMasterLink' ] = true
	optOverrides[ 'noProxyLink' ] = true

	createAsset( rxTest, fakeWFCatalog, docStore, assetId2, optOverrides )

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets(
			fakeWFCatalog, { assetId2 }, nil, nil, nil, nil, nil, optArgs )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return copiedAssetIds and #copiedAssetIds == 1
		end ),
	true )

	-- Compare the source asset to the copied asset, verifying the binaries were copied.

	rxTest:expectSingleValue(
		fakeWFCatalog:docStore():docValue( copiedAssetId )
			:map( function( docValue )

				local copy_originalPath = WFCatAssetUtils.getFullOriginalPathFromDocValue( docValue )
				local copy_proxyPath = WFCatAssetUtils.getFullProxyPathFromDocValue( docValue )

				return not not copy_originalPath, not not copy_proxyPath

			end ), true, true )
end

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

function WFCatCopiesTest.rxTestCopyAsContributor_Video_Local1080pProxy( rxTest )

	local optOverrides = {
		docSubtype = 'video',
		hasLocal1080p = 'true',
	   }

	testCopyAsContributor_Video( rxTest, optOverrides )

end

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

function WFCatCopiesTest.rxTestCopyAsContributor_Video_Local360pProxy( rxTest )

	local optOverrides = {
		docSubtype = 'video',
		hasLocal360p = 'true',
	   }

	testCopyAsContributor_Video( rxTest, optOverrides )

end

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

function WFCatCopiesTest.rxTestCopyAsContributor_Video_Local1080pAnd360pProxy( rxTest )

	local optOverrides = {
		docSubtype = 'video',
		hasLocal1080p = 'true',
		hasLocal360p = 'true',
	   }

	testCopyAsContributor_Video( rxTest, optOverrides )

end

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

if WFPlatformInfo.isDesktop then
	-- 720p proxy is actually the rendition
	function WFCatCopiesTest.rxTestCopyAsContributor_Video_Local720pProxy( rxTest )

		local optOverrides = {
			docSubtype = 'video',
			hasLocal720p = 'true',
		   }

		testCopyAsContributor_Video( rxTest, optOverrides )

	end


	function WFCatCopiesTest.rxTestCopyAsContributor_Video_Local1080pAnd720pProxy( rxTest )

		local optOverrides = {
			docSubtype = 'video',
			hasLocal1080p = 'true',
			hasLocal720p = 'true',
		   }

		testCopyAsContributor_Video( rxTest, optOverrides )

	end


	function WFCatCopiesTest.rxTestCopyAsContributor_Video_Local720pAnd360pProxy( rxTest )

		local optOverrides = {
			docSubtype = 'video',
			hasLocal720p = 'true',
			hasLocal360p = 'true',
		   }

		testCopyAsContributor_Video( rxTest, optOverrides )

	end

end

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

-- @mark Image assets with aux references

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

function WFCatCopiesTest.rxTestCreateCopiesOfAssetsOne_WithAux( rxTest )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	local assetId1 = KSIdGenerator.uuidInLowercaseHex()

	local assetOptions = { hasMasks = true }
	local _, assetValue = createAsset( rxTest, fakeWFCatalog, docStore, assetId1, assetOptions )

	-- Copy a single asset.
	local copiedAssetId

	rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( fakeWFCatalog, { assetId1 } )
		:map( function( copiedAssetIds )
			copiedAssetId = copiedAssetIds[ 1 ]
			return copiedAssetIds and #copiedAssetIds == 1
		end ),
	true )

	-- get some info from the copied asset.

	local copiedAssetRevId, copyId

	rxTest:expectSingleValue( fakeWFCatalog:docStore():docValue( copiedAssetId )
		:map( function( docValue )
			copiedAssetRevId = docValue._revId
			copyId = docValue.copy.copy_id
			return true
		end ), true )

	-- Test that aux ids have changed
	rxTest:waitFor(fakeWFCatalog:docStore():docValue( copiedAssetId )
			:map( function( copyValue )

				local originalAux = KSTableUtils_derefOpt( assetValue, 'aux' )
				local copyAux = KSTableUtils_derefOpt( copyValue, 'aux' )

				rxTest:expectEqual( originalAux, copyAux )

				-- Check that every aux is present in the copy, and that the aux ids are equal

				for auxId, aux in pairs( originalAux ) do

					local copyAuxId, copyAuxDoc = KSTableUtils.findInTable( copyAux, function(_, copy) return aux.digest == copy.digest end )
					rxTest:expectEqual( true,  not not copyAuxDoc )
					rxTest:expectEqual( copyAuxId, auxId )

				end

				-- Double check both aux sections have the same number of documents
				rxTest:expectEqual(#originalAux, #copyAux)

				local originalMasks = KSTableUtils_derefOpt( assetValue, 'develop', 'masks' )
				local copyMasks = KSTableUtils_derefOpt( copyValue, 'develop', 'masks' )

				rxTest:expectEqual( originalMasks, copyMasks)

				-- Check that every aux is present in the copy
				for maskId, mask in pairs( originalMasks ) do

					local copyMaskId, copyMask = KSTableUtils.findInTable( copyMasks, function(_, copyMask) return mask.digest == copyMask.digest end )
					rxTest:expectEqual( true,  not not copyMask )
					rxTest:expectEqual( copyMaskId, maskId )

				end

				-- Double check both mask sections have the same number of documents
				rxTest:expectEqual( #originalMasks, #copyMasks )

				-- paths should be unchanged
				rxTest:expectEqual( KSTableUtils_derefOpt( assetValue, '_localOnly', 'files', 'aux' ) , KSTableUtils_derefOpt( copyValue, '_localOnly', 'files', 'aux' ) )

			end )
	)

end


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

function WFCatCopiesTest.rxTestCreateCopiesOfAssetsMultiple_withAux( rxTest )

	rxTest:setTimeLimit( 300 )

	local fakeWFCatalog = createFakeWFCatalog( rxTest )

	local docStore = fakeWFCatalog:docStore()

	local assetId1 = KSIdGenerator.uuidInLowercaseHex()
	local assetId2 = KSIdGenerator.uuidInLowercaseHex()

	local assetIdsToCopy = { assetId1, assetId2 }
	local copyId1
	local copyId2
	local numCopiesAsset1 = 0
	local numCopiesAsset2 = 0
	local numDocUpdates = 0
	local asset1UpdateCount = 0
	local asset2UpdateCount = 0

	local changedDocsObserver = rxTest:observe(
		fakeWFCatalog:docStore():scannedFilteredMappedChangesFeedSince( "WFCatCopiesTest" , "reportAllChanges", {}, 1 )
		:map( function( doc )

			numDocUpdates = numDocUpdates + 1

			if doc._id == assetId1 then
				asset1UpdateCount = asset1UpdateCount + 1
			elseif doc._id == assetId2 then
				asset2UpdateCount = asset2UpdateCount + 1
			else
				table.insert( assetIdsToCopy, doc._id )
			end

			if doc.copy then
				if copyId1 == nil then
					copyId1 = doc.copy.copy_id
				end

				if doc.copy.copy_id ~= copyId1 then
					if copyId2 == nil then
						copyId2 = doc.copy.copy_id
					end
				end

				if doc.copy.copy_id == copyId1 then
					numCopiesAsset1 = numCopiesAsset1 + 1
				end

				if doc.copy.copy_id == copyId2 then
					numCopiesAsset2 = numCopiesAsset2 + 1
				end

			end

			return numDocUpdates, numCopiesAsset1, numCopiesAsset2

		end )
	)

	local assetOptions = { hasMasks = true }
	createAsset( rxTest, fakeWFCatalog, docStore, assetId1, nil, assetOptions )

	createAsset( rxTest, fakeWFCatalog, docStore, assetId2, nil, assetOptions )


	-- Wait for the two assets to show up, there will be no copies.
	changedDocsObserver:expectDataWithinTime( 45, 2, 0, 0 )

	-- reset numDocUpdates to ignore the doc updates made to create the assets

	numDocUpdates = 0

	local numDocUpdatesExpected = 4

	while #assetIdsToCopy < 256 do

		rxTest:expectSingleValue( WFCatCopies.createCopiesOfAssets( fakeWFCatalog, assetIdsToCopy )
			:map( function( copiedAssetIds )
				return #copiedAssetIds
			end )
		, #assetIdsToCopy )

		changedDocsObserver:expectDataWithinTime( 45, numDocUpdatesExpected, numDocUpdatesExpected / 2, numDocUpdatesExpected / 2 )

		numDocUpdatesExpected = numDocUpdatesExpected * 2

	end

	rxTest:expectEqual( #assetIdsToCopy, 256 )

end

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

return WFRxTestCase.wrapSuite( 'WFCatCopiesTest', WFCatCopiesTest )
