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

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

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

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

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

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

	-- WF.core.reactive
local KSPromises = require 'KSPromises'

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

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

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

	-- WF.oz_client
local WFOzPurgeSettings = require 'WFOzPurgeSettings'

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

	-- Lrmobile_models
local LrmCatalogTestHelper = require 'LrmCatalogTestHelper'	
local LrmImport = require 'LrmImport'

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

local LrmImportTest = {}

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

local derefOpt = assert( KSTableUtils.derefOpt )

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

local catalog

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

function LrmImportTest.setUp()

	catalog = LrmCatalogTestHelper.makeCatalogService()
	
end

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

function LrmImportTest.tearDown()

	if catalog then
		
		catalog:close()
		catalog = nil
		
	end
	
end

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

local function makeImportDocValue( isVideo )

	local docValue 
	
	if isVideo then
	
		docValue = {
			subtype = 'video',
			captureDate = "2022-06-12T11:01:10.000-07:00",
			importSource = {
				originalHeight = 1440,
				importTimestamp = "2022-06-18T22:19:50Z",
				contentType = "video/mp4",
				sha256 = "df125122ffed78df4eaf137eae0cc185ddc133fa46d7e08161e3dabcec2d09ca",
				fileSize = 22410541,
				originalWidth = 1080,
				fileName = "20220612_120034_07.mp4",
			},
			video = {
				chromaSubsampling = "4:2:0",
				storageWidth = 1440,
				duration = {
					4989,
					1000,
				},
				displayAspectRatio = {
					4,
					3,
				},
				bitDepth = 8,
				audioChannel = "stereo",
				colorRange = "limited",
				frameRate = {
					90001,
					1000,
				},
				videoBitrate = 35868393,
				videoCodec = "AVC",
				audioSampleSize = 16,
				colorMatrix = "unknown",
				audioCodec = "AAC",
				transferFunction = "unknown",
				fieldOrder = "progressive",
				colorPrimaries = "bt601",
				audioBitrate = 64000,
				audioSampleRate = 44100,
				storageHeight = 1080,
				pixelAspectRatio = {
					1,
					1,
				},
			},
			develop = {
				croppedHeight = 1934,
				croppedWidth = 3212,
				processVersion = 1,
				version = 1,
			},
			xmp = {
				tiff = {
					Orientation = "left bottom",
					Make = "nikon",
				},
			},
			_localOnly = {
				localUrl = '1234',
			}
		
		}
		
	else
	
		docValue = {
			subtype = 'image',
			captureDate = '2012-01-02T20:54:00.485',
			importSource = {
				fileName = 'Image01.jpg',
				filePath = '/var/mobile/0001', -- different format?
				fileSize = 10000,
				sha256 = 'B5bb9d8014a0f9b1d61E21e796d78dccdf1352f23cD32812f4850b878ae4944c',
				contentType = 'image/jpeg',
				originalWidth = 1200,
				originalHeight = 1000,
			},
			location = {
				longitude = -121.891392,
				latitude = 37.335753,
				altitude = 26,
				direction = 290.00,
				reference = 'C',
			},
			develop = {
				croppedHeight = 150,
				croppedWidth = 150,
				xmpCameraRaw = '<xmp />',
				userOrientation = 2,
			},
			xmp = {
				dc = {
					rights = 'Copyright',
				},
				exif = {
					ExposureTime = { 100, 1 },
					FNumber = { 100, 1 },
					ApertureValue = { 100, 1 },
					FocalLength = { 100, 1 },
					ISOSpeedRatings = 100,
				},
				tiff = {
					Make = 'Make',
					Model = 'Model',
				},
				aux = {
					Lens = 'Lens',
					IsMergedHDR = true,
					IsMergedPanorama = true,
				}
			},
			_localOnly = {
				localUrl = '1234',
			}
		}
		
	end
	
	return docValue
	
end

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

