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

LrClassicSyncStatus.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.

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

local KSTableUtils 	= require 'KSTableUtils'
local WFCatOzErrors = require 'WFCatOzErrors'
local WFArguments = require 'WFArguments'
local KSStringUtils = require 'KSStringUtils'

local LrAssetUtils = require 'LrAssetUtils'

	-- WF.oz_client.managed_catalog
local WFCatIndexUtils = require 'WFCatIndexUtils'
local WFCatTrashUtils = require 'WFCatTrashUtils'
local WFCatFileSpec = require 'WFCatFileSpec'
local WFCatAssetAux = require 'WFCatAssetAux'

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

local WFCatTrashUtils_isDeleteTrash = assert( WFCatTrashUtils.isDeleteTrash )
local isValidDisplayableAssetDocument = assert( WFCatIndexUtils.isValidDisplayableAssetDocument )

local LrClassicSyncStatus = {}

local check = assert( WFArguments.check )

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

-- Assets

-- Fully Synced, Is Syncing, Sync Error

	-- regular statuses
local kAssetSyncing							= 'syncing'
local kAssetSyncComplete 					= 'synced'
local kAssetNeedsDevelopXmpUpload			= 'needsDevelopXmpUpload'
local kAssetUploading						= 'uploading'

	-- error statuses
local kAssetSyncError						= 'syncError'
local kAssetStorageLimitExceeded			= 'storageLimitExceeded'
local kAssetTrialOrSubscriptionExpired		= 'trialOrSubscriptionExpired'

local kAssetSyncStatusById = {
	kAssetSyncing,						-- 1
	kAssetSyncComplete,					-- 2
	kAssetSyncError	,					-- 3
	kAssetStorageLimitExceeded,			-- 4
	kAssetTrialOrSubscriptionExpired,	-- 5
	kAssetNeedsDevelopXmpUpload,		-- 6
	kAssetUploading,					-- 7
}

local kAssetSyncStatus = KSTableUtils.invert( kAssetSyncStatusById )

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

local kAlbumSyncing = 'syncing'
local kAlbumComplete = 'synced'
local kAlbumSyncError = 'syncError'

local kAlbumSyncStatusById = {
	kAlbumSyncing,									-- 1
	kAlbumComplete,									-- 2
	kAlbumSyncError,   							    -- 3

}

local kAlbumSyncStatus = KSTableUtils.invert( kAlbumSyncStatusById )

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

local kMissingAux = "missingAux"
local kMissingProxy = "missingProxy"

local kDelimiter = ","
local kSubDelimiter = "::"

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

local function classic_asset_sync_status_table()

	return {

		sql = [[CREATE TABLE classic_asset_sync_status( assetId, syncStatus INTEGER, missingBinaries )]],

		useLocalId = true,

		hasOneRowPerDocument = false,

		relevantTypeSubtypeTuples = {
			{ 'asset', 'image' },
		},

		map = function( docValue, emit )

			if isValidDisplayableAssetDocument( docValue ) then

				local assetId = docValue._id
			 	local syncStatus = LrClassicSyncStatus.getAssetSyncStatus( docValue )

				-- Get a list of missing biaries which are not uploaded yet, but there's no localOnly path

				local missingBinariesString = LrClassicSyncStatus.getMissingBinariesString( docValue )

				emit( assetId, syncStatus, missingBinariesString )

			end

		end

	}

end

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

local function asset_status_by_assetId_index()
	return [[CREATE INDEX classic_asset_sync_status_by_assetId ON classic_asset_sync_status( assetId, syncStatus )]]
end

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

local function assetId_by_syncStatus_index()
	return [[CREATE INDEX classic_asset_sync_status_by_syncStatus ON classic_asset_sync_status( syncStatus, assetId )]]
end

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

local function missingBinaries_index()
	return [[CREATE INDEX classic_asset_sync_status_by_missingBinaries ON classic_asset_sync_status( missingBinaries )]]
end

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

function LrClassicSyncStatus.generateIndexSchema( indexStoreSchema )

	indexStoreSchema:addTable( classic_asset_sync_status_table() )

	indexStoreSchema:addIndex( asset_status_by_assetId_index() )

	indexStoreSchema:addIndex( assetId_by_syncStatus_index() )

	indexStoreSchema:addIndex( missingBinaries_index() )

