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

WFCatSyncIntegrationTest.lua

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

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.

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

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

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

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

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

	-- WF.oz_client.managed_catalog
local WFCatOzTestSession = require 'WFCatOzTestSession'
local WFCatIdUtils = require 'WFCatIdUtils'

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

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

local derefOpt = KSTableUtils.derefOpt
local child = KSPathUtils.child
local parent = KSPathUtils.parent

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

local kSubtypeImage = "image"
local kSubtypeVideo = "video"

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

local albumIdColumnName
local albumAssetTableName

if WFPlatformInfo.isMobile() then
	albumIdColumnName = "docId"
	albumAssetTableName = "album_asset"
else
	albumIdColumnName = "albumId"
	albumAssetTableName = "album_asset_v2"
end

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

local WFCatSyncIntegrationTest = {}

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

local function addAssetDoc( rxTest, catalog, options )

	options = options or {}
	
	local subtype = options.subtype or kSubtypeImage
	local localOriginal = options.localOriginal or false
	local localOriginalLarge = options.localOriginalLarge or false
	local xmpDevelop = options.xmpDevelop or false
	
	local userId = catalog:ozUserId()
	local catalogId = catalog:ozCatalogId()
	
	local tempTestFolder = WFTestFileUtils.createTestFolder( rxTest )
	local fileName = ( subtype == kSubtypeImage ) and 'IMG_7763.JPG' or 'IMG_7772.mp4'
	
	if localOriginalLarge then
		fileName = ( subtype == kSubtypeImage ) and 'IMG_9876.CR2' or 'IMG_6025.MOV'
	end
	
	local originalFilePath = WFCatAssetTestUtils.getTestDataOriginalFilePath( tempTestFolder, fileName )
	local sha256 = WFCrypto.digestFile( originalFilePath, 'SHA256' )
	
	local assetId = KSIdGenerator.uuidInLowercaseHex()
	local revisionId = KSIdGenerator.uuidInLowercaseHex()
	
	local assetDocValue = WFCatAssetTestUtils.createBaseAssetDocument( catalogId, assetId, revisionId, sha256, originalFilePath, userId )
	
	if localOriginal or localOriginalLarge then
		assetDocValue = WFCatAssetTestUtils.addOriginalPathToDocument( assetDocValue, sha256, originalFilePath )
	end
	
	rxTest:waitFor( catalog:docStore():setDocValue( assetDocValue ) )
	
	if xmpDevelop then
		local xmpDevelopFilePath = child( parent( originalFilePath ), 'testXmpDevelop.xmp' )
		revisionId = WFCatAssetTestUtils.addXmpDevelopToDocument( rxTest, catalog, assetId, xmpDevelopFilePath )
	end
	
	return assetId, revisionId
	
end

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

local function addAlbumDoc( rxTest, catalog, optAlbumName )

	local albumId
	local albumName = optAlbumName or "foo"
	
	if WFPlatformInfo.isMobile() then
		
		local LrmCatAlbum = require 'LrmCatAlbum'
		
		rxTest:waitFor( LrmCatAlbum.createAlbum( catalog, albumName, 'root', false, false )
			:map( function( newAlbumId )
				albumId = newAlbumId
			end ) )

	else
	
		local WFCatAlbums = require 'WFCatAlbums'
		
		rxTest:waitFor( WFCatAlbums.createAlbum( catalog, albumName )
			:map( function( _, newAlbumId )
				albumId = newAlbumId
			end ) )
	end
	
	return albumId

end

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