local function testSingleImportWithRenditionsAndMaster( test, isVideo )

	local assetArray = {}
	
	local importDocValue = makeImportDocValue( isVideo )
	
	local assetId
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local thumbnail2xData = KSData.make( 'thumbnail2x' )
	local thumbnail2xMd5 = thumbnail2xData:digest( 'MD5' )
	
	local previewData = KSData.make( 'preview' )
	local previewMd5 = previewData:digest( 'MD5' )
	
	local masterData = KSData.make( 'master' )
	local masterDataMd5 = masterData:digest( 'MD5' )
	
	local importAsset = {
		renditions = {
			{
				renditionName = 'thumbnail2x',
				renditionData = thumbnail2xData
			},
			{
				renditionName = 'preview',
				renditionData = previewData,
			}
		},
		original = masterData,
		docValue = KSJsonEncoder.encodeOzUploadJson( importDocValue ),
		deviceId = deviceId,
	}
	
	assetArray[ #assetArray + 1 ] = importAsset
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	local userId
	
	WFAsyncTestCase( test, 10 )
	
		:waitFor( function()
		
			return catalog:ozUserIdP():mapDataP_new( function( uId )
			
				userId = uId
				
			end )
			
		end )
		
		:waitFor( function()
		
			return LrmImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					assetId = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
					
				end )
		end )
	
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local originalFilePath = docValue._localOnly.originalFilePath
					local original = docValue._localOnly.original
					
					local originalData = KSData.memoryMappedContentsOfFile( originalFilePath )
					local originalMd5FromDisk = originalData:digest( 'MD5' )
					
					originalData:close()
					
					local deviceTable
					
					if not isVideo then
						deviceTable = { device = docValue.develop.device }
					end 

					return {
						importSource = {
							importedOnDevice = docValue.importSource.importedOnDevice,
							importedBy = docValue.importSource.importedBy,
						},
						original = {
							existsOnDisk = WFFileUtils.exists( originalFilePath ) and true or false,
							md5 = originalMd5FromDisk,
							epoch = original.epoch,
						},
						develop = {
							deviceTable
						},
					}
					
				end )
				
		end )
		
		:continueWhenValueMatchesFunction( function()
			local deviceTable
			local deviceNameWithId = string.format( '%s {%s}', WFDeviceInfo.deviceName(), deviceId )
			if not isVideo then
				deviceTable = { device = deviceNameWithId }
			end 

			return 'data', {
				importSource = {
					importedOnDevice = deviceNameWithId,
					importedBy = userId,
				},
				original = {
					existsOnDisk = true,
					md5 = masterDataMd5,
					epoch = 1,
				},
				develop = {
					deviceTable
				},
			}
			
		end )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local md5 = KSTableUtils.derefOpt( docValue, '_localOnly', 'thumbnail2x', 'md5' )
					
					return catalog:getDataCacheP():mapDataP_new( function( dataCache )
					
						return dataCache:getP( md5 )
							:mapDataP_new( function( data )
							
								local md5 = data:digest( 'MD5' )
								
								return md5
								
							end )
							
					end )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', thumbnail2xMd5 )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local md5 = KSTableUtils.derefOpt( docValue, '_localOnly', 'preview', 'md5' )
					
					return catalog:getDataCacheP():mapDataP_new( function( dataCache )
					
						return dataCache:getP( md5 )
							:mapDataP_new( function( data )
							
								local md5 = data:digest( 'MD5' )
								
								return md5
								
							end )
							
					end )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', previewMd5 )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local localAssetId = derefOpt( docValue, 'importSource', 'localAssetId' )
					local deviceId = derefOpt( docValue, 'importSource', 'uniqueDeviceId' )
					
					return { id = localAssetId, deviceId = deviceId }
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', { 
			id = '1234', 
			deviceId = WFDeviceInfo.uniqueDeviceId()
		} )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local originalFilePath = docValue._localOnly.originalFilePath
					pcall( WFFileUtils.delete, originalFilePath )
					
					local copyDocValue = KSTableUtils.deepCopy( docValue )
					
					return LrmCatalogTestHelper.equalToSourceTable( copyDocValue, importDocValue )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', importDocValue )
		
end

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

function LrmImportTest.testSingleImportWithRenditionsAndMaster_imageAsset( test, isVideo )

	testSingleImportWithRenditionsAndMaster( test, false )
	
end

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

function LrmImportTest.testSingleImportWithRenditionsAndMaster_videoAsset( test, isVideo )

	testSingleImportWithRenditionsAndMaster( test, true )
	