end

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

local function isMarkedError( docValue )
	-- check if there is any error in docValue
	if WFCatOzErrors.hasMultipartCompleteError( docValue )
	or WFCatOzErrors.hasInsufficientPermissionError( docValue ) then
		return true
	end
	return false
end

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

local function binaryProxyUploadPending( docValue )

	local willNotUploadMaster = KSTableUtils.derefOpt( docValue, 'importSource', 'willNotUploadMaster' )

	if willNotUploadMaster then

		local hasproxyLink, _, isInvalid = WFCatFileSpec.getProxyLinkFromDocValue( docValue, '2560' )

		return not hasproxyLink or not ( isInvalid == nil )

	end

	return false

end

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

function LrClassicSyncStatus.getAssetSyncStatus( docValue )

	local syncStatusId = kAssetSyncComplete

	if WFCatOzErrors.hasStorageExceededError( docValue ) then

		syncStatusId = kAssetStorageLimitExceeded

	elseif WFCatOzErrors.hasTrialOrSubscriptionExpiredError( docValue ) then

		syncStatusId = kAssetTrialOrSubscriptionExpired

	elseif isMarkedError( docValue ) then

		syncStatusId = kAssetSyncError

	--elseif WFCatAssetAux.hasUnsyncedDevelopAuxLocally( docValue )
	--	or WFCatAssetAux.needsAuxDownloadForDevelop( docValue ) then
	--
	--	syncStatusId = kAssetNeedsDevelopXmpUpload

	elseif docValue._spaceId then

		syncStatusId = kAssetSyncComplete

	elseif	docValue._deleted and not WFCatTrashUtils_isDeleteTrash( docValue ) then

		syncStatusId = kAssetSyncComplete

	elseif binaryProxyUploadPending( docValue ) then

		syncStatusId = kAssetUploading

	elseif LrAssetUtils.isLatestMetadataUploaded( docValue ) then

		syncStatusId = kAssetSyncComplete

	else

		syncStatusId = kAssetSyncing

	end

	return kAssetSyncStatus[ syncStatusId ]

end

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

function LrClassicSyncStatus.getMissingBinariesString( docValue )

	local missingBinaries = {}

	-- Check if proxy is not on Oz or invalid, and there's no proxy localOnly path

	local willNotUploadMaster = KSTableUtils.derefOpt( docValue, 'importSource', 'willNotUploadMaster' )
	local hasproxyLink, _, isInvalid = WFCatFileSpec.getProxyLinkFromDocValue( docValue, '2560' )
	local proxyPath = WFCatFileSpec.buildProxyPathFromDocValue( docValue )

	if willNotUploadMaster and ( not hasproxyLink or isInvalid ) and not proxyPath then

		local update = kMissingProxy
		table.insert( missingBinaries, update )

	end

	-- Check if any aux is not on Oz, and there's no aux localOnly path, include c2pa too
	
	local onlyConsiderDevelopAndC2pa = true
	local auxUploadsRequired, auxObjectsToUpload = WFCatAssetAux.uploadsRequired( docValue, onlyConsiderDevelopAndC2pa )

	if auxUploadsRequired then

		for _, auxInfo in pairs( auxObjectsToUpload ) do

			if not WFCatAssetAux.getAuxLocalOnlyFilePath( docValue, auxInfo.digest ) then

				local update = kMissingAux .. kSubDelimiter .. auxInfo.digest
				table.insert( missingBinaries, update )

			end

		end

	end

	local missingBinariesString

	if next( missingBinaries ) then
		missingBinariesString = table.concat( missingBinaries, kDelimiter )
	end

	return missingBinariesString

end

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

function LrClassicSyncStatus.getAssetSyncStatusList()

	return kAssetSyncStatus

end

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

