module(..., package.seeall)

local json        = require "json"
local cjson       = require "cjson"
local m_cassandra = require "helpers.cassandra"
local m_utils     = require "helpers.utils"
local m_redis     = require "helpers.redis"
local m_redisClient
local function GetRedisClient ()
	if m_redisClient then return m_redisClient end
	m_redisClient = m_redis.GetClient()
	assert(m_redisClient, "must initialize redis client")
	return m_redisClient
end

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

local m_permissions = {
	fullSystemAccess = {}
}

--[[
	La clave en la cual se ha almacenado en REDIS las acciones custom pertenecientes al VCC.
	Esto se almacena en el repo "agent" "shared.apps.permissions"
	La clave debe de coincidir.
]]--
m_permissions.MakeKeyVccApplicationsActions = function(vcc)
	assert(vcc)
	local key = string.format(
		"Permissions:VCC:%s:Applications:Actions",
		vcc
	)
	--LogDebug("[HELPERS.permissions:MakeKeyVccApplicationsActions]------------> key: '%s'", key)
	return key
end

--[[
]]--
m_permissions.GetApplicationsActions = function(ksAgent, excludedApps)
	assert(ksAgent)
	local options = {
		keyspace = ksAgent,
		columnFamily = "Application",
		startKey = "",
		endKey = "",
		columns = {"Actions", "Name", "IsOverrideable", "IsVisibleFor"}
	}
	local list = m_cassandra.GetAllColumns( options )
	--LogDebug("list: %s", json.encode(list))
	IncludeFn = IncludeFn or function(appId, appData)
		if excludedApps and excludedApps[appId] then
			return false
		end
		return true
	end
	local appsActions = {}
	for appId, appData in pairs(list) do
		-- LogDebug("  >%s", appId)
		-- LogDebug("      >%s", appData.Actions)
		if IncludeFn(appId, appData) then
			appsActions[appId] = appData
		end
	end
	return appsActions
end

--[[
]]--
m_permissions.GetVccApplicationsActions = function(vcc)
	assert(vcc)
	local redisClient = GetRedisClient()
	if not redisClient then
		return
	end
	local key = m_permissions.MakeKeyVccApplicationsActions(vcc)
	local encodedCurrentVccAppsActions = redisClient:get(key)
	local ok, currentVccAppActions = pcall(cjson.decode, encodedCurrentVccAppsActions)
	if not ok then
		return false
	end
	LogDebug("[HELPERS.permissions.GetVccApplicationsActions] -- > key: '" .. key .. "', encodedCurrentVccAppsActions: '%s'", encodedCurrentVccAppsActions)
	return currentVccAppActions, encodedCurrentVccAppsActions
end

--[[

]]--
m_permissions.GetGroupPermissions = function(fullGroupId, ksAgent)
	assert(fullGroupId)
	assert(ksAgent)
	local permissions = m_cassandra.GetAllColumns {
		keyspace = ksAgent,
		columnFamily = "GroupPermission",
		key = fullGroupId
	}
	--LogDebug("[HELPERS.permissions.GetGroupPermissions] --> fullGroupId: '%s' permissions: '%s'", fullGroupId, cjson.encode(permissions or {}))
	return permissions
end

--[[

]]--
m_permissions.GetGroups = function(vcc, ksAgent)
	assert(vcc)
	assert(ksAgent)
	local vccRoles = m_cassandra.GetAllColumns {
		keyspace = ksAgent,
		columnFamily = "Group",
		superColumns = true,
		key = vcc,
		columns = {} -- todos los grupos del vcc
	} or {}
	--LogDebug("[HELPERS.permissions:GetGroups] ---> vcc: %s, vccRoles: %s", vcc, json.encode(vccRoles))
	return vccRoles
end

--[[

]]--
m_permissions.GetGroup = function(fullGroupId, ksAgent, vccGroups)
	LogDebug("[HELPERS.permissions:GetGroup] fullGroupId: '%s'.", fullGroupId)
	local simpleGroupId, vcc = m_utils:Separate(fullGroupId, "@")
	vccGroups = vccGroups or m_permissions.GetGroups(vcc, ksAgent)
	LogDebug("[HELPERS.permissions:GetGroup] vccGroups: '%s'.", cjson.encode(vccGroups))
	local group
	for groupId, vccGroup in pairs(vccGroups) do
		if simpleGroupId:lower() == groupId:lower() then
			group = vccGroup
			break
		end
	end
	LogDebug("[HELPERS.permissions:GetGroup] retreive group: '%s'.", cjson.encode(group or {}))
	return group, vcc