end

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

local function testSingleImportWithRenditions( test, isVideo )

	local assetArray = {}
	
	local importDocValue = makeImportDocValue( isVideo )
	
	local assetId
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local thumbnail2xData = KSData.make( 'thumbnail2x' )
	local thumbnail2xMd5 = thumbnail2xData:digest( 'MD5' )
	
	local previewData = KSData.make( 'preview' )
	local previewMd5 = previewData:digest( 'MD5' )
	
	local importAsset = {
		renditions = {
			{
				renditionName = 'thumbnail2x',
				renditionData = thumbnail2xData
			},
			{
				renditionName = 'preview',
				renditionData = previewData,
			}
		},
		docValue = KSJsonEncoder.encodeOzUploadJson( importDocValue ),
		deviceId = deviceId,
	}
	
	assetArray[ #assetArray + 1 ] = importAsset
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	local userId
	
	WFAsyncTestCase( test, 10 )
	
		:waitFor( function()
		
			return catalog:ozUserIdP():mapDataP_new( function( uId )
			
				userId = uId
				
			end )
			
		end )
		
		:waitFor( function()
		
			return LrmImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					assetId = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
					
				end )
		end )
	
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
			
			
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
					local deviceTable
					
					if not isVideo then
						deviceTable = { device = docValue.develop.device }
					end 
					
					return {
						importSource = {
							importedOnDevice = docValue.importSource.importedOnDevice,
							importedBy = docValue.importSource.importedBy,
						},
						develop = {
							deviceTable
						},
					}
					
				end )
				
		end )
		
		:continueWhenValueMatchesFunction( function()
			local deviceTable
			local deviceNameWithId = string.format( '%s {%s}', WFDeviceInfo.deviceName(), deviceId )
			if not isVideo then
				deviceTable = { device = deviceNameWithId }
			end
			
			return 'data', {
				importSource = {
					importedOnDevice = deviceNameWithId,
					importedBy = userId,
				},
				develop = {
					deviceTable
				},
			}
			
		end )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local md5 = KSTableUtils.derefOpt( docValue, '_localOnly', 'thumbnail2x', 'md5' )
					
					return catalog:getDataCacheP():mapDataP_new( function( dataCache )
					
						return dataCache:getP( md5 )
							:mapDataP_new( function( data )
							
								local md5 = data:digest( 'MD5' )
								
								return md5
								
							end )
							
					end )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', thumbnail2xMd5 )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local md5 = KSTableUtils.derefOpt( docValue, '_localOnly', 'preview', 'md5' )
					
					return catalog:getDataCacheP():mapDataP_new( function( dataCache )
					
						return dataCache:getP( md5 )
							:mapDataP_new( function( data )
							
								local md5 = data:digest( 'MD5' )
								
								return md5
								
							end )
							
					end )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', previewMd5 )
		
end

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

function LrmImportTest.testSingleImportWithRenditions_imageAsset( test, isVideo )

	testSingleImportWithRenditions( test, false )
	
end

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

function LrmImportTest.testSingleImportWithRenditions_videoAsset( test, isVideo )

	testSingleImportWithRenditions( test, true )
	
end

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