local function addAlbumAssetDoc( rxTest, catalog, albumId, assetId )

	local albumAssetId

	if WFPlatformInfo.isMobile() then

		local LrmAlbumAssetUtils = require 'LrmAlbumAssetUtils'
		
		rxTest:waitFor( LrmAlbumAssetUtils.addAssetsToOwnAlbum( catalog, albumId, { assetId } ) )
			
	else
	
		local WFCatAlbums = require 'WFCatAlbums'
		
		rxTest:expectOnlyCompleted( WFCatAlbums.addAssetsToAlbum( catalog, albumId, { assetId } ) )
	
	end

	local getAlbumAssetId = catalog:indexStore():query{
			sql = string.format( "SELECT docId FROM %s WHERE albumId = ? AND assetId = ?", albumAssetTableName ),
			singleColumnQuery = true,
			albumId, assetId
		}
		
	if WFPlatformInfo.isMobile() then
		
		getAlbumAssetId = getAlbumAssetId
			:map( function( row )
				if row and row[ 1 ] then
					albumAssetId = row[ 1 ]
				end
				
				return #row
			end )
		
	else
	
		getAlbumAssetId = getAlbumAssetId
			:flatMap( function( row )
				
				if row and row[ 1 ] then
					local albumAssetLocalId = row[ 1 ]
					
					return WFCatIdUtils.getOzAssetIdsForLocalIds( catalog, { albumAssetLocalId } )
						:map( function( docIds )
							albumAssetId = docIds and docIds[ 1 ]
							return #row
						end )
				end
				
				return Observable.just( #row )
				
			end )
	
	end
	
	rxTest:observe( getAlbumAssetId )
		:expectDataWithinTime( 10, 1 )
		:dispose()
		
	return albumAssetId
	
end 

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

local function checkDocOnOz( rxTest, catalog, docId, optWaitTime )

	local waitTime = optWaitTime or 40
	
	local observeDocOnOz = rxTest:observe( catalog:docStore():ongoingDocValue( docId )
			:map( function( docValue )
				local receivedUpdateFromOz = docValue._serverProvidedMetadata ~= nil
				return receivedUpdateFromOz
			end ) )
			
	observeDocOnOz:expectDataWithinTime( waitTime, true )
	observeDocOnOz:dispose()

end

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

local function getLatestOzRevisionId( rxTest, catalog, docId )

	local revisionId

	rxTest:expectSingleValue( catalog:docStore():docValue( docId )
		:map( function( docValue )
			local revisionIds = derefOpt( docValue, '_serverProvidedMetadata', 'revision_ids' )
			if revisionIds then
				revisionId = revisionIds[ #revisionIds ]
			end
			return type( revisionId )
		end ),
	'string' )

	return revisionId
end

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

local function uploadBinaries( rxTest, options, catalog, puller, uploader, assetId )

	local localOriginal = options.localOriginal or false
	local localOriginalLarge = options.localOriginalLarge or false
	local xmpDevelop = options.xmpDevelop or false
	
	puller:getUpdatesContinuously()
	uploader:startContinuousUploads()
	
	-- observe uploader count
	
	if xmpDevelop then
	
		rxTest:observe( WFCatAssetTestUtils.pollBinaryUploadCount( uploader, 'xmp_develop' ) )
		:expectDataWithinTime( 150, 1 )
		:dispose()
		
	end
	
	if localOriginal or localOriginalLarge then
	
		local binaryType = localOriginalLarge and 'master-chunked' or 'master'
		local waitTime = localOriginalLarge and 300 or 120
	
		rxTest:observe( WFCatAssetTestUtils.pollBinaryUploadCount( uploader, binaryType ) )
		:expectDataWithinTime( waitTime, 1 )
		:dispose()
			
	end
	
	-- Wait for the update to get applied to the assets docValue
	
	if localOriginal or localOriginalLarge then
		
		rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
			:pluck( '_serverProvidedMetadata', 'links', '/rels/master', 'href' )
			:map( type ))
		:expectDataWithinTime( 60, 'string' )
		:dispose()
	
	end
	
	if xmpDevelop then
	
		rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
			:pluck( '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'href' )
			:map( type ))
		:expectDataWithinTime( 60, 'string' )
		:dispose()
		
	end
	
	puller:stopContinuousUpdates()
	uploader:stopContinuousUploads()
	
end

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

local function applyEmbeddedDevelopSettingsAndSync( rxTest, catalog, pusher, puller, assetId, subtype )

	local embeddedDevelop
	
	if subtype == kSubtypeImage then
	
		embeddedDevelop =  [[
			<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='XMP toolkit 3.0-28, framework 1.6'>
			<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:iX='http://ns.adobe.com/iX/1.0/'>
			 <rdf:Description rdf:about=''
			  xmlns:crs='http://ns.adobe.com/camera-raw-settings/1.0/'
				crs:Version='11.1'
				crs:ProcessVersion='11.0'
				crs:Temperature='50000'
				crs:Tint='+8'
				crs:Saturation='0'
			 </rdf:Description>
			</rdf:RDF>
			</x:xmpmeta>
			]]
		
	end
	
	rxTest:waitFor( catalog:docStore():applyOverlay {
		_id = assetId,
		userUpdated = KSDate.timeInIso8601Format(),
		develop = {
			processingModel = "lightroom",
			xmpCameraRaw = embeddedDevelop
		}
	} )
	
	-- sync develop settings
	
	local latestOzRevisionId = getLatestOzRevisionId( rxTest, catalog, assetId )
	
	puller:getUpdatesContinuously()
	
	local sendNUpdates = rxTest:observe( pusher:_DEBUG_sendNUpdates( 1 ) )
	sendNUpdates:expectCompletedWithinTime( 30 )
	sendNUpdates:dispose()

	rxTest:observe( catalog:docStore():ongoingDocValue( assetId )
			:map( function( docValue )
				
				local newOzRevisionId
				local revisionIds = derefOpt( docValue, '_serverProvidedMetadata', 'revision_ids' )
				
				if revisionIds then
					newOzRevisionId = revisionIds[ #revisionIds ]
				end
				
				return newOzRevisionId ~= latestOzRevisionId
			end ) )
		:expectDataWithinTime( 60, true )
		:dispose()
		
	puller:stopContinuousUpdates()

end

--==============================================================================
-- @mark Tests asset sync
--==============================================================================

local function syncAssetTest( rxTest, options )

	rxTest:setTimeLimit( 600 )
	rxTest:setRetryLimit( 3, 30 )

	local subtype = assert( options.subtype )
	local uploadBinary = options.uploadBinary or false
	local embeddedDevelop = options.embeddedDevelop or false
	local xmpDevelop = options.xmpDevelop or false
	
	assert( not ( embeddedDevelop and xmpDevelop )  )

	-- setup oz test
	
	local testSession, catalog, _, pusher, puller, uploader, _ = WFCatOzTestSession.setupOzTest( rxTest )
	assert( testSession )

	-- add asset
	
	local assetId = addAssetDoc( rxTest, catalog, options )
	
	-- sync init metadata
	
	puller:getUpdatesContinuously()
	
	local sendNUpdates = rxTest:observe( pusher:_DEBUG_sendNUpdates( 1 ) )
	sendNUpdates:expectCompletedWithinTime( 30 )
	sendNUpdates:dispose()
	
	checkDocOnOz( rxTest, catalog, assetId )
	
	puller:stopContinuousUpdates()
	
	-- upload binaries
	
	if uploadBinary then
	
		uploadBinaries( rxTest, options, catalog, puller, uploader, assetId )
	
	end

	-- add embedded develop settings
	
	if embeddedDevelop then
	
		applyEmbeddedDevelopSettingsAndSync( rxTest, catalog, pusher, puller, assetId, subtype )
	
	end

end

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

function WFCatSyncIntegrationTest.rxTestSyncAsset_image_testNightly( rxTest )

	syncAssetTest( rxTest, { subtype = kSubtypeImage } )
	
end

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

function WFCatSyncIntegrationTest.rxTestSyncAsset_image_localOriginal_testNightly( rxTest )

	syncAssetTest( rxTest, { subtype = kSubtypeImage, uploadBinary = true, localOriginal = true } )
	
end


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

function WFCatSyncIntegrationTest.rxTestSyncAsset_image_localOriginalLarge_testNightly( rxTest )

	syncAssetTest( rxTest, { subtype = kSubtypeImage, uploadBinary = true, localOriginalLarge = true } )
	
end

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

function WFCatSyncIntegrationTest.rxTestSyncAsset_image_embeddedDevelop_testNightly( rxTest )

	syncAssetTest( rxTest, { 
		subtype = kSubtypeImage, 
		uploadBinary = true, 
		localOriginal = true,
		embeddedDevelop = true } )
	
end

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

function WFCatSyncIntegrationTest.rxTestSyncAsset_image_xmpDevelop_testNightly( rxTest )

	syncAssetTest( rxTest, { 
		subtype = kSubtypeImage, 
		uploadBinary = true, 
		localOriginal = true,
		xmpDevelop = true } )
	
end

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

function WFCatSyncIntegrationTest.rxTestSyncAsset_video_testNightly( rxTest )

	syncAssetTest( rxTest, { subtype = kSubtypeVideo } )
	
end

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

function WFCatSyncIntegrationTest.rxTestSyncAsset_video_localOriginal_testNightly( rxTest )

	syncAssetTest( rxTest, { subtype = kSubtypeVideo, uploadBinary = true, localOriginal = true } )
	
end

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

function WFCatSyncIntegrationTest.rxTestSyncAsset_video_localOriginalLarge_testNightly( rxTest )

	syncAssetTest( rxTest, { subtype = kSubtypeVideo, uploadBinary = true, localOriginalLarge = true } )
	
end

--==============================================================================
-- @mark Tests album, album_asset sync
--==============================================================================

function WFCatSyncIntegrationTest.rxTestSyncAlbumAsset_testNightly( rxTest )

	rxTest:setTimeLimit( 120 )
	
	-- setup oz test
	
	local testSession, catalog, catalogSpec, pusher, puller = WFCatOzTestSession.setupOzTest( rxTest )
	assert( testSession )
	
	-- add asset
	
	local assetId = addAssetDoc( rxTest, catalog )
	
	-- sync init metadata
	
	puller:getUpdatesContinuously()
	
	local sendNUpdates = rxTest:observe( pusher:_DEBUG_sendNUpdates( 1 ) )
	sendNUpdates:expectCompletedWithinTime( 30 )
	sendNUpdates:dispose()
	
	checkDocOnOz( rxTest, catalog, assetId )
	
	puller:stopContinuousUpdates()
	
	-- add album, sync though pusher and puller
	
	local albumId = addAlbumDoc( rxTest, catalog )
	
	rxTest:observe( catalog:indexStore():query {
			sql = string.format( "SELECT %s FROM albums", albumIdColumnName ),
			singleColumnQuery = true
		} )
		:expectDataWithinTime( 10, { albumId } )
		:dispose()
		
	puller:getUpdatesContinuously()
	
	sendNUpdates = rxTest:observe( pusher:_DEBUG_sendNUpdates( 1 ) )
	sendNUpdates:expectCompletedWithinTime( 30 )
	sendNUpdates:dispose()
	
	checkDocOnOz( rxTest, catalog, albumId )
	
	puller:stopContinuousUpdates()
	
	-- add album_asset, sync though pusher and puller
	
	local albumAssetId = addAlbumAssetDoc( rxTest, catalog, albumId, assetId )
	
	puller:getUpdatesContinuously()
	
	if WFPlatformInfo.isMobile() then
		-- update count is 2 because when asset is added to an album, album's userUpdated timestamp would change
		sendNUpdates = rxTest:observe( pusher:_DEBUG_sendNUpdates( 2 ) )
	else
		sendNUpdates = rxTest:observe( pusher:_DEBUG_sendNUpdates( 1 ) )
	end
	
	sendNUpdates:expectCompletedWithinTime( 30 )
	sendNUpdates:dispose()
	
	checkDocOnOz( rxTest, catalog, albumAssetId )
	
	puller:stopContinuousUpdates()
	
	-- add album from oz API, and sync down through puller
	
	local albumId2 = KSIdGenerator.uuidInLowercaseHex()
	
	local albumDocPutUrl = string.format( '%s/albums/%s',
										  catalogSpec:catalogHref(),
										  albumId2 )

	local albumDocContent = KSJsonEncoder.encodeOzUploadJson {
		subtype = 'collection',
		payload = {
			name = "bar",
			
		},
	}

	local albumDocPutArgs = {
		requestHeaders = {
			{ header = 'Content-Type', value = 'application/json' },
			{ header = 'Accept', value = 'application/json' },
		},
	}

	rxTest:expectOnlyCompleted(
		catalogSpec:put( albumDocPutUrl, albumDocContent, albumDocPutArgs )
			:filter( function() return false end ) )
	
	puller:getUpdatesContinuously()
	
	rxTest:observe( catalog:indexStore():query{
			sql = string.format( "SELECT COUNT(*) FROM albums WHERE %s = ?", albumIdColumnName ),
			singleColumnQuery = true,
			albumId2
		} )
		:expectDataWithinTime( 30, { 1 } )
	
	puller:stopContinuousUpdates()
	
	-- add album_asset from oz API, and sync down through puller
	
	local albumAssetId2 = KSIdGenerator.uuidInLowercaseHex()

	local albumAssetDocPutUrl = string.format( '%s/albums/%s/assets',
											  catalogSpec:catalogHref(),
											  albumId2 )

	local albumAssetDocContent = KSJsonEncoder.encodeOzUploadJson {
		resources = {
			{
				id = assetId,
				album_asset_id = albumAssetId2,
			},
		},
	}

	local albumAssetDocPutArgs = {
		requestHeaders = {
			{ header = 'Content-Type', value = 'application/json' },
			{ header = 'Accept', value = 'application/json' },
		},
	}

	rxTest:expectOnlyCompleted(
		catalogSpec:put( albumAssetDocPutUrl, albumAssetDocContent, albumAssetDocPutArgs )
			:filter( function() return false end ) )
	
	puller:getUpdatesContinuously()
	
	local observeAlbumAssetId2 = catalog:indexStore():query{
			sql = string.format( "SELECT docId FROM %s WHERE albumId = ? AND assetId = ?", albumAssetTableName ),
			singleColumnQuery = true,
			albumId2, assetId
		}
		:flatMap( function( row )
			if row and row[ 1 ] and WFPlatformInfo.isDesktop then
				return WFCatIdUtils.getOzAssetIdsForLocalIds( catalog, row )
			end
			
			return Observable.just( row )
		end )
	
	rxTest:observe( observeAlbumAssetId2 )
		:expectDataWithinTime( 30, { albumAssetId2 } )
	
	puller:stopContinuousUpdates()
	
end

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

return WFRxTestCase.wrapSuite( WFCatSyncIntegrationTest )