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

WFCatSpacePurgeUtils.lua

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

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

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

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

	-- WF.core.utils
local KSTableUtils = require 'KSTableUtils'
local WFArguments = require 'WFArguments'
local WFOzDocUtils = require 'WFOzDocUtils'
local WFPlatformInfo = require 'WFPlatformInfo'
local WFRxThrottledBatchedBulkApplyOverlay = require 'WFRxThrottledBatchedBulkApplyOverlay'
local WFRxThrottledBatchedBulkDocValue = require 'WFRxThrottledBatchedBulkDocValue'
local WFFileUtils = require 'WFFileUtils'

	-- WF.oz_client.managed_catalog
local WFCatDelete = require 'WFCatDelete'

	-- WF.lrmobile.modules.source
local LrmOzPurgeAgentUtils

if WFPlatformInfo.isMobile() then
	LrmOzPurgeAgentUtils = require 'LrmOzPurgeAgentUtils'
end

	-- WF.core.logger
local WFLogger = require 'WFLogger'

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

local logger = WFLogger( 'WFCatSpacePurgeUtils' )
--logger:enable( 'print' )

local check = assert( WFArguments.check )

local derefOpt = assert( KSTableUtils.derefOpt )

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

local WFCatSpacePurgeUtils = {}

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

 WFCatSpacePurgeUtils.DEBUG_optOutDeletionBacklog = false

local getFilePathsToDeleteAndOverlay
local deleteFilePaths