local function testSingleImportWithProxyAndRenditions( test, isVideo )

	local assetArray = {}
	
	local importDocValue = makeImportDocValue()
	
	local assetId
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local thumbnail2xData = KSData.make( 'thumbnail2x' )
	local thumbnail2xMd5 = thumbnail2xData:digest( 'MD5' )
	
	local previewData = KSData.make( 'preview' )
	local previewMd5 = previewData:digest( 'MD5' )
	
	local proxy2560Data = KSData.make( 'proxy' )
	local proxy2560Md5 = proxy2560Data:digest( 'MD5' )
	
	local importAsset = {
		renditions = {
			{
				renditionName = 'thumbnail2x',
				renditionData = thumbnail2xData
			},
			{
				renditionName = 'preview',
				renditionData = previewData,
			}
		},
		proxy = proxy2560Data,
		docValue = KSJsonEncoder.encodeOzUploadJson( importDocValue ),
		deviceId = deviceId,
	}
	
	assetArray[ #assetArray + 1 ] = importAsset
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	local userId
	
	WFAsyncTestCase( test, 10 )
	
		:waitFor( function()
			
			return catalog:ozUserIdP():mapDataP_new( function( uId )
			
				userId = uId
				
			end )
			
		end )
		
		:waitFor( function()
		
			return LrmImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					assetId = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
					
				end )
		end )
	
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local proxy = docValue._localOnly.proxy
					
					local proxyData = KSData.memoryMappedContentsOfFile( proxy.filePath )
					local proxyMd5FromDisk = proxyData:digest( 'MD5' )
					
					proxyData:close()
					
					return {
						importSource = {
							importedOnDevice = docValue.importSource.importedOnDevice,
							importedBy = docValue.importSource.importedBy,
						},
						proxy = {
							existsOnDisk = WFFileUtils.exists( proxy.filePath ) and true or false,
							md5 = proxyMd5FromDisk,
							epoch = proxy.epoch,
						},
						develop = {
							device = docValue.develop.device
						},
					}
					
				end )
				
		end )
		
		:continueWhenValueMatchesFunction( function()

			local deviceNameWithId = string.format( '%s {%s}', WFDeviceInfo.deviceName(), deviceId )
			return 'data', {
				importSource = {
					importedOnDevice = deviceNameWithId,
					importedBy = userId,
				},
				proxy = {
					existsOnDisk = true,
					md5 = proxy2560Md5,
					epoch = 1,
				},
				develop = {
					device = deviceNameWithId
				},
			}
			
		end )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local md5 = KSTableUtils.derefOpt( docValue, '_localOnly', 'thumbnail2x', 'md5' )
					
					return catalog:getDataCacheP():mapDataP_new( function( dataCache )
					
						return dataCache:getP( md5 )
							:mapDataP_new( function( data )
							
								local md5 = data:digest( 'MD5' )
								
								return md5
								
							end )
							
					end )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', thumbnail2xMd5 )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local md5 = KSTableUtils.derefOpt( docValue, '_localOnly', 'preview', 'md5' )
					
					return catalog:getDataCacheP():mapDataP_new( function( dataCache )
					
						return dataCache:getP( md5 )
							:mapDataP_new( function( data )
							
								local md5 = data:digest( 'MD5' )
								
								return md5
								
							end )
							
					end )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', previewMd5 )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local localAssetId = derefOpt( docValue, 'importSource', 'localAssetId' )
					local deviceId = derefOpt( docValue, 'importSource', 'uniqueDeviceId' )
					
					return { id = localAssetId, deviceId = deviceId }
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', { 
			id = '1234', 
			deviceId = WFDeviceInfo.uniqueDeviceId()
		} )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					pcall( WFFileUtils.delete, docValue._localOnly.proxy.filePath )
					
					local copyDocValue = KSTableUtils.deepCopy( docValue )
					
					return LrmCatalogTestHelper.equalToSourceTable( copyDocValue, importDocValue )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', importDocValue )
		
end

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

function LrmImportTest.testSingleImportWithProxyAndRenditions_imageAsset( test, isVideo )

	testSingleImportWithProxyAndRenditions( test, false )
	
end

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

function LrmImportTest.testSingleImportWithProxyAndRenditions_videoAsset( test, isVideo )

	testSingleImportWithProxyAndRenditions( test, true )
	
end

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

local function duplicateFile( filePath )

	local ext = KSIdGenerator.uuidInLowercaseHex()
	
	local copiedPath = string.format( '%s_%s', filePath, ext )
	
	-- we create a copy of the source file as the upload code will remove it
	-- after completion
	
	copiedPath = WFFileUtils.copyToUniquelyNamedFile( filePath, copiedPath )
	
	return copiedPath
	
end

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

