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

NmbArchival.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2024 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.

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

	-- Rx
local Rx = require "Rx"
local Observable = Rx.Observable
local Subject = Rx.Subject

local KSTableUtils = require "KSTableUtils"

local NmbAcr = require "NmbAcr"
local WFCatExport = NmbAcr.WFCatExport
local WFCrContext = NmbAcr.WFCrContext
local WFRxThrottledBatchedBulkDocValue = require "WFRxThrottledBatchedBulkDocValue"
local WFVideoFoundationContext = NmbAcr.WFVideoFoundationContext

local WFCatAlbums = require "WFCatAlbums"
local WFCatFileSpec = require "WFCatFileSpec"
local WFCatIndexUtils = require "WFCatIndexUtils"
local WFFileUtils = require "WFFileUtils"
local WFLogger = require 'WFLogger'

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

local logger = WFLogger( 'NmbArchival' )

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

local derefOpt = KSTableUtils.derefOpt
local kNumParallelOriginalDigest = 3

local kExportError = 'kExportError'
local kVerifyError_coultNotFindFile = 'kVerifyError_coultNotFindFile'
local kVerifyError_couldnotOpenFile = 'kVerifyError_couldnotOpenFile'
local kVerifyError_notEqual = 'kVerifyError_notEqual'

--------------------------------------------------------------------------------
-- Using this version instead of WFCatAlbums.getAlbumAssetIds since we want to
-- ignore the assets that have been deleted but were part of this album.
-- TBD: Check if the function in WFCatAlbums itself can be changed to ignore deleted assets.