if WFPlatformInfo.isDesktop then

	getFilePathsToDeleteAndOverlay = function ( docValues, needToUpdateDocValue, keepRedactedBinaries )

		-- keepRedactedBinaries is not required for desktop. 
		-- Whether keepRedactedBinaries is true or false, desktop would always need to purge 'original', 'proxy', 'xmp' only

		local filePathsToDelete = {}
		local cacheEntriesToDelete = {}
		local overlays = {}

		local binaryTypesToPurge = { 'original', 'proxy', 'xmp' }
		
		for i, docValue in pairs( docValues ) do
		
			 local isVersion = docValue.type == 'version'
		
			local shouldRemoveFiles = false
		
			if isVersion then
		
				-- WF-1394: Prevent removing unsynced Version binary annotations. This allows us to resync these
				-- versions if the user is promoted to editor once again. PixelProviderFileCachePurgeWorker
				-- will also not purge the version binaries for this case.
				
				local isVersionOnOz = derefOpt( docValue, '_serverProvidedMetadata', 'links' ) ~= nil
			
				shouldRemoveFiles = isVersionOnOz and not WFOzDocUtils.doesDocumentNeedXmpDevelopSettingsUploaded( docValue )
			
			else
			
				local isValidOriginalAvailableOnOz = WFOzDocUtils.isValidOriginalAvailableOnOz( docValue )
				local isValidProxyAvailableOnOz = WFOzDocUtils.isValidProxyAvailableOnOz( docValue )
				
				shouldRemoveFiles = not keepRedactedBinaries or isValidOriginalAvailableOnOz or isValidProxyAvailableOnOz
			
			end
		
			if shouldRemoveFiles then
			
				local localFilePaths, assetOverlay = WFCatDelete.filePathsToDeleteAndOverlays( docValue, binaryTypesToPurge )
			
				-- On Desktop, externalized xmp develop settings can be shared by multiple documents; a asset
				-- document can reference the same exact settings file as a version document. Because of this,
				-- we do not explicitly remove the settings file from disk when a user is downgraded since
				-- it may be referenced by another document. The purging of these unreferenced settings
				-- files is handled by PixelProviderFileCachePurgeWorker. However, it's still important that
				-- we delete the path to the exteral file from the version document.
				
				if not assetOverlay then
				
					assetOverlay = {
						_id = docValue._id
					}
	
				end
				
				KSTableUtils.setNested( assetOverlay, { _delete = true }, '_localOnly', 'files', 'xmp_develop' )
			
				if next( localFilePaths ) and not isVersion then
					
					logger:infof( "Will purge files for document %s", docValue._id )
					
					-- WFCatDelete.deleteDocStoreFileArray needs another table layer to access filePathsToDelete correctly
					filePathsToDelete[ #filePathsToDelete + 1 ] = { localFilePaths }
				
				end
			
				if needToUpdateDocValue and assetOverlay then
					
					logger:infof( "Will update overlay for document %s", docValue._id )
				
					overlays[ #overlays + 1 ] = assetOverlay
					
				end
			
			end 
		
		end
		
		return filePathsToDelete, cacheEntriesToDelete, overlays

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

	deleteFilePaths = function( catalog, filePathsToDelete )
	
		local deletionBacklog
		
		if not WFCatSpacePurgeUtils.DEBUG_optOutDeletionBacklog then
			deletionBacklog = catalog:getDeletionBacklog()
		end
		
		for _, filePathsOfOneDoc in pairs( filePathsToDelete ) do
			WFCatDelete.deleteDocStoreFileArray( filePathsOfOneDoc, deletionBacklog )
		end
	
	end
	
else
	
	-- Mobile functions
	
	getFilePathsToDeleteAndOverlay = function ( docValues, needToUpdateDocValue, keepRedactedBinaries )
	
		local filePathsToDelete = {}
		local cacheEntriesToDelete = {}
		local overlays = {}
	
		local docType = derefOpt( docValues, 1, 'type' )
	
		local binaryTypesToPurge = { 'original', 'proxy', 'xmp_develop' }
		
		local isVersion = docType == 'version'
		
		if isVersion then
			binaryTypesToPurge = { 'xmp_develop', 'thumbnail2x' }
		elseif not keepRedactedBinaries then
			binaryTypesToPurge = { 'original', 'proxy', 'xmp_develop', 'thumbnail2x', 'preview' }
		end
		
		-- filePaths to delete
		
		for _, docValue in pairs( docValues ) do
		
			local shouldRemoveFiles = false
			
			if isVersion then
		
				-- WF-1394: Prevent removing unsynced Version binaries. This allows us to resync these
				-- versions if the user is promoted to editor once again.
				
				local isVersionOnOz = derefOpt( docValue, '_serverProvidedMetadata', 'links' ) ~= nil
			
				shouldRemoveFiles = isVersionOnOz and not WFOzDocUtils.doesDocumentNeedXmpDevelopSettingsUploaded( docValue )
			
			else
			
				local isValidOriginalAvailableOnOz = WFOzDocUtils.isValidOriginalAvailableOnOz( docValue )
				local isValidProxyAvailableOnOz = WFOzDocUtils.isValidProxyAvailableOnOz( docValue )
			
				shouldRemoveFiles = not keepRedactedBinaries or isValidOriginalAvailableOnOz or isValidProxyAvailableOnOz
			
			end
			
			if shouldRemoveFiles then
		
				local overlay = {}
			
				for _, binaryType in pairs( binaryTypesToPurge ) do
				
					local relativePath = LrmOzPurgeAgentUtils.renditionPath( docValue, binaryType )
					
					if relativePath and relativePath ~= '' then
					
						logger:infof( "Will purge files for document %s", docValue._id )
					
						if WFPlatformInfo.isAndroid 
							and ( binaryType == 'thumbnail2x' or binaryType == 'preview' ) then
							
							cacheEntriesToDelete[ #cacheEntriesToDelete + 1 ] = relativePath 
						
						else
						
							filePathsToDelete[ #filePathsToDelete + 1 ] = relativePath
						
						end
						
						if needToUpdateDocValue then
							
							logger:infof( "Will update overlay for document %s", docValue._id )
							
							overlay = LrmOzPurgeAgentUtils.updateOverlayWithRenditionName( overlay, binaryType )
						
						end
					end
				end
			
				if next( overlay ) then
					
					overlay._id = docValue._id
					
					overlays[ #overlays + 1 ] = overlay
			
				end
			end
		end
	
		return filePathsToDelete, cacheEntriesToDelete, overlays
	
	end
	
	--------------------------------------------------------------------------------

	deleteFilePaths = function( catalog, filePathsToDelete )
	
		for _, filePath in pairs( filePathsToDelete ) do
		
			local absolutePath = LrmOzPurgeAgentUtils.absolutePath( catalog:ozCatalogId(), filePath )
			WFFileUtils.deleteFile( absolutePath )
		
		end
	
	end

end

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

local function deleteFilesByDocIds( catalog, docIds, needToUpdateDocValue, keepRedactedBinaries, optDataCache )

	check( 'WFCatalog', 'table', '?boolean', '?boolean', '?KSDataCache' )

	local docStore = catalog:docStore()

	return WFRxThrottledBatchedBulkDocValue( "WFCatSpacePurgeUtils_deleteFilesByDocIds", docStore, docIds, 100, 10 )
			
		:map( function( assetDocValues )
		
			local filePathsToDelete, cacheEntriesToDelete, assetOverlays 
					= getFilePathsToDeleteAndOverlay( assetDocValues, needToUpdateDocValue, keepRedactedBinaries )
			
			return filePathsToDelete, cacheEntriesToDelete, assetOverlays
		
		end )
		
		:scan( function( filePathsToDelete, cacheEntriesToDelete, overlays, 
							batchOfFilePathsToDelete, batchOfCacheEntriesToDelete, batchOfOverlays )
		
			filePathsToDelete = KSTableUtils.appendArray( filePathsToDelete, batchOfFilePathsToDelete )
			cacheEntriesToDelete = KSTableUtils.appendArray( cacheEntriesToDelete, batchOfCacheEntriesToDelete )
			overlays = KSTableUtils.appendArray( overlays, batchOfOverlays )
			
			return filePathsToDelete, cacheEntriesToDelete, overlays
			
		end, {}, {}, {} )
		
		:takeLast( 1 ) 
		
		:flatMap( function( filePathsToDelete, cacheEntriesToDelete, overlays )
			
			-- delete filePaths
			
			deleteFilePaths( catalog, filePathsToDelete )
			
			-- delete cacheEntries
			
			local result = Observable.just( true )
			
			if optDataCache and #cacheEntriesToDelete > 0 then
			
				result = optDataCache:removeBulk( cacheEntriesToDelete )
				
			end 
			
			-- apply overlays
			
			if #overlays == 0 or not needToUpdateDocValue then
				return result
			end
			
			return result
				:flatMap( function()
					return WFRxThrottledBatchedBulkApplyOverlay( 'WFCatSpacePurgeUtils_deleteFilesByDocIds', docStore, overlays, 100, 10 )
				end )
				
		end )

end


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

-- For a given space, purge specified asset binary types

local function purgeAssetsForSpace( catalog, spaceId, needToUpdateDocValue, keepRedactedBinaries, optDataCache )

	check( 'WFCatalog', 'string', '?boolean', '?boolean', '?KSDataCache' )

	local albumAssetTable = "album_asset"
	local albumIdForAlbumTable = "docId"
	if WFPlatformInfo.isDesktop then
		albumAssetTable = "album_asset_v2"
		albumIdForAlbumTable = "albumId"
	end

	return catalog:indexStore():queryOnce{
			sql = string.format( [[SELECT assetId 
								FROM %s INNER JOIN albums ON %s.albumId == albums.%s
								WHERE albums.spaceId == ?]], albumAssetTable, albumAssetTable, albumIdForAlbumTable ),
			singleColumnQuery = true,
			spaceId
		}
		
		:flatMap( function( assetIds )
		
			if #assetIds == 0 then
				return Observable.just( true )
			end
			
			return deleteFilesByDocIds( catalog, assetIds, needToUpdateDocValue, keepRedactedBinaries, optDataCache )
			
		end ) 

end

WFCatSpacePurgeUtils.DEBUG_purgeAssetsForSpace = purgeAssetsForSpace

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

-- For a given space, purge specified version binary types

local function purgeVersionsForSpace( catalog, spaceId, needToUpdateDocValue )

	check( 'WFCatalog', 'string', '?boolean' )

	return catalog:indexStore():queryOnce{
			sql = [[SELECT docId FROM versions WHERE spaceId == ?]],
			singleColumnQuery = true,
			spaceId
		}
		
		:flatMap( function( versionIds )
			
			if #versionIds == 0 then
				return Observable.just( true )
			end
			
			return deleteFilesByDocIds( catalog, versionIds, needToUpdateDocValue )
		
		end )

end

WFCatSpacePurgeUtils.DEBUG_purgeVersionsForSpace = purgeVersionsForSpace

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

function WFCatSpacePurgeUtils.purgeAllForSpace( catalog, spaceId, optDataCache )

	check( 'WFCatalog', 'string', '?KSDataCache' )
	
	-- Purge assets and versions for space with needToUpdateDocValue = false and keepRedactedBinaries = false
	-- We would delete space related docs directly from docStore, so there's no
	-- need to update docValue after file deletion
	
	return purgeAssetsForSpace( catalog, spaceId, false, false, optDataCache )
		
		:flatMap( function()
		
			return purgeVersionsForSpace( catalog, spaceId, false )
			
		end )

end

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

function WFCatSpacePurgeUtils.purgeEditForSpace( catalog, spaceId )

	check( 'WFCatalog', 'string' )
	
	-- Purge assets for space with needToUpdateDocValue = true and keepRedactedBinaries = true
	
	return purgeAssetsForSpace( catalog, spaceId, true, true )
		
		:flatMap( function()
		
			-- For desktop, version purge xmp_develop would be handled by PixelProviderFileCachePurgeWorker
			
			if not WFPlatformInfo.isDesktop then
			
				-- Purge version for space with needToUpdateDocValue = true
				-- We would get redated version doc update Oz when role is downgraded, so
				-- we need to update docValue
			
				return purgeVersionsForSpace( catalog, spaceId, true )
			end
			
			return Observable.just( true )
			
		end )

end

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

return WFCatSpacePurgeUtils
