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

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

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

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

	-- WF.core.utils
local KSJsonDecoder = require 'KSJsonDecoder'
local WFLogger = require 'WFLogger'
local WFPlatformInfo = require 'WFPlatformInfo'

	-- WF.core.rxlua
local WFRxRetryObservable = require 'WFRxRetryObservable'

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

local WFCloseSupport = require 'WFCloseSupport'

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

local logger = WFLogger( 'WFCatOzMaintenanceTracker' )

local kMaintenanceError = 'MaintenanceError'
local kOutageError = 'OutageError'

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

-- @mark WFCatOzMaintenanceTracker namespace

local WFCatOzMaintenanceTracker = {}

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

local function isOzOutageOrDownForMaintenanceError( headers )

	local isOzDown = headers and 
			headers.status_code == '503' and 
			( headers[ 'X-Result-Code' ] == kMaintenanceError or
			  headers[ 'X-Result-Code' ] == kOutageError )
					
	local retryAfter = isOzDown and headers[ 'Retry-After' ]
	retryAfter = retryAfter and tonumber( retryAfter )
					
	return isOzDown, retryAfter

end

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

local pollAccountForMaintenanceUpdatesSubscription

local function disposeExistingPollingSubscription()
		
	if pollAccountForMaintenanceUpdatesSubscription then
		pollAccountForMaintenanceUpdatesSubscription:dispose()
		pollAccountForMaintenanceUpdatesSubscription = nil
	end

end

local function pollOzForMaintenanceUpdates( wfCatalog, ozMaintenanceStream, retryAfter )

	if not pollAccountForMaintenanceUpdatesSubscription then
	
		-- Here we create a HTTP request that polls the Oz account URL periodically
		-- until Oz is no longer under maintenance.

		retryAfter = WFPlatformInfo.isUnitTest() and 0.1 or retryAfter

		local downForMaintenance

		pollAccountForMaintenanceUpdatesSubscription = WFRxRetryObservable {
				maxAttempts = 100000, 
					-- Sufficiently large. Assuming a 60 second retry, this is equivalent 
					-- to waiting a total of 70 days. Basically the same as waiting indefinitely.
				retryDelay = retryAfter or 60,
				factory = function()
				
					if WFCloseSupport.hasBeenOrWillBeClosed( wfCatalog ) or
						WFCloseSupport.hasBeenOrWillBeClosed( wfCatalog:ozUser() ) then
						
						return Observable.just( true )
						
					end
				
					return wfCatalog:ozUser():getAccountDocFromOz()
					
						:catch( function( ksDataOrString, responseHeader, ... )
						
							if isOzOutageOrDownForMaintenanceError( responseHeader ) then
							
								if not downForMaintenance then
								
									downForMaintenance = true
									
									local ok, accountAndErrorInformation = pcall( KSJsonDecoder.decodeWebSafeJson_stripNull_ignoreEmpty, ksDataOrString )
									 
									if not ok then
									
										accountAndErrorInformation = string.format( "An unknown error has occured: %s", tostring( ksDataOrString ) or "Unknown" )
									
									end
									
									ozMaintenanceStream:onNext( {
										id = 'catalog',
										isUnderMaintenance = true,
										accountAndErrorInfo = accountAndErrorInformation
									} )
									
								end
								
							end
							
							-- Throw so that we will continue to retry.
							
							return Observable.throw( ksDataOrString, responseHeader, ... )
								
						end )

				end
			}
			
			:take( 1 )
			
			:subscribe( function()
			
				-- Oz is no longer under maintenance!
				
				if downForMaintenance or downForMaintenance == nil then
				
					ozMaintenanceStream:onNext( {
						id = 'catalog',
						isUnderMaintenance = false
					} )
					
				end
				
				disposeExistingPollingSubscription()
			
			end, disposeExistingPollingSubscription, disposeExistingPollingSubscription )
	
		pollAccountForMaintenanceUpdatesSubscription:disposeWith( ozMaintenanceStream )
	
	end

end

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

local function monitorForOzMaintenanceErrors( wfCatalog, ozMaintenanceStream, optArgs )

	-- Setup a stream that monitors all HTTP errors that occur within
	-- WFRxHttpClient. If we detect an Oz Maintenance error, we then
	-- begin polling Oz (pollOzForMaintenanceUpdates) to monitor the
	-- status of the maintenance and to get additional information
	-- which we deliver to the clients. Please note that we are only
	-- looking for Oz maintenance errors related to the user's own account;
	-- WFCatSpaceFeedsManager is responsible for detecing and reporting
	-- outages related to shared-with-you albums owner's accounts.

	local monitorForErrorsSubscription

	local function disposeMonitorSubscription()
		
		if monitorForErrorsSubscription then
			monitorForErrorsSubscription:dispose()
			monitorForErrorsSubscription = nil
		end
	
	end

	local httpErrorStream = optArgs.httpErrorStream or WFRxHttpClient.watchHttpErrors()

	disposeExistingPollingSubscription()

	monitorForErrorsSubscription = httpErrorStream
	
		:map( function( ksDataOrString, headers )
				
			local isOzDownForMaintenance, retryAfter = isOzOutageOrDownForMaintenanceError( headers )
						
			if isOzDownForMaintenance then
			
				logger:warnf( "Oz Maintenance detected. Info: %s", tostring( ksDataOrString ) )
			
			end
					
			return isOzDownForMaintenance, retryAfter
			
		end )
		
		:filter( function( isOzDownForMaintenance )
		
			return isOzDownForMaintenance
		
		end )

		:subscribe( function( isOzDownForMaintenance, retryAfter )
		
			pollOzForMaintenanceUpdates( wfCatalog, ozMaintenanceStream, retryAfter )
		
		end, disposeMonitorSubscription, disposeMonitorSubscription )

	monitorForErrorsSubscription:disposeWith( ozMaintenanceStream )

end

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

--[[!
	\brief Get a stream that fires with updates when the user's catalog availability
		 changes due to Oz maintenance or an Oz service outage. Calling this
		 function multiple times will return the same observable stream.

	\return Observable( { id = 'catalog', isUnderMaintenance = status, accountAndErrorInfo = accountInfo } ) 
		status is 'true' in the event that the user's account is under maintenance or down, otherwise
		it will be 'false'. accountInfo is a table that contains the error response
		when trying to retrieve the account doc from oz, it will only be present if
		status is true.
		
		Note that this stream will immediately fire with { id = 'catalog', isUnderMaintenance = 'false' }
		when first subscribed to.
--]]

local instanceOfMaintenanceStream

function WFCatOzMaintenanceTracker.getOzMaintenanceNotificationStream( wfCatalog, optArgs )

	if not instanceOfMaintenanceStream or instanceOfMaintenanceStream:isDisposed() then
	
		optArgs = optArgs or {}

		local ozMaintenanceStream = Rx.ReplaySubject( 1 )
		
		ozMaintenanceStream:onNext( {
			id = 'catalog',
			isUnderMaintenance = false
		} )
		
		monitorForOzMaintenanceErrors( wfCatalog, ozMaintenanceStream, optArgs )

		instanceOfMaintenanceStream = ozMaintenanceStream
	
	end
	
	return instanceOfMaintenanceStream

end

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

return WFCatOzMaintenanceTracker
