local json = require "cjson"
local m_model = require "authfilter.model"

module(..., package.seeall)

-- Apps que soportan licencias
local m_appsThatSupportedLicenses = {
	helpdesk = {
		permissionId = "helpdesk", source = "AppsIntegrations", target = "HelpDesk"
	}
}
local m_appsBySource = {}

local function GetRedis ()
	local _, redis = m_model.GetDatasource()
	return redis
end

local function CanUpdateVccAvailableLicenses (vcc, user)
	assert(vcc)
	--LogDebug("[AUTHFILTER:licenses.CanUpdateVccAvailableLicenses] Validate if must update licenses in the VCC: %s, user: %s", vcc, json.encode(user or {}))
	-- 1 - Los VCC "System" (provisioning) no actualizan
	if vcc:lower() == "system" then
		LogInfo("[AUTHFILTER:licenses.CanUpdateVccAvailableLicenses] Unable to Update licenses in the SYSTEM VCC")
		return false
	end
	if user then
		-- 2 - Los Super Usuarios no actualizan
		local userGroup = (user.usergroup or ""):lower()
		if userGroup == "full-system-access" then
			LogInfo("[AUTHFILTER:licenses.CanUpdateVccAvailableLicenses] Unable to update licenses in VCC: '%s' to the Super users. User: '%s', role:'full-system-access'.", vcc, user.username)
			return false
		end
	end
	-- 3 - SI, actualiza
	--LogDebug("[AUTHFILTER:licenses.CanUpdateVccAvailableLicenses] YES Must Update licenses in the VCC: %s", vcc)
	return true
end

--[[
	Actualiza en REDIS la información de las licencias para el VCC.
]]--
local function UpdateVccAvailableLicenses (authfilterObject, vcc, availableLicences)
	assert(authfilterObject)
	assert(vcc)
	assert(availableLicences)

	local encodedAvailableLicences = json.encode(availableLicences)

	--LogInfo("[AUTHFILTER:licenses.UpdateVccAvailableLicenses] Updating Available Licenses in VCC: '%s'. Licenses: '%s'.", vcc, encodedAvailableLicences)

	local redis = GetRedis()
	local redisKey = authfilterObject.VCC_AVAILABLE_LICENCES(vcc)
	redis:set(redisKey, encodedAvailableLicences)
--[[
	redis:transaction({watch = redisKey, cas = true, retry = math.huge }, function(tran)
		--LogDebug("-------------> 1")
		tran:multi()
		tran:set(redisKey, encodedAvailableLicences)
		--LogDebug("-------------> 2")
	end)
]]--
end

--[[
	Esta función es la encargada de validar si el Usuario tiene permisos sobre una aplicación determianda.
	La inforamción es cargada proveniente desde el agente, al momento de iniciar sesión; ahí valida los permisos pertinentes y los envía flagueado hacia
	el authfilter, para que este lo almacene en REDIS para su posterior validación.
]]--
local function HasPermissionToApp (authfilterObject, user, appId)
	assert(user)
	assert(appId)

	--LogInfo("[AUTHFILTER:licenses.HasPermissionToApp] Validating App permissions. User '%s', App '%s' in VCC: '%s'.", user.username, appId, user.virtualContactCenter)

	if not user.AppsPermissions then
		--LogInfo("[AUTHFILTER:licenses.HasPermissionToApp] User '%s', not configured any PERMISSION in VCC: '%s'.", user.username, user.virtualContactCenter)
		return false
	end
	local hasPermission = user.AppsPermissions[appId]
	--LogInfo("[AUTHFILTER:licenses.HasPermissionToApp] User '%s', has PERMISSION '%s' for App'%s' in VCC: '%s'.", user.username, hasPermission, appId, user.virtualContactCenter)

	return hasPermission
end