-- Returns a map of assetIds and their syncstatus
-- This method is the classic variant of WFCatSyncStatus.getSyncStatusForAssets()
function LrClassicSyncStatus.getSyncStatusOfAssets( wfCatalog, assetIds )

	local assetidMapSyncStatus = {}
	return wfCatalog:indexStore():query {
			sql = [[ SELECT assetId, syncStatus
					FROM classic_asset_sync_status
					WHERE assetId IN CARRAY(?, ?, 'char*')
				  ]],
			assetIds,
			#assetIds
		}
		:map( function( rows )

			for _, row in ipairs( rows ) do
				local assetId = row[ 1 ]
				local statusId = row[ 2 ]

				if assetId  and statusId then
					-- kAssetSyncStatusById is using WFCatSyncStatus
					local syncStatus = kAssetSyncStatusById[ statusId ]
					assetidMapSyncStatus[ #assetidMapSyncStatus + 1 ] = {
						assetId = assetId,
						syncStatus = syncStatus
					}
				end
			end

			return assetidMapSyncStatus

		end )

end

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

function LrClassicSyncStatus.getSyncStatusForAssets( wfCatalog )

	return wfCatalog:indexStore():query {
			sql = [[ SELECT syncStatus, count( syncStatus )
					FROM classic_asset_sync_status
					GROUP BY syncStatus ]],
		}
		:map( function( rows )
			local statusCounts = {}
			local syncingCount = 0

			for _, row in ipairs( rows ) do
				local statusId = row[ 1 ]
				local statusCount = row[ 2 ]

				if statusId and statusCount then
					local status = kAssetSyncStatusById[ statusId ]
					if status then
						statusCounts[ status ] = tonumber( statusCount ) or 0
					end
				end
			end

			for _, status in pairs( kAssetSyncStatusById ) do
				if not statusCounts[ status ] then
					statusCounts[ status ] = 0
				end
				if status ~= kAssetSyncComplete then
					syncingCount = syncingCount + tonumber( statusCounts[ status ] )
				end
			end

			return { statusCounts, syncingCount }
		end )

end

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

function LrClassicSyncStatus.getAlbumSyncStatus( docValue )

	local syncStatusStr = kAlbumSyncing

	if isMarkedError( docValue ) then

		syncStatusStr = kAlbumSyncError

	elseif LrAssetUtils.isLatestMetadataUploaded( docValue ) then

		syncStatusStr = kAlbumComplete

	end

	local syncStatus = kAlbumSyncStatus[ syncStatusStr ]

	return syncStatus

end

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

function LrClassicSyncStatus.getAlbumSyncStatusByAlbumIds( wfCatalog, albumIds )

	check( 'WFCatalog', 'table' )

	return wfCatalog:indexStore():query {
			sql = [[ SELECT albumId, syncStatus
					FROM albums
					WHERE albumId IN CARRAY(?, ?, 'char*')
				  ]],
			calm = 2,
			albumIds,
			#albumIds
		}
		:map( function( rows )

			local albumIdMapSyncStatus = {}

			for _, row in ipairs( rows ) do
				local albumId = row[ 1 ]
				local statusId = row[ 2 ]

				if albumId  and statusId then
					local syncStatus = kAlbumSyncStatusById[ statusId ]
					albumIdMapSyncStatus[ #albumIdMapSyncStatus + 1 ] = {
						albumId = albumId,
						syncStatus = syncStatus
					}
				end
			end

			return albumIdMapSyncStatus

		end )
end

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

function LrClassicSyncStatus.getSyncStatusForAlbums( wfCatalog )

	return wfCatalog:indexStore():query {
		sql = [[ SELECT syncStatus, count( syncStatus )
				FROM albums
				GROUP BY syncStatus ]],
		calm = 2,
	}
	:map( function( rows )
		local statusCounts = {}
		local syncingCount = 0

		for _, row in ipairs( rows ) do
			local statusId = row[ 1 ]
			local statusCount = row[ 2 ]

			if statusId and statusCount then
				local status = kAlbumSyncStatusById[ statusId ]
				if status then
					statusCounts[ status ] = tonumber( statusCount ) or 0
				end
			end
		end

		for _, status in pairs( kAlbumSyncStatusById ) do
			if not statusCounts[ status ] then
				statusCounts[ status ] = 0
			end
			if status ~= kAssetSyncComplete then
				syncingCount = syncingCount + tonumber( statusCounts[ status ] )
			end
		end

		return { statusCounts, syncingCount }
	end )