function LrmImportTest.testLinkedFiledImport( test )

	local assetArray = {}
	
	local importDocValue = makeImportDocValue()
	
	local assetId
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local thumbnail2xData = KSData.make( 'thumbnail2x' )
	
	local previewData = KSData.make( 'preview' )
	
	local testDataPath = KSPathUtils.getStandardFilePath( "testdata" )
	testDataPath = KSPathUtils.child( testDataPath, 'assets' )
	
	local proxyPath = duplicateFile( KSPathUtils.child( testDataPath, '1.5MB.jpg' ) )
	local originalPath = duplicateFile( KSPathUtils.child( testDataPath, '12MB.jpg' ) )
	
	local originalFD
	if WFPlatformInfo.isAndroid then
		originalFD = WFFileUtils.getReadOnlyFileDescriptor( originalPath )
	end
	
	local importAsset = {
		renditions = {
			{
				renditionName = 'thumbnail2x',
				renditionData = thumbnail2xData
			},
			{
				renditionName = 'preview',
				renditionData = previewData,
			}
		},
		proxy = proxyPath,
		original = originalPath,
		originalfd = originalFD,
		docValue = KSJsonEncoder.encodeOzUploadJson( importDocValue ),
		deviceId = deviceId,
	}
	
	assetArray[ #assetArray + 1 ] = importAsset
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	local userId
	
	WFAsyncTestCase( test, 10 )
	
		:waitFor( function()
		
			return catalog:ozUserIdP():mapDataP_new( function( uId )
			
				userId = uId
				
			end )
			
		end )
		
		:waitFor( function()
		
			return LrmImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					assetId = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
					
				end )
		end )
	
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local proxy = docValue._localOnly.proxy
					local originalFile = docValue._localOnly.originalFilePath
					local original = docValue._localOnly.original
					
					pcall( WFFileUtils.delete, proxyPath )
					pcall( WFFileUtils.delete, originalPath )
					
					return {
						importSource = {
							importedOnDevice = docValue.importSource.importedOnDevice,
							importedBy = docValue.importSource.importedBy,
						},
						proxy = {
							existsOnDisk = WFFileUtils.exists( proxy.filePath ) and true or false,
							epoch = proxy.epoch,
						},
						original = {
							existsOnDisk = WFFileUtils.exists( originalFile ) and true or false,
							epoch = original.epoch,
						},
						develop = {
							device = docValue.develop.device
						},
					}
					
				end )
				
		end )
		
		:continueWhenValueMatchesFunction( function()
			local deviceNameWithId = string.format( '%s {%s}', WFDeviceInfo.deviceName(), deviceId )
			return 'data', {
				importSource = {
					importedOnDevice = deviceNameWithId,
					importedBy = userId,
				},
				proxy = {
					existsOnDisk = true,
					epoch = 1,
				},
				original = {
					existsOnDisk = true,
					epoch = 1,
				},
				develop = {
					device = deviceNameWithId
				},
			}
			
		end )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					local proxy = docValue._localOnly.proxy
					
					pcall( WFFileUtils.delete, proxy.filePath )
					pcall( WFFileUtils.delete, docValue._localOnly.originalFilePath )
					
					local copyDocValue = KSTableUtils.deepCopy( docValue )
					
					return LrmCatalogTestHelper.equalToSourceTable( copyDocValue, importDocValue )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', importDocValue )
		
end

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

local function testBulkAssetImport( test, isVideo )

	local assetArray = {}
	
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local kMaxAssets = 10
	
	for i = 1, kMaxAssets do
	
		local thumbnail2xData = KSData.make( 'thumbnail2x' .. i )
		
		local previewData = KSData.make( 'preview' .. i )
		
		local importDocValue = makeImportDocValue()
		
		local sha256 = previewData:digest( 'SHA256' )
		
		importDocValue.captureDate = string.format( '2015-01-%02d', i )
		importDocValue.importSource.sha256 = sha256
		
		if i % 2 == 0 then
		
			importDocValue = KSJsonEncoder.encodeOzUploadJson( importDocValue )
			
		end
		
		local importAsset = {
			renditions = {
				{
					renditionName = 'thumbnail2x',
					renditionData = thumbnail2xData
				},
				{
					renditionName = 'preview',
					renditionData = previewData,
				}
			},
			docValue = importDocValue,
			deviceId = deviceId,
		}
		
		assetArray[ #assetArray + 1 ] = importAsset
	
	end
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	WFAsyncTestCase( test, 10 )
	
		:waitFor( function()
		
			return LrmImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					return type( assetIds ) == 'table' and #assetIds == kMaxAssets
					
				end )
		end )
	
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return catalog:indexStore():queryP( 'SELECT count( docId ) FROM coreInfo' )
				:mapDataP_new( function( rows )
				
					return rows[ 1 ][ 1 ]
					
				end )
			
		end )
		
		:continueWhenValueMatches( 'data', kMaxAssets )
		