--[[
	Función encargada de obtener las licencias disponibles de las Apps para el VCC.
	- Primero obtiene la info que se encuentra configurada en cassandra para el VCC.
]]--
local function LoadVCCAvailableLicenses (authfilterObject, user, vcc, sessionId)
	local fullUserId = user.username .. "@" .. vcc

	--LogInfo("[AUTHFILTER:licenses.LoadVCCAvailableLicenses] Loading VCC '%s' Available Licenses. UserId '%s', sessionId '%s'.", vcc, fullUserId, sessionId)

	if not CanUpdateVccAvailableLicenses(vcc, user) then
		return false
	end

	-- 1 - Obtengo el VCC desde cassandra para su posterior validación.
	local vccInfo = m_model.GetVCC(vcc)
	assert(vccInfo)

	--LogDebug("[AUTHFILTER:licenses.LoadVCCAvailableLicenses] vcc '%s' vccInfo.AppsIntegrations: '%s'", vcc, vccInfo.AppsIntegrations)

	local redisKey = authfilterObject.VCC_AVAILABLE_LICENCES(vcc)
	--LogDebug("[AUTHFILTER:licenses.LoadVCCAvailableLicenses] redisKey: '%s'", redisKey)
	local redis = GetRedis()
	--redis:del(redisKey)

	-- 2 - Realizo validaciones sobre las Apps que soportan licencias, verificando que al menos exista una configuración para el VCC.
	local totalSupported = 0
	-- validacion
	m_appsBySource = {}
	for appId, info in pairs(m_appsThatSupportedLicenses) do
--		LogDebug("---------------> vccInfo['%s'] = %s", info.source, vccInfo[info.source])
		m_appsBySource[info.source] = m_appsBySource[info.source] or {}
		m_appsBySource[info.source][info.target] = appId

		local permissionId = info.permissionId
		local hasPermission = HasPermissionToApp(authfilterObject, user, permissionId)
		if hasPermission then
			if vccInfo[info.source] then
				totalSupported = totalSupported + 1
			else
				--LogWarning("[AUTHFILTER:licenses.LoadVCCAvailableLicenses] Unable to load AVAILABLE LICENSE in '%s' VCC. SOURCE vccInfo.'%s' does not exist.", vcc, info.source)
			end
		else
			LogInfo("[AUTHFILTER:licenses.LoadVCCAvailableLicenses] Unable to load AVAILABLE LICENSE in '%s' VCC. User '%s' has not permission to App '%s'.", vcc, fullUserId, appId)
		end
	end

	if totalSupported == 0 then
	-- Si no hay Apps Configuradas.
		--LogInfo("[AUTHFILTER:licenses.LoadVCCAvailableLicenses] There are no licenses configured for the VCC '%s' Available Licenses.", vcc)
		redis:del(redisKey)
		return false
	end

	local availableLicences
	local currentEncodedAvailableLicences = redis:get(redisKey)

	--LogDebug("[AUTHFILTER:licenses.LoadVCCAvailableLicenses] REDIS Current Available Licences: '%s' in the vcc '%s'.", tostring(currentEncodedAvailableLicences), vcc)

	-- 3 - Ahora tengo que obtener las licencias disponibles. (Ya sea desde REDIS o desde CASSANDRA)
	if currentEncodedAvailableLicences then
	-- Si los datos ya se encuentran en REDIS
		availableLicences = json.decode(currentEncodedAvailableLicences)
	else
	-- Si los datos aún no se encuentran en REDIS, creo la estructura a paritr de CASSANDRA.
	--
		-- Ej: de los datos las licencias que se crean en la estructura:
		--[[
				availableLicences = {
					AppsIntegrations = {
						HelpDesk = {
							Licenses = appsIntegrations.HelpDesk.Licenses
						}
					}
				}
		-- Se tiene en cuenta al cargar de cassandra los usuarios que ya estan logueados y estan licenciados
		]]--

		local totalUsersLicensedByTarget = function (source, target)
			--Solo voy a contar
			local total = 0
			authfilterObject:GetAllSessions(
				vcc,
				function (user)
					if user
						and user.Licenses
						and user.Licenses[source]
						and user.Licenses[source][target]
						and user.Licenses[source][target].IsLicensed
					then
						total = total + 1
					end
					return false
				end,
				true -- Debe excluir los Super Users (excludeSuperUsers)
			)
			return total
		end

		for _, info in pairs(m_appsThatSupportedLicenses) do
			local source = type(vccInfo[info.source]) == "string" and json.decode(vccInfo[info.source]) or vccInfo[info.source] -- Ej: vccInfo.AppsIntegrations
			-- armo la estructura de las licencias
			availableLicences = availableLicences or {}
			availableLicences[info.source] = availableLicences[info.source] or {}
			availableLicences[info.source][info.target] = {}
			local licenses = source[info.target].Licenses
			availableLicences[info.source][info.target].Licenses = licenses
			local totalUserLicensed = totalUsersLicensedByTarget(info.source, info.target)
			if licenses and licenses.Users then
				if licenses.Users - totalUserLicensed > 0 then
					availableLicences[info.source][info.target].Licenses.Users = licenses.Users - totalUserLicensed
				else
					availableLicences[info.source][info.target].Licenses.Users = 0
				end
			end
		end
	end

	LogInfo("[AUTHFILTER:licenses.LoadVCCAvailableLicenses] VCC '%s' with Current Available Licences: '%s'.", vcc, json.encode(availableLicences or {}))

	return true, availableLicences, vccInfo