end

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

function LrClassicSyncStatus.getDocIdsAndStatusWithSyncPendingOrError( wfCatalog )

	return wfCatalog:indexStore():queryOnce {
			sql = [[SELECT assetId, syncStatus
			FROM classic_asset_sync_status
			WHERE syncStatus != 2]],
		}
		:map( function( rows )
			local results = {}
			for _, row in ipairs( rows ) do
				table.insert( results, { assetId = row[ 1 ], statusString = kAssetSyncStatusById[ row[ 2 ] ] } )
			end
			return results
		end )
end

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

function LrClassicSyncStatus.getMissingProxyList( wfCatalog )

	return wfCatalog:indexStore():queryOnce {
			sql = [[SELECT assetId, missingBinaries
					FROM classic_asset_sync_status
					WHERE missingBinaries IS NOT NULL]],
			quiesceUntilUpToDate = true
		}

		:map( function( rows )

			local missingProxyList = {}

			for _, row in ipairs( rows ) do

				local assetId = row[ 1 ]
				local missingBinariesString = row[ 2 ]

				if missingBinariesString and missingBinariesString ~= "" then

					local missingBinaries = KSStringUtils.split( missingBinariesString, kDelimiter )

					for _, missingBinary in ipairs( missingBinaries ) do

						local missingBinaryType, _ = unpack( KSStringUtils.split( missingBinary, kSubDelimiter ) )

						if missingBinaryType == kMissingProxy then
							missingProxyList[ #missingProxyList + 1 ] = assetId
						end

					end

				end

			end

			return missingProxyList

		end )

end

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

function LrClassicSyncStatus.getMissingAuxList( wfCatalog )

	return wfCatalog:indexStore():queryOnce {
			sql = [[SELECT assetId, missingBinaries
					FROM classic_asset_sync_status
					WHERE missingBinaries IS NOT NULL]],
			quiesceUntilUpToDate = true
		}

		:map( function( rows )

			local missingAuxList = {}

			for _, row in ipairs( rows ) do

				local missingBinariesString = row[ 2 ]

				local missingAuxForOneAsset = {}

				if missingBinariesString and missingBinariesString ~= "" then

					local missingBinaries = KSStringUtils.split( missingBinariesString, kDelimiter )

					for _, missingBinary in ipairs( missingBinaries ) do

						local missingBinaryType, missingBinaryData = unpack( KSStringUtils.split( missingBinary, kSubDelimiter ) )

						if missingBinaryType == kMissingAux then

							local auxDigest = missingBinaryData

							local basePath = WFCatAssetAux.getBasePath( wfCatalog )
							local filePath = WFCatAssetAux.calculatePathUsingDigest( basePath, auxDigest )

							missingAuxForOneAsset[ #missingAuxForOneAsset + 1 ] = {
								digest = auxDigest,
								filePath = filePath
							}

						end

					end

				end

				if next( missingAuxForOneAsset ) then
					local assetId = row[ 1 ]
					missingAuxList[ assetId ] = missingAuxForOneAsset
				end

			end

			return missingAuxList

		end )

end

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

function LrClassicSyncStatus.getPendingUploadList( lrClassicCatalog )

	return lrClassicCatalog:indexStore():queryOnce {
			sql = [[SELECT docId, binaryType
					FROM needsBinaryUpload 
					WHERE docType == 'asset']],
			quiesceUntilUpToDate = true
		}

		:map( function( rows )

			local pendingUploadIds = {}
			pendingUploadIds["binaryIds"] = {}
			pendingUploadIds["developIds"] = {}
			
			for _, row in ipairs( rows ) do

				local assetId = row[ 1 ]
				local pendingBinaryType = row[ 2 ]
				
				if pendingBinaryType == 'original' or pendingBinaryType == 'proxy' then
					table.insert( pendingUploadIds["binaryIds"] , assetId )
				else
					table.insert( pendingUploadIds["developIds"] , assetId )
				end

			end

			return pendingUploadIds

		end )

end

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

return LrClassicSyncStatus