end

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

function LrmImportTest.testBulkAssetImport_imageAsset( test, isVideo )

	testBulkAssetImport( test, false )
	
end

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

function LrmImportTest.testBulkAssetImport_videoAsset( test, isVideo )

	testBulkAssetImport( test, true )
	
end

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

function LrmImportTest.testFailingImport_invalidJson( test )

	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local docValue = {
		foo = 'bar',
	}
	
	local docValueAsString = KSJsonEncoder.encodeOzUploadJson( docValue )
	
	docValueAsString = docValueAsString:replace( ':', '=' )
	
	local importAsset = {
		docValue = docValueAsString,
		deviceId = deviceId,
	}
	
	local assetArray = { importAsset }
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	WFAsyncTestCase( test, 10 )
	
		:waitFor( function()
		
			return LrmImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
			
		end )
		
		:continueWhenValueMatches( 'error', ':ignore:' )
	
end

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

local function testDerivedFromNonExistingAsset( test, isVideo )

	local assetArray = {}
	
	local importDocValue = makeImportDocValue( isVideo )
	
	local originCatalogId = KSIdGenerator.uuidInLowercaseHex()
	local originAssetId = KSIdGenerator.uuidInLowercaseHex()
	local originRevisionId = KSIdGenerator.uuidInLowercaseHex()
	
	local assetId
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local thumbnail2xData = KSData.make( 'thumbnail2x' )
	local previewData = KSData.make( 'preview' )
	local originalData = KSData.make( 'original' )
	
	local derivedDocValue = {
		captureDate = '2015-11-09',
		importSource = {
			sha256 = originalData:digest( 'SHA256' )
		},
	}
	
	local originInformation = {
		catalogId = originCatalogId,
		assetId = originAssetId,
		revisionId = originRevisionId,
	}
	
	local importAsset = {
		renditions = {
			{
				renditionName = 'thumbnail2x',
				renditionData = thumbnail2xData
			},
			{
				renditionName = 'preview',
				renditionData = previewData,
			}
		},
		docValue = derivedDocValue,
		deviceId = deviceId,
		derivedFrom = originInformation
	}
	
	assetArray[ #assetArray + 1 ] = importAsset
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	WFAsyncTestCase( test, 10 )
	
		:waitFor( function()
		
			return catalog:docStore():applyOverlayToDocumentP {
				docType = 'asset',
				docId = originAssetId,
				overlay = importDocValue,
			}
			
		end )
		
		:waitFor( function()
		
			return LrmImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					assetId = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
					
				end )
				
		end )
	
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					return derefOpt( docValue, 'xmp', 'xmpMM', 'DerivedFromDocument' )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', nil )
		
		:waitFor( function()
		
			return catalog:getCatalogIdP():mapDataP_new( function( catalogId )
			
				WFTableMerge.applyOverlay( originInformation, {
					catalogId = catalogId,
					assetId = { _delete = true },
				} )
				
				return LrmImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
					:mapDataP_new( function( assetIds )
					
						assetId = assetIds[ 1 ].assetId
						
						return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
						
					end )
					
			end )
				
		end )
	
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					return derefOpt( docValue, 'xmp', 'xmpMM', 'DerivedFromDocument' )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', nil )
		
		:waitFor( function()
		
			WFTableMerge.applyOverlay( originInformation, {
				assetId = KSIdGenerator.uuidInLowercaseHex(),
			} )
			
			return LrmImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					assetId = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table'
					
				end )
				
		end )
	
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					return derefOpt( docValue, 'xmp', 'xmpMM', 'DerivedFromDocument' )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', nil )
		
end

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

function LrmImportTest.testDerivedFromNonExistingAsset_imageAsset( test, isVideo )

	testDerivedFromNonExistingAsset( test, false )
	
end

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

function LrmImportTest.testDerivedFromNonExistingAsset_videoAsset( test, isVideo )

	testDerivedFromNonExistingAsset( test, true )
	
end

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