end

--[[

]]--
m_permissions.SaveGroup = function(vcc, groups, ksAgent)
	assert(vcc)
	assert(groups)
	assert(ksAgent)
	LogDebug("[HELPERS.permissions:SaveGroup] vcc: '%s' '%s'.", vcc, cjson.encode(groups))
	--[[
	]]--
	local function InsertGroup(id, description, isSystemGroup, isDeployer, isFullSystemAccess)
		ksAgent:InsertSuperColumn(vcc, 'Group', id, 'Id', id)
		ksAgent:InsertSuperColumn(vcc, 'Group', id, 'Name', id)
		ksAgent:InsertSuperColumn(vcc, 'Group', id, 'Description', description)
		ksAgent:InsertSuperColumn(vcc, 'Group', id, 'IsSystemGroup', tostring(isSystemGroup))
		ksAgent:InsertSuperColumn(vcc, 'Group', id, 'IsDeployer', tostring(isDeployer or 0))
		ksAgent:InsertSuperColumn(vcc, 'Group', id, 'IsFullSystemAccess', tostring(isFullSystemAccess or 0))
	end

	for _, roleInfo in pairs(groups) do
		InsertGroup(
			roleInfo.Id,
			roleInfo.Description,
			roleInfo.IsSystemGroup,
			roleInfo.IsDeployer,
			roleInfo.IsFullSystemAccess
		)
	end
end