end

--[[
	Función encargada de restar en 1 las licencias disponibles del VCC.
	Este es el caso en el cual el Usuario inicia Sesión.
]]--
function DecreaseVCCAvailableLicenses (authfilterObject, user, vcc, sessionId)
	local fullUserId = user.username .. "@" .. vcc

	LogInfo("[AUTHFILTER:licenses.DecreaseVCCAvailableLicenses] (-) --> Decrease VCC '%s' Available Licenses. UserId '%s', sessionId '%s'.", vcc, fullUserId, sessionId)

	if not CanUpdateVccAvailableLicenses(vcc, user) then
		return false
	end

	local success, availableLicences = LoadVCCAvailableLicenses(authfilterObject, user, vcc, sessionId)
	if not success then
		return false
	end

	local redis = GetRedis()
	local noAvailableLicense
	for appSourceName, appTargetInfo in pairs(m_appsBySource) do
		--LogDebug("-- 1 source %s", appSourceName)
		for targetName, appId in pairs(appTargetInfo) do
		--	LogDebug("--- 2 target %s app %s", targetName, appId)
			local availableLicenceSource = availableLicences[appSourceName] --availableLicences.AppsIntegrations
			if availableLicenceSource then
				local availableLicenseTarget = availableLicenceSource[targetName] -- availableLicences.AppsIntegrations.HelpDesk
				if availableLicenseTarget and availableLicenseTarget.Licenses then
					local currentAvailableLicenes = tonumber(availableLicenseTarget.Licenses.Users) -- availableLicences.AppsIntegrations.HelpDesk.Licenses.Users

					local isLicensed = currentAvailableLicenes > 0

					user.Licenses = user.Licenses or {}
					user.Licenses[appSourceName] = user.Licenses[appSourceName] or {} -- AppsIntegrations
					user.Licenses[appSourceName][targetName] = user.Licenses[appSourceName][targetName] or {} -- AppsIntegrations.HelpDesk
					user.Licenses[appSourceName][targetName].IsLicensed = isLicensed

					-- Aca actualizo la información del Usuario, para saber si el mismo tiene licencia para la App
					redis:hset(authfilterObject.USER_BY_SID, sessionId, json.encode(user))

					local msg = isLicensed and "LICENSES" or "NOT LICENSES"
					LogInfo("[AUTHFILTER:licenses.DecreaseVCCAvailableLicenses] User '%s' has %s licenses for App '%s'.", fullUserId, msg, appId)

					if currentAvailableLicenes > 0 then
						availableLicenseTarget.Licenses.Users = currentAvailableLicenes - 1 -- availableLicences.AppsIntegrations.HelpDesk.Licenses.Users
						LogInfo("[AUTHFILTER:licenses.DecreaseVCCAvailableLicenses] Remain %d licenses are available for App '%s'.", availableLicenseTarget.Licenses.Users, appId)
					else
						LogWarning("[AUTHFILTER:licenses.DecreaseVCCAvailableLicenses] There are no licenses available for App '%s'.", appId)
						noAvailableLicense = true
					end
				end
			end
		end
	end

	-- Actualizo la información de las licencias del VCC en REDIS
	UpdateVccAvailableLicenses(authfilterObject, vcc, availableLicences)

	return true, noAvailableLicense
end