local function getAlbumAssetIds( wfCatalog, localAlbumId )
	
	return wfCatalog:indexStore():queryOnce {
		sql = [[SELECT DISTINCT album_asset_v2.assetId, album_asset_v2.docId 
					FROM album_asset_v2 INNER JOIN assets USING( assetId )
					JOIN albums as a ON album_asset_v2.albumId == a.albumId
					WHERE a.docId = ? ]],
		localAlbumId,
		quiesceUntilUpToDate = true,
	}

	:map( function( rows )
		
		logger:debugf( 'getAlbumAssetIds for localId %s complete w/ %d rows', localAlbumId, #rows )
		
		local assetIds = {}
		
		for _, albumAsset in ipairs( rows ) do
			
			local assetId = albumAsset[ 1 ]
			local docId = albumAsset[ 2 ]
			
			assetIds[ assetId ] = docId

		end
		
		return assetIds
	
	end )
	
end

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

local function getAssetHierarchy( wfCatalog, items )

	local docStore = wfCatalog:docStore()
	
	local allAlbums
	
	local pendingSub = Subject()
	
	local pendingContainers = {}

	local hierarchy = {
		pendingItems = items,
	}

	return WFCatAlbums._basicAlbumsList( wfCatalog, false )
	
		:take( 1 )
		
		:map( function( _allAlbums )
			
			allAlbums = _allAlbums
			
			return hierarchy
			
		end )
		
		:merge( pendingSub )
		
		:flatMapLatest( function( currContainer )
		
			local pendingItemsObs
			
			if currContainer.pendingItems then
				
				pendingItemsObs = Observable.just( currContainer.pendingItems )
				currContainer.pendingItems = nil
			
			elseif currContainer.type == 'folder' then
			
				local childIds = {}
				
				for _, albumInfo in ipairs( allAlbums ) do
					if albumInfo.parentId == currContainer.id then
						childIds[ #childIds + 1 ] = albumInfo.id
					end
				end
				
				pendingItemsObs = Observable.just( childIds )
			
			elseif currContainer.type == 'album' then
			
				pendingItemsObs = getAlbumAssetIds( wfCatalog, currContainer.id )
					
					:map( KSTableUtils.extractKeys )
			
			elseif currContainer.type == 'smart' then
				
				assert( false, "We do not process smart albums" )
				
			end
			
			assert( pendingItemsObs, "unknown type in hierarchy" )
			
			return pendingItemsObs
			
				:flatMapLatest( function( pendingItems )
				
					return WFRxThrottledBatchedBulkDocValue( "NmbArchival_getAssetHierarchy", docStore, pendingItems, 1000, 100 )
					
				end )
			
				:toArray()
				
				:map( function( listOfDocValueList )
				
					for _, docValueList in ipairs( listOfDocValueList ) do
				
						for _, docValue in ipairs( docValueList ) do
						
							if docValue.type == 'asset' then
							
								local fileSize = derefOpt( docValue, 'importSource', 'fileSize' )
								
								if derefOpt( docValue, 'importSource', 'willNotUploadMaster' ) then
									fileSize = 0
								end
							
								currContainer[ #currContainer + 1 ] = {
									type = 'asset',
									id = docValue._localId,
									docId = docValue._id,
									name = derefOpt( docValue, 'importSource', 'fileName' ),
									fileSize = fileSize,
									captureDate = derefOpt( docValue, 'captureDate' ),
									subtype = derefOpt( docValue, 'subtype' ),
								}
								
							elseif docValue.type == 'album' and docValue.subtype ~= 'smart' then
							
								local pendingContainer = {
									type = docValue.subtype == 'collection' and 'album' or 'folder',
									id = docValue._localId,
									docId = docValue._id,
									name = derefOpt( docValue, 'name' ),
								}
								
								pendingContainers[ #pendingContainers + 1 ] = pendingContainer
								
								currContainer[ #currContainer + 1 ] = pendingContainer
							
							end
						
						end
					
					end
					
					-- We are done processing this album. Bump the queue.
					local pendingAlbum = table.remove( pendingContainers, 1 )
					
					if pendingAlbum then
						pendingSub:onNext( pendingAlbum )
					else
						pendingSub:onCompleted()
					end
				
			end	)	
			
		end )
		
		:takeLast( 1 )
		
		:map( function()
			return hierarchy
		end )

end

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

local function formVerifyReport( info, success, err )
	return {
		info = info,
		success = success,
		err = err
	}
end

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

local function verifyExport( wfCatalog, exportObservable )

	local docStore = wfCatalog:docStore()
	
	return exportObservable
		
		:map( function( exportedInfo )
		
			if not exportedInfo.extraInfo then
				return Observable.empty()
			end
			
			local assetId = derefOpt( exportedInfo, 'extraInfo', 'id' )
			local exportedPath = derefOpt( exportedInfo, 'extraInfo', 'data', 'path' )
			
			return docStore:docValue( assetId )
				:flatMapLatest( function( docValue )
					
					local fileName = derefOpt( docValue, 'importSource', 'fileName' ) 
				
					if fileName then
						KSTableUtils.setNested( exportedInfo, fileName, 'extraInfo', 'fileName' )
					end
					
					if derefOpt( docValue, 'importSource', 'willNotUploadMaster' ) then
						KSTableUtils.setNested( exportedInfo, true, 'extraInfo', 'willNotUploadMaster' )	 
					end
					
					if not derefOpt( exportedInfo, 'extraInfo', 'success' ) and derefOpt( exportedInfo, 'extraInfo', 'error' ) then
						return Observable.just( formVerifyReport( exportedInfo, false, kExportError ) )
					end				
				
					local originalFilePath = WFCatFileSpec.buildOriginalPathFromDocValue( docValue )
					
					if not originalFilePath or not exportedPath
						or WFFileUtils.exists( originalFilePath ) ~= 'file'
						or WFFileUtils.exists( exportedPath ) ~= 'file' then
						
						-- Cannot verify
						return Observable.just( formVerifyReport( exportedInfo, false, kVerifyError_coultNotFindFile ) )
						
					end
					
					local isVideo = WFCatIndexUtils.isVideoDocument( docValue )
					
					local success1, context1, success2, context2
					
					if isVideo then
						success1, context1 = pcall( WFVideoFoundationContext, originalFilePath )
						success2, context2 = pcall( WFVideoFoundationContext, exportedPath )
					else
						success1, context1 = pcall( WFCrContext, originalFilePath )
						success2, context2 = pcall( WFCrContext, exportedPath )
					end
					
					if not success1 or not success2 then
						return Observable.just( formVerifyReport( exportedInfo, false, kVerifyError_couldnotOpenFile ) )
					end
					
					return Observable.combineLatest(
						function( originalDigest1, originalDigest2 )
						
							if originalDigest1 ~= originalDigest2 then
								return formVerifyReport( exportedInfo, false, kVerifyError_notEqual )
							end
						
							return formVerifyReport( exportedInfo, true )
						end,
						context1:getOriginalDigest(),
						context2:getOriginalDigest() )
						
						:catch( function()
							return Observable.just( formVerifyReport( exportedInfo, false, kVerifyError_notEqual ) )
						end )
				end )
		
		end )
		
		:flatMerge( kNumParallelOriginalDigest )
	
end

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

local function archiveAssets( wfCatalog, pixelProviderProcessorStream, maybeSynchronizer, assetsList, archiveSettings, exportSettings )

	if #assetsList == 0 then
		return Observable.just( {} )
	end
	
	local requests = {}
	
	for i, item in ipairs( assetsList ) do
		
		local settings = KSTableUtils.deepCopy( exportSettings )
		settings.destinationFolder = item.destination
		
		requests[ i ] = {
			id = item.id,
			subtype = item.subtype,
			settings = settings,
			devSettingsOverride = item.devSettingsOverride,
		}
		
	end
	
	local exportTask = WFCatExport.exportAssets(
		wfCatalog,
		pixelProviderProcessorStream,
		maybeSynchronizer,
		requests )
		
	local exportObservable = exportTask:status()
		
		:filter( function( data )

			-- NMB-18044: Send info only when some assets are processed
			return data
					and data.processedCount
					and data.processedCount > 0

		end )
		
		:finally( function()
			exportTask:dispose()
		end )
		
	return verifyExport( wfCatalog, exportObservable )
	
end

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

return {
	getAssetHierarchy = getAssetHierarchy,
	archiveAssets = archiveAssets,
}