--[[
	Esta funcion genera de forma dinamica los permisos para el Role del sistema "full-system-access".

	Para ello obtiene todas las AppActions de todas las apps menos las acciones de las Apps "interaction" y "provisioning".
	Con estas genera todos los permisos para este role del sistema.

	Esta funcion se debe de invocar en todos .sh de Migraciones que actualicen agreguen permisos a alguna de las Apps de OCC.

	el parametro from puede recibir los valores: from:
		"INITIALDATA_4_CASSANDRA": si el script se ejecuta desde una instalacion de cero (script initialdata4cassandra.lua).
		"MIGRATIONS"             : siempre que se ejecute un archivo de migrations, en el cual se hayan agregado nuevos permisos.
]]--
m_permissions.fullSystemAccess.UpdatePermissions = function(ksAgent, from)
	assert(ksAgent)
	assert(from)

	local fullSystemAccessId = "full-system-access"

	LogDebug("[HELPERS.permissions.fullSystemAccess.UpdatePermissions] Updating Dynamic Permissions for role: " .. fullSystemAccessId .. " Invoked from: " .. tostring(from))

	--[[
		Esta funcion inserta los permisos en la tabla groupPermission para el Role + apps + acciones.
		Luego con esta lista se actualizara la CF 'GroupPermission' en el paso 4)
	]]--
	local groupPermission = {}
	local function InsertGroupPermission (appId, actionId, groupId, vcc, enabled)
		if type(enabled) == "boolean" then
			enabled = enabled
		else
			enabled = true -- por defecto siempre es true
		end
		local full_groupId = groupId .. "@" .. vcc
		groupPermission[ full_groupId ] = groupPermission[ full_groupId ] or {}
		groupPermission[ full_groupId ][ appId ] = groupPermission[ full_groupId ][ appId ] or {}
		groupPermission[ full_groupId ][ appId ][ actionId ] = enabled
	end

	----------------------------------------------------------
	-- 1) obtengo los permisos de las diferentes Apps, menos las apps de la tabla excludedApps
	local excludedApps = {interaction = true, provisioning = true}
	local systemAppsActions = m_permissions.GetApplicationsActions(ksAgent, excludedApps)

	----------------------------------------------------------
	-- 2) recorro la lista de apps y sus acciones,para poder agregar los permisos invocando a la funcion 'InsertGroupPermission'
	local noIncludeAppsActions = {
		administration = {
			admin_restricted = true -- si esta en true NO la debe de incluir
		}
	}

	--[[
		funcion encargada de cargar los permisos y acciones en la tabla 'groupPermission' para un VCC determinado.
	]]--
	local function FillGroupPermissionByVccs (vcc, appsActions)
		for appId, appData in pairs(appsActions) do
			LogDebug("  > " .. appId)
			--LogDebug(json.encode(appData.Actions))
			for actionId in pairs(appData.Actions or {}) do
				LogDebug("     > " .. actionId)
				if not noIncludeAppsActions[appId]
					or not noIncludeAppsActions[appId][actionId]
				then
					InsertGroupPermission(appId, actionId, fullSystemAccessId, vcc)
				else
					LogDebug(string.format("    actionId: %s   -->  NO INCLUDE PERMISSION", actionId))
				end
			end
		end
	end

	----------------------------------------------------------
	-- 3) cargo los permisos al VCC system (siempre)
	FillGroupPermissionByVccs("system", systemAppsActions)

	----------------------------------------------------------
	-- 4) recorro la lista que se inserto en el paso 2) y voy actualizando los permisos para el grupo
	local function UpdateGroupPermissions ()
		LogDebug("[HELPERS.permissions.fullSystemAccess.UpdatePermissions] UpdateGroupPermissions-----> groupPermission: " .. json.encode(groupPermission))
		for roleId, applications in pairs(groupPermission) do
			for appId, permissions in pairs(applications) do
				local encodedPermissions = json.encode(permissions)
				LogDebug("[HELPERS.permissions.fullSystemAccess.UpdatePermissions] Creating permissions for Role: "..roleId.." and application "..appId .. ", encodedPermissions: " .. encodedPermissions)
				ksAgent:InsertColumn(roleId, 'GroupPermission', appId, encodedPermissions)
			end
		end
	end
	UpdateGroupPermissions()

	--[[--------------------------------------------------------
	-- 5)
		Si la función fue invocada desde los scripts de migración, tiene que validar en todos los VCCs (no del sistema):
			- Si el vcc tiene configurada alguna app para la app "app_launcher",
				y si es asi tiene que crear un rol custom en el VCC ídem al rol "full-system-access", pero que además tenga permisos para
				las acciones (apps) que se hayan creado en la app "apps_launcher".

		De esta forma todos los SuperUsuarios van a tener acceso a todas las apps que se han generado en esta app "app_launcher"
	--]]
	if from == "MIGRATIONS" then
		-- obtengo todos los VCCs
		local vccs = m_cassandra.LoadAllVCCs(ksAgent) or {}
		for vccId, vccInfo in pairs(vccs) do
			if tostring(vccInfo.IsSystemVCC or "0") == "0" then
				-- obtengo las Apps custom del VCC (para obtener las Apps app_launcher)
				local vccApplicationsActions = m_permissions.GetVccApplicationsActions(vccId)
				--LogDebug("  1--> vccId: "..vccId.. ", vccApplicationsActions: " .. json.encode(vccApplicationsActions or {})  )
				if vccApplicationsActions and vccApplicationsActions.apps_launcher then
					groupPermission = {}
					-- obtengo los permisos del full-system-access@system (NO los customs), con ellos voy a generar los customs agregandoles las Apps_lauchers del VCC.
					local systemRoleId = fullSystemAccessId .. "@system"
					local systemGroupPermission = m_permissions.GetGroupPermissions(systemRoleId, ksAgent)
					--LogDebug("     2--> vccId: "..vccId.. ", systemGroupPermission: " .. json.encode(systemGroupPermission or {})  )
					local vccRoleId = fullSystemAccessId .. "@" .. vccId
					groupPermission[vccRoleId] = systemGroupPermission
					-- ahora hago un template con las acciones de la app_launcher del VCC.
					local vccAppLauncherActions = {
						apps_launcher = vccApplicationsActions.apps_launcher
					}
					-- cargo en memoria en la tabla groupPermission['full-system-access@CUSTOM_VCC'] las acciones de la app_launcher
					--  es decir a las acciones del sistema del role "full-system-access" le agrego las acciones apps_launcher para despues envicar a guardar en GroupPermission-
					FillGroupPermissionByVccs(vccId, vccAppLauncherActions)
					-- creo en GroupPermission el role custom 'full-system-access@CUSTOM_VCC' (idem al del sistema + las acciones de la app_launcher)
					UpdateGroupPermissions()
					-- ahora tengo que almacenar en la SuperCF Group el nuevo role.
					local systemRoleInfo = m_permissions.GetGroup(systemRoleId, ksAgent)
					if systemRoleInfo then
						local rolesToSave = {
							[vccRoleId] = systemRoleInfo
						}
						m_permissions.SaveGroup(vccId, rolesToSave, ksAgent)
					else
						LogWarning("     Unable to retrive info of system role: "..systemRoleId )
					end
				end
			end
		end
	end
end

-------------------------------------------------------------------------------------------------------------------------------
return {
	fullSystemAccess = {
		UpdatePermissions = m_permissions.fullSystemAccess.UpdatePermissions
	},
	GetApplicationsActions    = m_permissions.GetApplicationsActions,
	GetVccApplicationsActions = m_permissions.GetVccApplicationsActions
}