local function testDerivedFromValidAsset( test, isVideo )

	local assetArray = {}
	
	local importDocValue = makeImportDocValue( isVideo )
	
	WFTableMerge.applyOverlay( importDocValue, {
		_serverProvidedMetadata = {
			links = {
				master = '/v1.0/foo',
			},
		},
	} )
	
	local originAssetId = KSIdGenerator.uuidInLowercaseHex()
	local originRevisionId = KSIdGenerator.uuidInLowercaseHex()
	
	local assetId
	
	local thumbnail2xData = KSData.make( 'thumbnail2x' )
	local previewData = KSData.make( 'preview' )
	local originalData = KSData.make( 'original' )
	
	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	local catalogId
	local derivedDocValue
	
	WFAsyncTestCase( test, 10 )
	
		:waitFor( function()
		
			return catalog:getCatalogIdP():mapDataP_new( function( catId )
			
				catalogId = catId
				
			end )
			
		end )
		
		:waitFor( function()
		
			return catalog:ozUserIdP():mapDataP_new( function( userId )
			
				return catalog:getCatalogIdP():mapDataP_new( function( catalogId )
				
					derivedDocValue = {
						version = 1,
						captureDate = '2015-11-09',
						importSource = {
							sha256 = originalData:digest( 'SHA256' ),
							importedOnDevice = string.format( '%s {%s}', WFDeviceInfo.deviceName(), WFDeviceInfo.uniqueDeviceId() ),
							uniqueDeviceId = WFDeviceInfo.uniqueDeviceId(),
							importedBy = userId,
						},
						_localOnly = {
							localUrl = 'file://foo'
						}
					}
		
					local importAsset = {
						renditions = {
							{
								renditionName = 'thumbnail2x',
								renditionData = thumbnail2xData
							},
							{
								renditionName = 'preview',
								renditionData = previewData,
							}
						},
						docValue = derivedDocValue,
						derivedFrom = {
							catalogId = catalogId,
							assetId = originAssetId,
							revisionId = originRevisionId,
						},
					}
					
					assetArray[ #assetArray + 1 ] = importAsset
					
					return catalog:docStore():applyOverlayToDocumentP {
						docType = 'asset',
						docId = originAssetId,
						overlay = importDocValue,
					}
					
				end )
				
			end )
			
		end )
		
		:waitFor( function()
		
			return LrmImport.addAssetArrayToCatalogP( catalog, assetArray, importOptions )
				:mapDataP_new( function( assetIds )
				
					local importedAsset = assetIds[ 1 ]
					assetId = assetIds[ 1 ].assetId
					
					local localUrl = importedAsset.localUrl
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table',
						localUrl
					
				end )
		end )
	
		:continueWhenValueMatches( 'data', true, 'file://foo' )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					return derefOpt( docValue, '_localOnly', 'localUrl' )
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', nil )
		
		:waitFor( function()
		
			return catalog:docStore():docValueOnceP( 'asset', assetId )
				:mapDataP_new( function( docValue )
				
					return derefOpt( docValue, 'xmp' )
					
				end )
				
		end )
		
		:continueWhenValueMatchesFunction( function()
		
			local copiedDocValue = KSTableUtils.deepCopy( importDocValue )
			
			WFTableMerge.applyOverlay( copiedDocValue, derivedDocValue )
			
			WFTableMerge.applyOverlay( copiedDocValue, {
				xmp = {
					xmpMM = {
						DerivedFromDocument = string.format( '/v2/catalogs/%s/assets/%s', catalogId, originAssetId ),
						DerivedFromInstance = string.format( '/v2/catalogs/%s/assets/%s/revisions/%s', catalogId, originAssetId, originRevisionId )
					}
				}
			} )
			
			return 'data', derefOpt( copiedDocValue, 'xmp' )
		
		end )
		
end

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

function LrmImportTest.testDerivedFromValidAsset_imageAsset( test, isVideo )

	testDerivedFromValidAsset( test, false )
	
end

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

function LrmImportTest.testDerivedFromValidAsset_videoAsset( test, isVideo )

	testDerivedFromValidAsset( test, true )
	
end

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

local function testDuplicateSha256( test, isVideo )

	local assetId1, assetId2
	local deviceId = KSIdGenerator.uuidInLowercaseHex()
	
	local importDocValue = makeImportDocValue( isVideo )

	local importAsset = {
		docValue = importDocValue,
		deviceId = deviceId
	}

	local importOptions = {
		minimumFreeDiskSpace = WFOzPurgeSettings.minimumFreeSpace(),
	}
	
	WFAsyncTestCase( test, 30 )
	
		:waitFor( function()
		
			return LrmImport.addAssetArrayToCatalogP( catalog, { importAsset }, importOptions )
			
				:mapDataP_new( function( assetIds )
				
					assetId1 = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table' and
						assetIds[ 1 ].importStatus == 'new' and assetIds[ 1 ].localUrl == '1234'
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return LrmImport.addAssetArrayToCatalogP( catalog, { importAsset }, importOptions )
			
				:mapDataP_new( function( assetIds )
				
					assetId2 = assetIds[ 1 ].assetId
					
					return type( assetIds ) == 'table' and type( assetIds[ 1 ] ) == 'table' and
						assetIds[ 1 ].importStatus == 'duplicate'
					
				end )
				
		end )
		
		:continueWhenValueMatches( 'data', true )
		
		:waitFor( function()
		
			return KSPromises.resolvedPromiseP( 'data', assetId1 == assetId2 )
			
		end )
		
		:continueWhenValueMatches( 'data', true )
		
end

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

function LrmImportTest.testDuplicateSha256_imageAsset( test, isVideo )

	testDuplicateSha256( test, false )
	
end

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

function LrmImportTest.testDuplicateSha256_videoAsset( test, isVideo )

	testDuplicateSha256( test, true )
	
end

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

local function testHasAssetBySha256( test, isVideo )

	local assetId = KSIdGenerator.uuidInLowercaseHex()
	
	local sha256_1 = KSData.make( 'hash' ):digest( 'SHA256' )
	local sha256_2 = KSData.make( 'hash1' ):digest( 'SHA256' )
	
	local userId

	WFAsyncTestCase( test, 30 )
	
		:waitFor( function()

			return catalog:ozUserIdP():mapDataP_new( function( uId )
				userId = uId
			end )
			
		end )
		:waitFor( function()

			local status = pcall( LrmImport.hasAssetBySha256P, catalog, 'hash', nil, userId )
			
			return KSPromises.resolvedPromiseP( 'error', status )
			
		end )
		
		:continueWhenValueMatches( 'error', false )
		
		:waitFor( function()
		
			local status = pcall( LrmImport.hasAssetBySha256P, catalog, 42, nil, userId )
			
			return KSPromises.resolvedPromiseP( 'error', status )
			
		end )
		
		:continueWhenValueMatches( 'error', false )
		
		:waitFor( function()
			
			local subtypeStr 
			
			if isVideo then
				subtypeStr = 'image'
			else
				subtypeStr = 'video'
			end
			
			return catalog:docStore():applyOverlayToDocumentP {
				docId = assetId,
				docType = 'asset',
				overlay = {
					subtype = subtypeStr,
					importSource = {
						sha256 = sha256_1,
						importedBy = userId
					},
					_localOnly = {
						localUrl = '1234',
					}
				}
			}
			
		end )
		
		:waitFor( function()
		
			return LrmImport.hasAssetBySha256P( catalog, sha256_1, nil, userId )
			
		end )
		
		:continueWhenValueMatches( 'data', assetId )
		
		:waitFor( function()
		
			return LrmImport.hasAssetBySha256P( catalog, sha256_2, nil, userId )
			
		end )
		
		:continueWhenValueMatches( 'data', nil )
end

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


function LrmImportTest.testHasAssetBySha256_imageAsset( test, isVideo )

	testHasAssetBySha256( test, false )
	
end

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

function LrmImportTest.testHasAssetBySha256_videoAsset( test, isVideo )

	testHasAssetBySha256( test, true )
	
end

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

if WFPlatformInfo.isMobile() then
	return WFRxTestCase.wrapSuite( 'LrmImportTest', LrmImportTest )
else
	return {} 
		-- Desktop does not use LrmImport, and the LrmImport test cases
		-- cause Mac permissions issues on the build machines. 
end
