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

WFCatStacksTestFactory.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2021 Adobe
All Rights Reserved.

NOTICE: All information contained herein is, and remains
the property of Adobe and its suppliers, if any. The intellectual
and technical concepts contained herein are proprietary to Adobe
and its suppliers and are protected by all applicable intellectual
property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Adobe.

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

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

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

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

	-- WF.oz_client.managed_catalog
local WFCatAssetInfoListUtils = require 'WFCatAssetInfoListUtils'
local WFCatMockCatalog = require 'WFCatMockCatalog'
local WFCatSearch = require 'WFCatSearch'
local WFCatSearchAndSort = require 'WFCatSearchAndSort'
local WFCatIdUtils = require 'WFCatIdUtils'
local WFModelsUtils = require 'WFModelsUtils'
local WFCatStacks = require 'WFCatStacks'

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

return function( optSubmodelsByImplementation, maybeExistingTestClass )

	----------------------------------------------------------------------------
	
	local function callFunctionOrSubmodel( rxTest, wfCatalog, fnNamespace, fnName, ... )

		return WFModelsUtils.callFunctionOrSubmodel( rxTest, optSubmodelsByImplementation, wfCatalog, fnNamespace, fnName, ... )

	end
	
	----------------------------------------------------------------------------

	local createFakeWFCatalog = function( rxTest )

		WFCatIdUtils.debug_clearCaches()

		return WFCatMockCatalog.createFakeWFCatalog( rxTest )

	end

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

	local defaultSort = {
		handlerType = 'date',
		sortFieldInfo = {
			search_text = 'text',
			value = false,
		},
		sortOrder = 'ASC',
	}

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

	local function addTestAsset( rxTest, catalog, localId, maybeCaptureDate)

		rxTest:expectSingleValue(

			catalog:docStore():bulkSetDocValue {
				{
					_id = KSIdGenerator.uuidInLowercaseHex(),
					_localId = localId,
					type = "asset",
					subtype = "image",
					_localOnly = {
						files = {
							original = {
								master = {
									path = string.format( 'local path %s', localId ),
								}
							}
						}
					},
					captureDate = maybeCaptureDate or '2012-08-15T15:22:29',
					develop = {
						croppedHeight = 768,
						croppedWidth = 1024,
						xmpCameraRaw = '<xmp />',
						userOrientation = 1,
					},
					rating = 1,
					_serverProvidedMetadata = {

						links = {
							[ '/rels/rendition_type/thumbnail2x' ] = { href = '/href/bogus/testing' }
						}

					}
				},
			},

			':ignore:' )

	end

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

	local function addTestAlbum( rxTest, catalog, localId )
		
		local docId
		rxTest:expectOnlyCompleted(

				require 'WFCatAlbums'.createAlbum( catalog, 'WFCatStacks Test Album' )
					:tap( function( localId, globalId )
						docId = localId
					end )
					:takeLast( 0 )
			)
		
		return docId
	end

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

	local function addAssetsToAlbum( rxTest, catalog, localAlbumId, localAssetIds )
		
		rxTest:expectOnlyCompleted(
			
				require 'WFCatAlbums'.addAssetsToAlbum( catalog, localAlbumId, localAssetIds )
					:takeLast( 0 )	
			)

	end

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

	local function parseAndSortAssets( searchResult ) 

		local assets = WFCatAssetInfoListUtils.mergePackedAssetInfoTable( searchResult.assetInfos )

		local ids = {}
		for _, packedAssetInfo in ipairs( assets ) do
			local _, _, asset = WFCatAssetInfoListUtils.processPackedAssetInfo( packedAssetInfo )
			asset = WFCatAssetInfoListUtils.unpackAssetInfoDetailsToTable( asset )
			table.insert( ids, asset.id )
		end

		table.sort( ids )

		return ids

	end 

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

	local function simpleAssetSearchModel( rxTest, catalog )

		return rxTest:observe( WFCatSearchAndSort.makeModel( Observable.just( catalog ), { version = 1 }, nil, defaultSort ).value
			:map( parseAndSortAssets ) )

	end

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

	local function simpleAlbumAssetSearchModel( rxTest, catalog, albumId )

		return rxTest:observe( WFCatSearchAndSort.makeModel( Observable.just( catalog ), {
				predicates = {
					album = albumId,
					type = "album",
				},
				version = 1,
			}, nil, defaultSort ).value
			:map( parseAndSortAssets ) ) 

	end

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

	local WFCatStacksTest = maybeExistingTestClass or {}

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

	function WFCatStacksTest.rxTestCreateStack( rxTest )

		rxTest:setTimeLimit( 20 )

		local catalog = createFakeWFCatalog( rxTest )
		
		addTestAsset( rxTest, catalog, 1, '2012-08-15T15:22:29' )
		addTestAsset( rxTest, catalog, 2, '2012-08-15T15:22:29.123' )
		addTestAsset( rxTest, catalog, 3, '2012-08-15T15:22:29.456' )
		
		local assetSearch = simpleAssetSearchModel( rxTest, catalog )
		local assetCount = rxTest:observe( WFCatSearch.makeCountModel( Observable.just( catalog ), { version = 1 } ).value )
		local allStacks = rxTest:observe(
				callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatStacks','allStacks', catalog ) )
		
		assetSearch:expectDataWithinTime( 5,  { 1, 2, 3 } )
		assetCount:expectDataWithinTime( 5, 3 )
		allStacks:expectDataWithinTime( 5, {} )

		-- need to specify at least two images to create a valid stack
		rxTest:expectOnlyError( WFCatStacks.createStack( catalog, {} ), ":ignore:" )
		rxTest:expectOnlyError( WFCatStacks.createStack( catalog, { 1 } ), ":ignore:" )

		-- create a valid stack
		rxTest:expectSingleValue( WFCatStacks.createStack( catalog, { 2, 3 } ), ":ignore:", ":ignore:" )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3, 4 } )
		assetCount:expectDataWithinTime( 5, 3 ) -- stack shouldn't increase the asset count
		allStacks:expectDataWithinTime( 5, { { leader = 2, leaderSubtype = 'image', id = 4, contents = { 2, 3 }, 
												 subTypes = { [ '2' ] = 'image', [ '3' ] = 'image' } } } )
		
		-- create a second stack with some of the same images
		rxTest:expectSingleValue( WFCatStacks.createStack( catalog, { 1, 3 } ), ":ignore:", ":ignore:" )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3, 4, 7 } )
		assetCount:expectDataWithinTime( 5, 3 ) -- stack shouldn't increase the asset count
		allStacks:expectDataWithinTime( 5, {
			{ id = 4, leader = 2, leaderSubtype = 'image', contents = { 2, 3 }, subTypes = { [ '2' ] = 'image', [ '3' ] = 'image' } },
			{ id = 7, leader = 1, leaderSubtype = 'image', contents = { 1, 3 }, subTypes = { [ '1' ] = 'image', [ '3' ] = 'image' } }
		} )

	end

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

	function WFCatStacksTest.rxTestRemoveStack( rxTest )

		rxTest:setTimeLimit( 20 )
		
		local catalog = createFakeWFCatalog( rxTest )
		
		addTestAsset( rxTest, catalog, 1, '2012-08-15T15:22:29' )
		addTestAsset( rxTest, catalog, 2, '2012-08-15T15:22:29.123' )
		
		local assetSearch = simpleAssetSearchModel( rxTest, catalog )
		local assetCount = rxTest:observe( WFCatSearch.makeCountModel( Observable.just( catalog ), { version = 1 } ).value )
		local allStacks = rxTest:observe( callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatStacks','allStacks', catalog ) )
			
		assetSearch:expectDataWithinTime( 5, { 1, 2 } )
		assetCount:expectDataWithinTime( 5, 2 )
		allStacks:expectDataWithinTime( 5, {} )
		
		-- create a valid stack
		rxTest:expectSingleValue( WFCatStacks.createStack( catalog, { 1, 2 } ), ":ignore:", ":ignore:" )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3 } )
		assetCount:expectDataWithinTime( 5, 2 ) -- stack shouldn't increase the asset count
		allStacks:expectDataWithinTime( 5, { { leader = 1, leaderSubtype = 'image', id = 3, contents = { 1, 2 }, 
												subTypes = { [ '1' ] = 'image', [ '2' ] = 'image' } } } )
		
		-- delete the stack
		rxTest:expectOnlyCompleted(
				callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatStacks','deleteStack', catalog, 3 ) )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2 } )
		assetCount:expectDataWithinTime( 5, 2 ) -- stack shouldn't increase the asset count
		allStacks:expectDataWithinTime( 5, {} )
		
		-- make sure there are no stack_asset records either
		local stackAssetRowCount = rxTest:observe(  catalog:indexStore():query { 
				sql = [[SELECT * FROM stack_assets]],
				quieseUntilUpToDate = true,
			}
			:map( function( rows ) 
				return #rows
			end ) )
			
		stackAssetRowCount:expectDataWithinTime( 5, 0 )
		
	end

	----------------------------------------------------------------------------
	
	-- WF-1553 this pass-through model test testAddAssetsToStack will cause 
	-- NmbRootModelTest.lua/testCommentsFromIndexStore, 
	-- NmbRootModelTest.lua/testKeywordsDelete, 
	-- NmbRootModelTest.lua/testKeywordsSingleAsset to fail,
	-- For now, we just disable this unit test if it's running on pass-through model,
	-- need more investigation on what cause this issue.
	
	if not optSubmodelsByImplementation then
	
	function WFCatStacksTest.rxTestAddAssetsToStack( rxTest )

		rxTest:setTimeLimit( 20 )
		
		local catalog = createFakeWFCatalog( rxTest )
		
		addTestAsset( rxTest, catalog, 1, '2012-08-15T15:22:29' )
		addTestAsset( rxTest, catalog, 2, '2012-08-15T15:22:29.123' )
		addTestAsset( rxTest, catalog, 3, '2012-08-15T15:22:29.456' )
		
		
		local assetSearch = simpleAssetSearchModel( rxTest, catalog )
		local assetCount = rxTest:observe( WFCatSearch.makeCountModel( Observable.just( catalog ), { version = 1 } ).value )
		local allStacks = rxTest:observe( callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatStacks','allStacks', catalog ) )
		
		assetSearch:expectDataWithinTime( 5,  { 1, 2, 3 } )
		assetCount:expectDataWithinTime( 5, 3 )
		allStacks:expectDataWithinTime( 5, {} )
		
		-- create a valid stack
		rxTest:expectSingleValue( WFCatStacks.createStack( catalog, { 2, 3 } ), ":ignore:", ":ignore:" )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3, 4 } )
		assetCount:expectDataWithinTime( 5, 3 )
		allStacks:expectDataWithinTime( 5, { { leader = 2, leaderSubtype = 'image', id = 4, contents = { 2, 3 }, 
												subTypes = { [ '2' ] = 'image', [ '3' ] = 'image' } } } )
		
		-- add some assets to the stack
		rxTest:expectSingleValue( 
				callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatStacks','addAssetsToStack', catalog, 4, { 1, 3, 4 } ), ':ignore:' )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3, 4 } )
		
		assetCount:expectDataWithinTime( 5, 3 )
		allStacks:expectDataWithinTime( 5, { { leader = 2, leaderSubtype = 'image', id = 4, contents = { 1, 2, 3 },
												subTypes = { [ '1' ] = 'image', [ '2' ] = 'image', [ '3' ] = 'image' } } } )
		
		--create an album
		local albumId = addTestAlbum( rxTest, catalog )
		
		-- create a new asset
		local newAssetId = albumId + 1
		addTestAsset( rxTest, catalog, newAssetId, '2012-08-15T15:22:29.890' )
		
		-- add stack and existing assets to album
		addAssetsToAlbum( rxTest, catalog, albumId, { 1, 2, 3, 4 } )
		
		-- add it to the stack
		rxTest:expectSingleValue(
				callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatStacks','addAssetsToStack', catalog, 4, { newAssetId } ), ':ignore:' )
		
		--- check that the enw asset is part of the stack
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3, 4, newAssetId } )
		allStacks:expectDataWithinTime( 5, { { leader = 2, leaderSubtype = 'image', id = 4, contents = { 1, 2, 3, newAssetId }, 
												subTypes = { [ '1' ] = 'image', [ '2' ] = 'image', [ '3' ] = 'image', [ tostring( newAssetId ) ] = 'image' } } } )
		
		-- check that it's now also part of the album too
		local albumAssetSearch = simpleAlbumAssetSearchModel( rxTest, catalog, albumId )
		albumAssetSearch:expectDataWithinTime( 5, { 1, 2, 3, 4, newAssetId } )
		allStacks:expectDataWithinTime( 5, { { leader = 2, leaderSubtype = 'image', id = 4, contents = { 1, 2, 3, newAssetId },
												subTypes = { [ '1' ] = 'image', [ '2' ] = 'image', [ '3' ] = 'image', [ tostring( newAssetId ) ] = 'image' } } } )
		
	end

	end
	
	----------------------------------------------------------------------------

	function WFCatStacksTest.rxTestSetCover( rxTest )
		
		rxTest:setTimeLimit( 20 )
		
		local catalog = createFakeWFCatalog( rxTest )
		
		addTestAsset( rxTest, catalog, 1, '2012-08-15T15:22:29' )
		addTestAsset( rxTest, catalog, 2, '2012-08-15T15:22:29.123' )
		addTestAsset( rxTest, catalog, 3, '2012-08-15T15:22:29.456' )
		
		local assetSearch = simpleAssetSearchModel( rxTest, catalog )
		local assetCount = rxTest:observe( WFCatSearch.makeCountModel( Observable.just( catalog ), { version = 1 } ).value )
		local allStacks = rxTest:observe( callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatStacks','allStacks', catalog ) )

		assetSearch:expectDataWithinTime( 5, { 1, 2, 3 } )
		assetCount:expectDataWithinTime( 5, 3 )
		allStacks:expectDataWithinTime( 5, {} )
		
		-- create a valid stack
		rxTest:expectSingleValue( WFCatStacks.createStack( catalog, { 2, 3 } ), ":ignore:", ":ignore:" )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3, 4 } )
		assetCount:expectDataWithinTime( 5, 3 )
		allStacks:expectDataWithinTime( 5, { { leader = 2, leaderSubtype = 'image', id = 4, contents = { 2, 3 }, 
											subTypes = { [ '2' ] = 'image', [ '3' ] = 'image' } } } )
		
		-- set a cover (that is not in the current stack, it should add it)
		rxTest:expectOnlyCompleted(
				callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatStacks','setCover', catalog, 4, 1 ) )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3, 4 } )
		allStacks:expectDataWithinTime( 5, { { leader = 1, leaderSubtype = 'image', id = 4, contents = { 1, 2, 3 },
												subTypes = { [ '1' ] = 'image', [ '2' ] = 'image', [ '3' ] = 'image' } } } )
		
		-- set the cover back to something previously in the stack
		rxTest:expectOnlyCompleted( 
				callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatStacks','setCover', catalog, 4, 2 ) )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3, 4 } )
		assetCount:expectDataWithinTime( 5, 3 )
		allStacks:expectDataWithinTime( 5, { { leader = 2, leaderSubtype = 'image', id = 4, contents = { 1, 2, 3 },
												subTypes = { [ '1' ] = 'image', [ '2' ] = 'image', [ '3' ] = 'image' } } } )
		
	end

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

	function WFCatStacksTest.rxTestRemoveAssetsFromStack( rxTest )

		rxTest:setTimeLimit( 20 )
		
		local catalog = createFakeWFCatalog( rxTest )
		
		addTestAsset( rxTest, catalog, 1, '2012-08-15T15:22:29' )
		addTestAsset( rxTest, catalog, 2, '2012-08-15T15:22:29.123' )
		addTestAsset( rxTest, catalog, 3, '2012-08-15T15:22:29.456' )
		
		local assetSearch = simpleAssetSearchModel( rxTest, catalog )
		local assetCount = rxTest:observe( WFCatSearch.makeCountModel( Observable.just( catalog ), { version = 1 } ).value )
		local allStacks = rxTest:observe( callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatStacks','allStacks', catalog ) )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3 } )
		assetCount:expectDataWithinTime( 5, 3 )
		allStacks:expectDataWithinTime( 5, {} )
		
		-- create a valid stack
		rxTest:expectSingleValue( WFCatStacks.createStack( catalog, { 2, 1, 3 } ), ":ignore:", ":ignore:" )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3, 4 } )
		assetCount:expectDataWithinTime( 5, 3 )
		allStacks:expectDataWithinTime( 5, { { leader = 2, leaderSubtype = 'image', id = 4, contents = { 1, 2, 3 }, 
												subTypes = { [ '1' ] = 'image', [ '2' ] = 'image', [ '3' ] = 'image' } } } )
		
		-- Remove some assets from the stack. We're going to allow the stack count to go to 1 here because it allows
		-- users to shuffle assets fully in/out.
		rxTest:expectOnlyCompleted(
				callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatStacks','removeAssetsFromStack', catalog, 4, { 1, 3 } ) )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3, 4 } )
		assetCount:expectDataWithinTime( 5, 3 )
		allStacks:expectDataWithinTime( 5, { { leader = 2, leaderSubtype = 'image', id = 4, contents = { 2 }, 
													subTypes = { [ '2' ] = 'image' } } } )
		
		-- make sure there is only one stack_asset record left
		local stackAssetRowCount = rxTest:observe(  catalog:indexStore():query { 
				sql = [[SELECT * FROM stack_assets]],
				quieseUntilUpToDate = true,
			}
			:map( function( rows ) 
				return #rows
			end ) )
			
		stackAssetRowCount:expectDataWithinTime( 5, 1 )
		
		-- test removal of all assets (turning into stack deletion )
		local stackRowCount = rxTest:observe( catalog:indexStore():query {
				sql = [[SELECT * FROM stacks]],
				quiesceUntilUpToDate = true,
			}
			:map( function( rows )
				return #rows 
			end ) )
		
		rxTest:expectOnlyCompleted(
				callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatStacks','removeAssetsFromStack', catalog, 4, { 2 } ) )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3 } )
		allStacks:expectDataWithinTime( 5, {} )
		
		stackAssetRowCount:expectDataWithinTime( 5, 0 )
		stackRowCount:expectDataWithinTime( 5, 0 )
		
		-- test removal of cover and picking a new asset
		rxTest:expectSingleValue( WFCatStacks.createStack( catalog, { 2, 1, 3 } ), ":ignore:", ":ignore:" )
		
		assetSearch:expectDataWithinTime( 5, { 1, 2, 3, 8 } )
		assetCount:expectDataWithinTime( 5, 3 )
		allStacks:expectDataWithinTime( 5, { { leader = 2, leaderSubtype = 'image', id = 8, contents = { 1, 2, 3 }, 
													subTypes = { [ '1' ] = 'image', [ '2' ] = 'image', [ '3' ] = 'image' } } } )
		
		stackAssetRowCount:expectDataWithinTime( 5, 3 )
		stackRowCount:expectDataWithinTime( 5, 1 )
		
		
		rxTest:expectOnlyCompleted( 
				callFunctionOrSubmodel(
					rxTest, catalog,
					'WFCatStacks','removeAssetsFromStack', catalog, 8, { 2 } ) )

		assetSearch:expectDataWithinTime( 5, { 1, 2, 3, 8 } ) 
		assetCount:expectDataWithinTime( 5, 3 )
		allStacks:expectDataWithinTime( 5, { { leader = 1, leaderSubtype = 'image', id = 8, contents = { 1, 3 },
												subTypes = { [ '1' ] = 'image', [ '3' ] = 'image' } } }  )
		
		stackAssetRowCount:expectDataWithinTime( 5, 2 )
		stackRowCount:expectDataWithinTime( 5, 1 )
		
	end
	
	----------------------------------------------------------------------------

	return maybeExistingTestClass and WFCatStacksTest
			or WFRxTestCase.wrapSuite( WFCatStacksTest )
	
end