--[[
	Función encargada de sumar en 1 las licencias disponibles del VCC.
	Este es el caso en el cual el Usuario cierra Sesión.
]]--
function IncreaseVCCAvailableLicenses (authfilterObject, user, vcc, sessionId)
	LogInfo("[AUTHFILTER:licenses.IncreaseVCCAvailableLicenses] (+) -->  Increase VCC '%s' Available Licenses. UserId '%s', sessionId '%s'.", vcc, user.username, sessionId)

	if not CanUpdateVccAvailableLicenses(vcc, user) then
		return false
	end

	local success, availableLicences, vccInfo = LoadVCCAvailableLicenses(authfilterObject, user, vcc, sessionId, true)
	if not success then
		return false
	end
	--LogDebug("[AUTHFILTER:licenses.IncreaseVCCAvailableLicenses] Current AVAILABLE Licences in vcc '%s'. Info: '%s'.", vcc, json.encode(availableLicences))

	if not user.Licenses then
		LogInfo("[AUTHFILTER:licenses.IncreaseVCCAvailableLicenses] Unable to INCREASE licenses in '%s' VCC. User '%s' has NOT LICENSES CONFIGURED.", vcc, user.username)
		return false
	end

 --or not user.Licenses.AppsIntegrations or not user.Licenses.AppsIntegrations.HelpDesk
 	for appSourceName, appTargetInfo in pairs(m_appsBySource) do
		--LogDebug("-- 1 source %s", appSourceName)

		-- Para poder incrementar las licencias disponibles de las Apps, primero tengo que validar que el Current Usuario, este utilizando alguna licencia de las disponibles.
		if user.Licenses[appSourceName] then -- user.Licenses.AppsIntegrations
			for targetName, appId in pairs(appTargetInfo) do
				--LogDebug("--- 2 target %s app %s", targetName, appId)
				if user.Licenses[appSourceName][targetName] then -- user.Licenses.AppsIntegrations.HelpDesk
					if user.Licenses[appSourceName][targetName].IsLicensed then -- user.Licenses.AppsIntegrations.HelpDesk.IsLicensed
					-- Si llegó entro a este IF quiere decir que el Usuario esta utilizando una licencia para la APP.

						-- Ahora tengo que actualizar las licencias disponibles, incrementando en 1
						local availableLicenceSource = availableLicences[appSourceName] --availableLicences.AppsIntegrations

						if availableLicenceSource then
							local availableLicenseTarget = availableLicenceSource[targetName] -- availableLicences.AppsIntegrations.HelpDesk
							if availableLicenseTarget and availableLicenseTarget.Licenses then
								local currentAvailableLicenes = tonumber(availableLicenseTarget.Licenses.Users) -- availableLicences.AppsIntegrations.HelpDesk.Licenses.Users
								currentAvailableLicenes = currentAvailableLicenes + 1
								availableLicenseTarget.Licenses.Users = currentAvailableLicenes

								-- Acá tengo que validar que al incrementar el número de licencias disponiles para la APP que van a quedar en REDIS, no supere la cantidad configurada en CASSANDRA.
								local vccConfigApps = type(vccInfo[appSourceName]) == "string" and json.decode(vccInfo[appSourceName]) or vccInfo[appSourceName] -- vccInfo.AppsIntegrations
								if vccConfigApps and vccConfigApps[targetName] and vccConfigApps[targetName].Licenses and vccConfigApps[targetName].Licenses.Users then
									local totalVccConfigAppLicenses = tonumber(vccConfigApps[targetName].Licenses.Users) --vccConfigApps.HelpDesk.Licenses.Users
									if availableLicenseTarget.Licenses.Users > totalVccConfigAppLicenses then
										-- Si entra acá es porque ha superado el número y debo dejar igual a lo que esta en CASSANDRA.
										availableLicenseTarget.Licenses.Users = totalVccConfigAppLicenses
									end
								end
								LogInfo("[AUTHFILTER:licenses.IncreaseVCCAvailableLicenses] Remain %d licenses are available for App '%s'.", currentAvailableLicenes, appId)
							end
						end
					end
				end
			end
		end
	end

	-- Actualizo la información de las licencias del VCC en REDIS
	UpdateVccAvailableLicenses(authfilterObject, vcc, availableLicences)

	return true
end

--[[
	Función encargada de resetear las licencias disponibles en REDIS, para un VCC dado.
	Ej:
		Esta función es invocada desde "provisioning" (pm-cli.lua) al momento de actualizar o elmiminar un vcc.
]]--
function CleanUpVccAvailableLicenses (authfilterObject, vcc)
	assert(authfilterObject)
	assert(vcc)
	if not CanUpdateVccAvailableLicenses(vcc) then
		return false
	end
	local redisKey = authfilterObject.VCC_AVAILABLE_LICENCES(vcc)
	LogInfo("[AUTHFILTER:licenses.CleanUpVccAvailableLicenses] Clean Up VCC '%s' redisKey: '%s'", vcc, redisKey)
	local redis = GetRedis()
	redis:del(redisKey)
end

