local json = require "cjson" 
require "luis.logger" -- require utilizado para pm-cli/provisioningmanager
local utils = require "luis.utils"
--------------------
-- version API Base
local appsBase = require "luis.ApiBase.apps"
local luis_models = require("luis.models"):GetModels()
local m_useWebAPI = false

----
local m_apiInstances = nil

module(..., package.seeall)

local m_defaultAppVersion = "0.1"

local function InitApiVersionsModules (apiName) 
	
	assert(type(apiName) == "string")

	_M.m_apiInstances[apiName].apps = appsBase
	
	for name, module in pairs(_M.m_apiInstances[apiName]) do
		--print(tostring(name) .. " = " .. tostring(module))
		if type(module) == "table" and module.SetOwner then
			module.SetOwner(_M.m_apiInstances[apiName])
		end
	end
end

--[[ --------------------------------------
	API - URL BASE
--------------------------------------]]--
local API_ALL_VERSIONS_LUIS_BASE_URIS

local function SetVersionBaseURIs ()
	local urisVersions = {
	-- VERSION DEFAULT: 2.0
	--  Sample:
	--			https://{endpoint}/luis/api/v2.0/apps/{appId}/..
	-- 			{endpoint} = westus.api.cognitive.microsoft.com // cascorp.cognitiveservices.azure.com
		["DEFAULT"] = {
			APPS          = [[https://westus.api.cognitive.microsoft.com/luis/api/v2.0/apps/]],
			AZUREACCOUNTS = [[https://westus.api.cognitive.microsoft.com/luis/api/v2.0/azureaccounts/]],
			WEBAPI_APPS   = [[https://westus.api.cognitive.microsoft.com/luis/webapi/v2.0/apps/]],
			REQUIRE_AZURE_LANGUAGE_STUDIO_ACCOUNT = false,
			ALLOW_NEW_APPLICATIONS = false
		},
	-- VERSION 2.0
	--  Sample:
	--			https://{endpoint}/luis/api/v2.0/apps/{appId}/..
	-- 			{endpoint} = westus.api.cognitive.microsoft.com // cascorp.cognitiveservices.azure.com
		["2.0"] = {
			APPS          = [[https://westus.api.cognitive.microsoft.com/luis/api/v2.0/apps/]],
			AZUREACCOUNTS = [[https://westus.api.cognitive.microsoft.com/luis/api/v2.0/azureaccounts/]],
			WEBAPI_APPS   = [[https://westus.api.cognitive.microsoft.com/luis/webapi/v2.0/apps/]],
			REQUIRE_AZURE_LANGUAGE_STUDIO_ACCOUNT = false,
			ALLOW_NEW_APPLICATIONS = false
		},
	-- VERSION 3.0-PREVIEW
	--  Sample:
	--			https://{endpoint}/luis/authoring/v3.0-preview/apps/{appId}/..
	-- 			{endpoint} = westus.api.cognitive.microsoft.com // cascorp.cognitiveservices.azure.com
		--["3.0-PREVIEW"] = {
		--	APPS          = [[https://westus.api.cognitive.microsoft.com/luis/authoring/v3.0-preview/apps/]],
		--	AZUREACCOUNTS = [[https://westus.api.cognitive.microsoft.com/luis/authoring/v3.0-preview/azureaccounts/]],
		--	WEBAPI_APPS   = [[https://westus.api.cognitive.microsoft.com/luis/webapi/v2.0/apps/]],
		--	REQUIRE_AZURE_LANGUAGE_STUDIO_ACCOUNT = false,
		--	ALLOW_NEW_APPLICATIONS = false
		--},
	-- VERSION 2022-10-01-preview

		["2022-10-01-preview"] = {
			APPS          = [[https://resourcename.cognitiveservices.azure.com/language/authoring/analyze-conversations/projects/]],
			REQUIRE_AZURE_LANGUAGE_STUDIO_ACCOUNT = true,
			ALLOW_NEW_APPLICATIONS = true
		}
	}
	API_ALL_VERSIONS_LUIS_BASE_URIS = urisVersions

end
-- SetVersionBaseURIs()
----------------------------------

function EnableLogger (enabled)
	-- Funcion "SetEnabledLogger" existente en luis.logger
	if type(SetEnabledLogger) == "function" then
		SetEnabledLogger(enabled)
	end
end

local function MakeTaskVersion (versionId)
	return (versionId or ""):len() > 0 and versionId or m_defaultAppVersion
end

function GetBaseURI (self, id, api_version) 

	assert(type(id) == "string")

	local urisApiVersion = API_ALL_VERSIONS_LUIS_BASE_URIS[api_version or "DEFAULT"]
	assert(urisApiVersion)

	local baseURI = urisApiVersion[id]
	assert(baseURI)

	return baseURI
end

function MakeCompleteURI (self, api_version, uriBaseId, uri, ...) 

	assert(type(uriBaseId) == "string")
	assert(type(uri) == "string")
	local args = {...}
	local baseURI = self:GetBaseURI(uriBaseId, api_version)
	--print("------------>" .. baseURI)
	return baseURI .. uri:format(unpack(args))
end

function GetDefaultDemoSubscriptionKey ()
	local request = require "luis.request"
	return request.GetDefaultDemoSubscriptionKey()
end

--[[
	Recibe:
		paging = {
			page: 0,
			rows_per_page: 50
		}
	Retorna los parametros:
		1) Si tiene paginado   : "?skip=0&take=2"
		2) Si no tiene paginado: ""
]]--
function MakePagingParameters (paging)
	if type(paging) ~= "table" then
		return ""
	end
	local page = tonumber(paging.page)
	if not page or page < 0 then
		return ""
	end
	local defaultTakeRows = 100
	local maxTakeRows = 500
	local takeRows = tonumber(paging.rows_per_page or defaultTakeRows)
	local skipToRow
	if page > 0 then
		-- Los números de páginas se deben de calcular a partir de la página 0 que correspondería a la solicitud de la página 1.
		-- Por este motivo siempre hay que restarle 1
		page = page - 1
	end
	local skipToRow = page * (takeRows - 1)
	takeRows = takeRows + 1 -- tiene que ser mas 1 del total
	if takeRows > maxTakeRows then
		takeRows = maxTakeRows
	end
	return string.format("?skip=%s&take=%s", skipToRow, takeRows)
end

-- *******************************************************************************
-- API APPS
-- *******************************************************************************

--[[
]]--
function GetAssignedAzureAccountsResourceInApp (self, appId, info)
	assert(type(appId) == "string")
	assert(type(info) == "table")
	assert(type(info.tenant_id) == "string")
	assert(type(info.client_id) == "string")
	assert(type(info.client_secret) == "string")
	assert(type(info.resource) == "string")
	return self.apps.GetAssignedAzureAccountsResource(appId, info)
end

--[[
]]--
function LoadApps (vcc)
	assert(vcc)
	local appsList = luis_models.VCCs.GetApplicationsMsCognitiveServices(vcc)
	local total = utils.GetRowCount(appsList)
	return appsList, total
end

--[[
]]--
function GetAppFromDataBase (vcc, appId)
	assert(type(vcc) == "string")
	assert(type(appId) == "string")
	return luis_models.VCCs.GetApplicationMsCognitiveService(vcc, appId)
end

--[[
]]--
function GetApp (self, appId, vcc, api_version)
	assert(type(appId) == "string")
	assert(type(vcc) == "string")
	assert(type(api_version) == "string")
	return self.apps.GetApp(appId, vcc, api_version)
end

--[[
	Al nombre de la "App" le concatena el VCC y el ClusterId.
	Ej:
		Recibe : ElNombre
		Retorna: ElNombre@ElVcc - i6BetaCaroteno
--]]
local function MakeAppNameForVcc (name, vcc)
	if not name then
		return
	end
	local newName = name
	-- Le agrego el VCC al Name
	if vcc then
		newName =  newName .. "@" .. vcc
	end
	-- Le agrego el ClusterId
	local clusterId = os.getenv("CLUSTER_ID") or "NONE"
	if clusterId then
		newName =  newName .. " - " .. clusterId
	end
	return newName
end

--[[
	A la descripción de la "App" le concatena la Versión del Build de i6 que se encuentra instalado.
	Ej:
		Recibe : LaDescripcion
		Retorna: LaDescripcion - 6.0.1674+MCWJ-1674
--]]
local function MakeAppDescriptionForVcc (description)
	description = description or ""
	local newDescription = description
	-- Le agrego la Version del Build de i6
	local iccVersion = os.getenv("ICC_VERSION") or "NONE"
	if iccVersion then
		newDescription =  newDescription .. " - " .. iccVersion
	end
	return newDescription
end

--[[
]]--
function AddApp (self, info, vcc) 
	assert(type(info) == "table")
	assert(type(vcc) == "string")
	assert(type(info.api_version) == "string")

	local vccAzureLanguageStudioAccounts, totalVCCAzureLanguageStudioAccounts = LoadVCCAzureLanguageStudioAccount(vcc)
	 
	for name, module in pairs(API_ALL_VERSIONS_LUIS_BASE_URIS) do

		if name == info.api_version then
			if module.ALLOW_NEW_APPLICATIONS then
				if module.REQUIRE_AZURE_LANGUAGE_STUDIO_ACCOUNT and totalVCCAzureLanguageStudioAccounts == 0 then
					return false, {code = 400, reason = "This version requires an azure language studio account"}
				end
			else
				return false, {code = 400, reason = "This version does not support creating new applications"}
			end
		end

	end

	info.__occName = MakeAppNameForVcc(info.name, vcc)
	info.__occDescription = MakeAppDescriptionForVcc(info.description)

	local success, response = self.apps.AddApp(info, vcc, info.api_version) 
	
	if not success then
		return false, response
	end
	-- Elimino caracteres del Id y actualiza la info de la App en el VCC.
	local newId = utils.SanitizeId(response)
	info.id = newId

	if info.api_version == "2022-10-01-preview" then 
		info.activeVersion = info.name
	else
		info.activeVersion = info.activeVersion or m_defaultAppVersion
	end

	luis_models.VCCs.UpdateApplicationMsCognitiveServices(vcc, newId, info)
	return true, response
		
end

--[[
]]--
function RenameApp (self, info, vcc) 
	assert(type(info) == "table")
	assert(type(vcc) == "string")

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, info.id)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	info.__occName = MakeAppNameForVcc(info.name, vcc)
	info.__occDescription = MakeAppDescriptionForVcc(info.description)
	local success, response = self.apps.RenameApp(info, vcc, api_version) 
	if not success then
		return false, response
	end
	-- Renombra la info de la App en el VCC.
	local appId = info.id
	info.id = nil
	luis_models.VCCs.UpdateApplicationMsCognitiveServices(vcc, appId, info)
	response.__occName = info.__occName
	response.__occDescription = info.__occDescription
	return true, response
end

local function DeleteAppFromCassandra (vcc, appId)
	-- Se quita del VCC la App.
	LogInfo("[LUIS_API:DeleteAppFromCassandra] Deleting application '%s' VCC: '%s' from Cassandra Database.", appId, vcc)
	luis_models.VCCs.RemoveApplicationMsCognitiveServices(vcc, appId)
end

--[[
]]--
function DeleteApp (self, appId, vcc) 
	
	assert(type(appId) == "string")
	assert(type(vcc) == "string")

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	local success, response = self.apps.DeleteApp(appId, vcc, api_version)
			
	if not success then
		return false, response
	end
	-- Se quita del VCC la App.
	DeleteAppFromCassandra(vcc, appId)
	return true, response

end

local function __MakeEndpointURL (appId, appPublishInfo, inconcertInfo, api_version, vcc) 
	local timeZone = luis_models.VCCs.GetTimezone(inconcertInfo.timezone)
	assert(type(timeZone) == "table")

	--LogDebug("[LUIS_API:__MakeEndpointURL] timeZone: %s", json.encode(timeZone))

	local minutes = timeZone.offset * 60 -- (*) A la hora del timezone la convierte en minutos
	local timeZoneMinutes = timeZone.minutes
	if timeZoneMinutes > 0 then
	-- Si tiene configurado minutos, se los tiene que sumar a los mintuos de las horas (*).
	-- Si la hora es negativa tiene que dejar los minutos del timezone en negativo, para que se incrementen.
		minutes = minutes + ( minutes >= 0 and timeZoneMinutes or timeZoneMinutes * -1 )
	end

	if api_version == "2022-10-01-preview" then
		
		local vccAzureLanguageStudioAccounts = luis_models.VCCs.GetAzureLanguageStudioAccountsMsCognitiveServices(vcc)
		local totalVCCAzureLanguageStudioAccounts = utils.GetRowCount(vccAzureLanguageStudioAccounts)
		local azureLanguageStudio = {} 
		if totalVCCAzureLanguageStudioAccounts > 0 then 
			local firstKey = next(vccAzureLanguageStudioAccounts) 
			azureLanguageStudio = vccAzureLanguageStudioAccounts[firstKey] 
		end
		
		local endpointUrl = string.format("https://%s.cognitiveservices.azure.com/language/:analyze-conversations?api-version=2022-10-01-preview",
			azureLanguageStudio.resource_name
		)

		return endpointUrl
		
	else
		local endpointUrl = string.format("%s?subscription-key=%s&verbose=%s&timezoneOffset=%s&q=",
			tostring(appPublishInfo.endpointUrl),
			tostring(inconcertInfo.subscriptionKey),
			tostring(inconcertInfo.verbose),
			tostring(minutes)
		)

		LogInfo("[LUIS_API:__MakeEndpointURL] AppId: '%s', EndpointURL: %s", appId, endpointUrl)

		return endpointUrl
	end
	
end

--[[
	De momento utilizamos el WEBAPI (**) para poder quitar los resources de la cuenta de Azure a la APP.
	Esto hasta que se pueda obtener el TOKEN en "azure.GetAzureResourceManagerToken", y ahí sí poder utilizar la método del API (*)
]]--
local function UnassignAzureAccountsResourceOfApp (self, appId, vcc)
	LogInfo("[LUIS_API:UnassignAzureAccountsResourceOfApp] Unassigning Azure Resources App: '%s', vcc: '%s'.", appId, vcc)
	local success, response
	if m_useWebAPI then
		success, response = self.apps.UnassignAzureAccountsResourceOfAppWebApi(appId, vcc) -- (**)
	else
		success, response = self.apps.UnassignAzureAccountsResourceOfApp(appId, vcc) -- (*)
	end
	if not success then
		LogWarning("[LUIS_API:UnassignAzureAccountsResourceOfApp] Unable to unassign Azure Resources to App '%s' of VCC: '%s'. Error: '%s'.", appId, vcc, response)
		return success, response
	end
	LogInfo("[LUIS_API:UnassignAzureAccountsResourceOfApp] Unassigned Azure Resources to App: '%s' VCC '%s'. Response: '%s'.", appId, vcc, response)
	return success, response
end

--[[
	De momento utilizamos el WEBAPI (**) para poder asignar los resources de la cuenta de Azure a la APP.
	Esto hasta que se pueda obtener el TOKEN en "azure.GetAzureResourceManagerToken", y ahí sí poder utilizar la método del API (*)
]]--
local function AssignAzureAccountsResourceToApp (self, appId, vcc)
	LogInfo("[LUIS_API:AssignAzureAccountsResourceToApp] Assigning Azure Resources to App: '%s', vcc: '%s'.", appId, vcc)
	local success, response
	if m_useWebAPI then
		success, response = self.apps.AssignAzureAccountsResourceToAppWebApi(appId, vcc) -- (**)
	else
		success, response = self.apps.AssignAzureAccountsResourceToApp(appId, vcc) -- (*)
	end
	if not success then
		LogWarning("[LUIS_API:AssignAzureAccountsResourceToApp] Unable to assign Azure Resources to App '%s' inf VCC: '%s'. Error: '%s'.", appId, vcc, response)
		return success, response
	end
	LogInfo("[LUIS_API:AssignAzureAccountsResourceToApp] Assigned Azure Accounts Resources to App: '%s' VCC '%s'. Response: '%s'.", appId, vcc, response)
	return success, response
end

--[[
]]--
function PublishApp (self, appId, info, vcc)
	assert(type(appId) == "string")
	assert(type(info) == "table")
	--assert(type(info.subscriptionKey) == "string") -- esto no es necesario en la nueva versión
	assert(type(info.timezone) == "string")
	assert(type(info.language) == "string")
	assert(type(vcc) == "string")

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, info.appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	if api_version == "2022-10-01-preview" then

		LogInfo("[LUIS_API:PublishApp] No requiero validar cuenta de azure")

	else

		assert(type(info.subscriptionKey) == "string")

		info.versionId = MakeTaskVersion(info.versionId)

		-- 1) Antes de publicar, siempre tengo que QUITAR la Azure Account Resources PAGA (Subscriptions keys) de la App,
		--    Esto sin importar, que el tipo de publicación (DEMO o PAGA), ya que la subkey paga tengo que quitarla previamente.
		UnassignAzureAccountsResourceOfApp(self, appId, vcc)

		local demoString = ""
		local hasConfiguredDemo, demoSubscription = HasConfiguredDemoSubscriptionsInVcc(vcc)
		if not hasConfiguredDemo or (demoSubscription.subscriptionKey or ""):lower() ~= info.subscriptionKey:lower() then
			-- 2) Tengo que asignar la Azure Account Resources (Subscriptions keys) a la App
			local success, response = AssignAzureAccountsResourceToApp(self, appId, vcc)
			if not success then
				return success, response
			end
		else
			demoString = "DEMO"
		end
		
	end

	-- 3) Publico la App
	local success, encodedPublishResponse = self.apps.PublishApp(appId, info, vcc, api_version) 
	if not success then
		return success, encodedPublishResponse
	end

	LogInfo("[LUIS_API:PublishApp] Publish %s data: '%s'", demoString, encodedPublishResponse)

	local success, encodedAppInfo = self:GetApp(appId, vcc, api_version) 
	if not success then
		return success, encodedAppInfo
	end
	LogInfo("[LUIS_API:PublishApp] %s encodedAppInfo: '%s'", demoString, encodedAppInfo)

	-- 4) Almaceno información de la publicación en Cassandra.
	local currentPublish = json.decode(encodedPublishResponse)
	local appInfo = json.decode(encodedAppInfo)
	appInfo.inconcertInfo = {
		subscriptionKey = info.subscriptionKey,
		originalEndpointUrl = currentPublish.endpointUrl,
		verbose = info.verbose,
		timezone = info.timezone,
		language = info.language,
		api_version = api_version
	}

	if api_version == "2022-10-01-preview" then
	
		local subscription_key_1 = ""

		local vccAzureLanguageStudioAccounts = luis_models.VCCs.GetAzureLanguageStudioAccountsMsCognitiveServices(vcc)
		local totalVCCAzureLanguageStudioAccounts = utils.GetRowCount(vccAzureLanguageStudioAccounts)
		local azureLanguageStudio = {}
		if totalVCCAzureLanguageStudioAccounts > 0 then
			local firstKey = next(vccAzureLanguageStudioAccounts)
			azureLanguageStudio = vccAzureLanguageStudioAccounts[firstKey]
			subscription_key_1 = azureLanguageStudio.subscription_key_1
		end

		appInfo.inconcertInfo.subscriptionKey = subscription_key_1		
	end

	appInfo.inconcertInfo.endpointUrl = __MakeEndpointURL(appId, currentPublish, info, api_version, vcc) 

	-- Lista de Columns que no se deben actualizar en cassandra.
	--  Esto es así, ya que al obtener Info de la App, desde el API de LUIS, el nombre y la descripcion, es diferente al que se ha almacenado en CASSANDRA.
	--  El nombre en la BD de LUIS contiene el @vcc y el Cluster Id, y la descripción contiene el número de Build de i6 instalado.
	local excludeFields = {
		name = true,
		description = true
	}
	luis_models.VCCs.UpdateApplicationMsCognitiveServices(vcc, appId, appInfo, nil, excludeFields)

	return success, encodedPublishResponse
end

--[[
	**********************************************
	ATENCION!!!
	-----------
		Esta Funcion NO debe invocarse, sin que previamente se haya realizado un TRAIN (POST) de la APP.
		Ya que sino los scores de los utterances NO cambian.
		Ref #8900

		https://westus.dev.cognitive.microsoft.com/docs/services/5890b47c39e2bb17b84a55ff/operations/5890b47c39e2bb052c5b9c46
			train - Get version training status
			Gets the training status of all models (intents and entities) for the specified LUIS app. You must call the train API to train the LUIS app before you call this API to get training status.

			Select the testing console in the region where you created your resource:
	**********************************************
]]--
function GetTrainApp (self, appId, versionId)
	assert(type(appId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.GetTrainApp(appId, versionId)
end

--[[
]]--
function TrainApp (self, info, vcc) 
	assert(type(info) == "table")
	assert(type(vcc) == "string")

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, info.appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"
	
	info.versionId = MakeTaskVersion(info.versionId)
	return self.apps.TrainApp(info, vcc, api_version)
end

function GetTrainStatus (self, info, vcc, operationLocation) 
	assert(type(info) == "table")
	assert(type(vcc) == "string")
	assert(type(operationLocation) == "string")

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, info.appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"
	
	return self.apps.GetTrainStatus(info, vcc, api_version, operationLocation)
end

--[[
]]--
function GetAppVersion (self, appId, versionId, vcc) 
	assert(type(appId) == "string")
	assert(type(vcc) == "string")

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	return self.apps.GetAppVersion(appId, versionId, vcc, api_version)

end

--[[
]]--
function LoadAppVersions (self, appId, paginated, vcc) 
	assert(type(appId) == "string")
	assert(type(vcc) == "string")
	
	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	return self.apps.LoadAppVersions(appId, paginated, vcc, api_version)

end

--[[
]]--
function ExportApp (self, appId, versionId, exportFormat)
	assert(type(appId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.ExportApp(appId, versionId, exportFormat)
end

--[[
]]--
function ImportApp (self, vcc, appData, optionalName)
	assert(type(appData) == "table")
	assert(type(vcc) == "string")
	appData.__occName = MakeAppNameForVcc(appData.name, vcc)
	appData.__occDescription = MakeAppDescriptionForVcc(appData.description)
	local success, response = self.apps.ImportApp(appData, optionalName)
	if not success then
		return false, response
	end
	-- Elimino caracteres del Id y actualiza la info de la App importada en el VCC.
	local newId = utils.SanitizeId(response)
	appData.id = newId
	appData.activeVersion = appData.activeVersion or m_defaultAppVersion
	luis_models.VCCs.UpdateApplicationMsCognitiveServices(vcc, newId, appData)
	return success, response
end

--[[
]]--
function LoadAppCultures (self, vcc, api_version)  
	assert(type(vcc) == "string")
	return self.apps.LoadAppCultures(vcc, api_version)
end

function LoadAppCulturesForIntents (self, vcc)
	assert(type(vcc) == "string")
	return self.apps.LoadAppCulturesForIntents(vcc)
end

function LoadAvailableApiVersions (self, vcc)  
	assert(type(vcc) == "string")

	local vccAzureLanguageStudioAccounts, totalVCCAzureLanguageStudioAccounts = LoadVCCAzureLanguageStudioAccount(vcc)
	local response = {}

	for name, module in pairs(API_ALL_VERSIONS_LUIS_BASE_URIS) do

		if name ~= "DEFAULT" then
			if module.REQUIRE_AZURE_LANGUAGE_STUDIO_ACCOUNT then
				if totalVCCAzureLanguageStudioAccounts > 0 then
					table.insert(response, {
						code = name,
						name = name,
						allow_news = module.ALLOW_NEW_APPLICATIONS
					})
				end
			else
				table.insert(response, {
					code = name,
					name = name,
					allow_news = module.ALLOW_NEW_APPLICATIONS
				})
			end
		end

	end

	return true, response

end

--[[
]]--
function DeleteAppSuggestedUtterance (self, appId, utteranceName, versionId)
	assert(type(appId) == "string")
	assert(type(utteranceName) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.DeleteAppSuggestedUtterance(appId, utteranceName, versionId)
end

-- *******************************************************************************
-- API INTENTS
-- *******************************************************************************

--[[
]]--
function LoadAllAppIntents (self, appId, versionId, paginated, vcc) 

	assert(type(appId) == "string")
	assert(type(vcc) == "string")

	versionId = MakeTaskVersion(versionId)

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	return self.apps.LoadAllAppIntents(appId, versionId, paginated, vcc, api_version)

end

--[[
]]--
function GetAppIntent (self, appId, intentId, versionId)
	assert(type(appId) == "string")
	assert(type(intentId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.GetAppIntent(appId, intentId, versionId)
end

--[[
]]--
function CreateAppIntent (self, info, vcc) 
	assert(type(info) == "table")
	assert(type(vcc) == "string")

	info.versionId = MakeTaskVersion(info.versionId)

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, info.appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	return self.apps.CreateAppIntent(info, vcc, api_version)
end

--[[
]]--
function RenameAppIntent (self, info)
	assert(type(info) == "table")
	info.versionId = MakeTaskVersion(info.versionId)
	return self.apps.RenameAppIntent(info)
end

--[[
]]--
function DeleteAppIntent (self, appId, intentId, versionId, vcc) 
	assert(type(appId) == "string")
	assert(type(intentId) == "string")
	assert(type(vcc) == "string")

	versionId = MakeTaskVersion(versionId)

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	return self.apps.DeleteAppIntent(appId, intentId, versionId, vcc, api_version)
end

--[[
]]--
function LoadPagingUtterancesForApp (self, appId, versionId, paginated)
	assert(type(appId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.LoadPagingUtterancesForApp(appId, versionId, paginated)
end

--[[
]]--
function LoadAllUtterancesForApp (self, appId, versionId, noEncodeResult)
	assert(type(appId) == "string")
	versionId = MakeTaskVersion(versionId)

	local allUtterances = {}
	local paginated = {page = 0, rows_per_page = 100}

	while true do
		LogInfo("[LUIS_API:LoadAllUtterancesForApp] Loaded Utterances for in intent: '%s', app: '%s', version: '%s'. Page: '%d', Rows Per Page: '%d'.", intentName, appId, versionId, paginated.page, paginated.rows_per_page)

		local ok, response = LoadPagingUtterancesForApp(self, appId, versionId, paginated)
		if not ok then
			return false, response
		end

		local encodedPagingUtterances = response
		--LogDebug("[LUIS_API:LoadAllUtterancesForApp] Loaded Utterances for in intent: '%s', app: '%s', version: '%s'. Utterances: %s", intentName, appId, versionId, encodedPagingUtterances)
		local ok, pagingUtterances = pcall(json.decode, encodedPagingUtterances)
		if not ok then
			return false, {code = 400, reason = "Decode utterances error"}
		end

		local countPagingUtterances = #pagingUtterances
		--LogDebug("--> countPagingUtterances: %d", countPagingUtterances)

		-- Si la pagina no tiene registros me voy
		if countPagingUtterances == 0 then
			--LogDebug("-a-> La pagina no tiene utterances")
			break
		end

		-- Inserto los rows de la pagina, en la tabla que contiene los rows de todas las paginas
		for _, utterance in ipairs(pagingUtterances) do
			table.insert(allUtterances, utterance)
		end

		-- Si la cantidad de rows de la pagina es menos al maximo total por pagina, me voy porque es la ultima pagina.
		if countPagingUtterances < paginated.rows_per_page then
			--LogDebug("-b-> Es la ultima pagina")
			break
		end
		-- incremento en 1 para pedir la siguiente pagina
		paginated.page = paginated.page + 1
	end

	if noEncodeResult then
		return true, allUtterances
	end

	return true, json.encode(allUtterances)
end

--[[
	Esta funcion obtiene contra el API "cognitive" todos los Utterances para una App determinada.
	Luego de obtenerlos los filtra según el intentName
]]--
function LoadUtterancesByAppIntent (self, appId, intentName, versionId, vcc) 
	assert(type(appId) == "string")
	assert(type(intentName) == "string")
	assert(type(vcc) == "string")

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	if api_version == "2.0" then
		
		-- Obtengo contra el API "cognitive" todos los Utterances de la app.
		local success, response = LoadAllUtterancesForApp(self, appId, versionId, true)
		if not success then
			return success, response
		end

		-- Ahora filtro por el Intent
		local CanAdd = function(utterance)
			if utterance.intentLabel == intentName then
				return true
			end
			return false
		end

		local appUtterances = response
		local intentUtterances = {}
		for _, utterance in ipairs(appUtterances) do
			if CanAdd(utterance) then
				table.insert(intentUtterances, utterance)
			end
		end

		local ok, encodedIntentUtterances = pcall(json.encode, intentUtterances)
		if not ok then
			return false, {code = 400, reason = "Encode utterances error"}
		end

		return true, encodedIntentUtterances

	else
		return self.apps.LoadUtterancesByAppIntent(appId, intentName, versionId, vcc, api_version)
	end
	
end

--[[
]]--
function LoadSuggestedUtterancesByAppIntent (self, appId, versionId, intentId, vcc) 
	assert(type(appId) == "string")
	assert(type(intentId) == "string")
	assert(type(vcc) == "string")
	versionId = MakeTaskVersion(versionId)

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	if api_version == "2.0" then
		return self.apps.LoadSuggestedUtterancesByAppIntent(appId, intentId, versionId, vcc, api_version)
	else
		return true, {}
	end

	
end

--[[

]]--
function GetUtteranceByAppIntent (self, appId, intentName, utteranceId, versionId, vcc)
	assert(type(appId) == "string")
	assert(type(intentName) == "string")
	--assert(type(utteranceId) == "number") : en la nueva versión es un texto
	assert(type(vcc) == "string")

	LogInfo("[LUIS_API:GetUtteranceByAppIntent] Find Utterance: '%s' in intent: '%s', app: '%s', version: '%s'.", utteranceId, intentName, appId, versionId)

	local success = false
	local response = {}
	local utterance = {}

	local retry = 0
	local TOTAL_RETRIES = 10
	local TIMEOUT_SEC = 3

	while retry < TOTAL_RETRIES do
		retry = retry + 1

		success, encodedIntentUtterances = LoadUtterancesByAppIntent(self, appId, intentName, versionId, vcc)
		if not success then
			return success, encodedIntentUtterances
		end

		local utterances = json.decode(encodedIntentUtterances)
		
		for _, utt in ipairs(utterances) do
			if utt.id == utteranceId then
				utterance = utt
				break
			end
		end

		if utterance then
			return true, utterance
		end

	end

	if not utterance then
		LogDebug("[LUIS_API:GetUtteranceByAppIntent] Utterance: '%s' not found.", utteranceId)
		return false, {code = 404}
	end

end

--[[
]]--
function AddUtteranceAppIntent (self, info, vcc) 
	assert(type(info) == "table")
	assert(type(vcc) == "string")

	LogInfo("AddUtteranceAppIntent: %s", json.encode(info))
	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, info.appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	info.versionId = MakeTaskVersion(info.versionId)
	return self.apps.AddUtteranceAppIntent(info, vcc, api_version)
end

--[[
]]--
function DeleteAppUtterance (self, appId, utteranceId, versionId, vcc)
	assert(type(appId) == "string")
	assert(type(utteranceId) == "string")
	versionId = MakeTaskVersion(versionId)

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"
	
	return self.apps.DeleteAppUtterance(appId, utteranceId, versionId, vcc, api_version)
end

-- *******************************************************************************
-- API ENTITIES SIMPLE / MACHINE LEARNED
-- *******************************************************************************

--[[
]]--
function LoadAllAppEntities (self, appId, versionId, encodedPaginated, vcc) 
	assert(type(appId) == "string")
	assert(type(vcc) == "string")

	versionId = MakeTaskVersion(versionId)

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	if api_version == "2.0" then
		return self.apps.LoadAllAppEntitiesSimple(appId, versionId, encodedPaginated, vcc, api_version)
	end
	return self.apps.LoadAllAppEntitiesMachineLearned(appId, versionId, encodedPaginated, vcc, api_version)

end

_M.LoadAllAppEntitiesSimple = LoadAllAppEntities
_M.LoadAllAppEntitiesMachineLearned = LoadAllAppEntities

--[[
]]--
function GetAppEntity (self, appId, entityId, versionId, api_version)
	assert(type(appId) == "string")
	assert(type(entityId) == "string")
	versionId = MakeTaskVersion(versionId)
	
	if api_version == "" or api_version == nil then
		api_version = "2.0"
	end

	if api_version == "2.0" then
		return self.apps.GetAppEntitySimple(appId, entityId, versionId)
	end
	return self.apps.GetAppEntityMachineLearned(appId, entityId, versionId)
end

_M.GetAppEntitySimple = GetAppEntity
_M.GetAppEntityMachineLearned = GetAppEntity

--[[
]]--
function CreateAppEntity (self, info, vcc) 
	assert(type(info) == "table")
	assert(type(vcc) == "string")

	info.versionId = MakeTaskVersion(info.versionId)

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, info.appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	if api_version == "2.0" then
		return self.apps.CreateAppEntitySimple(info, vcc, api_version)
	end
	return self.apps.CreateAppEntityMachineLearned(info, vcc, api_version)
end

_M.CreateAppEntitySimple = CreateAppEntity
_M.CreateAppEntityMachineLearned = CreateAppEntity

--[[
]]--
function RenameAppEntity (self, info, versionId, api_version)
	assert(type(info) == "table")
	info.versionId = MakeTaskVersion(info.versionId)
	
	if api_version == "" or api_version == nil then
		api_version = "2.0"
	end

	if api_version == "2.0" then
		return self.apps.RenameAppEntitySimple(info)
	end
	return self.apps.RenameAppEntityMachineLearned(info)
end

_M.RenameAppEntitySimple = RenameAppEntity
_M.RenameAppEntityMachineLearned = RenameAppEntity

--[[
]]--
function DeleteAppEntity (self, appId, entityId, versionId, vcc) 
	assert(type(appId) == "string")
	assert(type(entityId) == "string")
	assert(type(vcc) == "string")

	versionId = MakeTaskVersion(versionId)
	
	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	if api_version == "2.0" then
		return self.apps.DeleteAppEntitySimple(appId, entityId, versionId, vcc, api_version)
	end
	return self.apps.DeleteAppEntityMachineLearned(appId, entityId, versionId, vcc, api_version)
end

_M.DeleteAppEntitySimple = DeleteAppEntity
_M.DeleteAppEntityMachineLearned = DeleteAppEntity

--[[
]]--
function CreateAppEntityChildMachineLearned (self, info)
	assert(type(info) == "table")
	info.versionId = MakeTaskVersion(info.versionId)
	return self.apps.CreateAppEntityChild(info)
end

-- *******************************************************************************
-- API ENTITIES COMPOSITE
-- *******************************************************************************

--[[
]]--
function LoadAllAppEntitiesComposite (self, appId, versionId, encodedPaginated)
	assert(type(appId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.LoadAllAppEntities(appId, versionId, encodedPaginated)
end

--[[
]]--
function GetAppEntityComposite (self, appId, entityId, versionId)
	assert(type(appId) == "string")
	assert(type(entityId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.GetAppEntity(appId, entityId, versionId)
end

--[[
]]--
function CreateAppEntityComposite (self, info)
	assert(type(info) == "table")
	info.versionId = MakeTaskVersion(info.versionId)
	return self.apps.CreateAppEntity(info)
end

--[[
]]--
function RenameAppEntityComposite (self, info)
	assert(type(info) == "table")
	info.versionId = MakeTaskVersion(info.versionId)
	return self.apps.RenameAppEntity(info)
end

--[[
]]--
function DeleteAppEntityComposite (self, appId, entityId, versionId)
	assert(type(appId) == "string")
	assert(type(entityId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.DeleteAppEntity(appId, entityId, versionId)
end

-- *******************************************************************************
-- API ENTITIES HIERARCHICAL
-- *******************************************************************************

--[[
]]--
function LoadAllAppEntitiesHierarchical (self, appId, versionId, encodedPaginated)
	assert(type(appId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.LoadAllAppEntities(appId, versionId, encodedPaginated)
end

--[[
]]--
function GetAppEntityHierarchical (self, appId, entityId, versionId)
	assert(type(appId) == "string")
	assert(type(entityId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.GetAppEntity(appId, entityId, versionId)
end

--[[
]]--
function CreateAppEntityHierarchical (self, info)
	assert(type(info) == "table")
	info.versionId = MakeTaskVersion(info.versionId)
	return self.apps.CreateAppEntity(info)
end

--[[
]]--
function RenameAppEntityHierarchical (self, info)
	assert(type(info) == "table")
	info.versionId = MakeTaskVersion(info.versionId)
	return self.apps.RenameAppEntity(info)
end

--[[
]]--
function DeleteAppEntityHierarchical (self, appId, entityId, versionId)
	assert(type(appId) == "string")
	assert(type(entityId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.DeleteAppEntity(appId, entityId, versionId)
end

-- *******************************************************************************
-- API ENTITIES PREBUILT
-- *******************************************************************************

--[[
]]--
function LoadAllAppEntitiesPrebuilt (self, appId, versionId, encodedPaginated)
	assert(type(appId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.LoadAllAppEntities(appId, versionId, encodedPaginated)
end

--[[
]]--
function AddAppEntityPrebuilt (self, info)
	assert(type(info) == "table")
	info.versionId = MakeTaskVersion(info.versionId)
	return self.apps.CreateAppEntity(info)
end

--[[
]]--
function DeleteAppEntityPrebuilt (self, appId, entityId, versionId)
	assert(type(appId) == "string")
	assert(type(entityId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.DeleteAppEntity(appId, entityId, versionId)
end

--[[
]]--
function LoadAvailablePrebuiltEntityList (self, appId, versionId, vcc)
	assert(type(appId) == "string")
	versionId = MakeTaskVersion(versionId)

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	return self.apps.LoadAvailablePrebuiltEntityList(appId, versionId, vcc, api_version)
end

-- *******************************************************************************
-- API SUBSCRIPTIONS
-- *******************************************************************************

--[[
]]--
function HasConfiguredDemoSubscriptionsInVcc (vcc)
	assert(vcc)
	local isDemo, demoSubscription = luis_models.VCCs.HasConfiguredDemoSubscriptionsInVcc(vcc)
	return isDemo, demoSubscription
end

--[[
]]--
function LoadVCCSubscriptions (vcc)
	assert(vcc)
	local subscriptionsKeys = luis_models.VCCs.GetSubscriptionsKeysMsCognitiveServices(vcc)
	local total = utils.GetRowCount(subscriptionsKeys)
	return subscriptionsKeys, total
end

--[[
]]--
function LoadVCCSubscriptionsWithoutDemo (vcc)
	assert(vcc)
	local hasConfiguredDemo, demoSubscription = HasConfiguredDemoSubscriptionsInVcc(vcc)
	local subscriptionsKeys = luis_models.VCCs.GetSubscriptionsKeysMsCognitiveServices(vcc)
	local total = utils.GetRowCount(subscriptionsKeys)

	--print("LoadVCCSubscriptionsWithoutDemo - VCC " .. vcc .. " total subscriptionsKeys: " .. tostring(total) .. " hasConfiguredDemo: " .. tostring(hasConfiguredDemo))

	if total == 0 then
		--print("LoadVCCSubscriptionsWithoutDemo 1")
		return {}, 0
	end
	if total == 1 and hasConfiguredDemo then
		--print("LoadVCCSubscriptionsWithoutDemo 2")
		return {}, 0
	end
	local subscriptionsKeysWithDemo = {}
	for key, info in pairs(subscriptionsKeys or {}) do
		if not demoSubscription or (demoSubscription.subscriptionKey or ""):lower() ~= info.subscriptionKey:lower() then
			subscriptionsKeysWithDemo[key] = info
		end
	end
	local totalWithDemo = utils.GetRowCount(subscriptionsKeysWithDemo)
	--print("LoadVCCSubscriptionsWithoutDemo 3")
	return subscriptionsKeysWithDemo, totalWithDemo
end

--[[
]]--
function LoadVCCsBySubscription (key)
	assert(key)
	local vccs = luis_models.VCCs.LoadVCCsBySubscriptionKey(key)
	local total = utils.GetRowCount(vccs)
	return vccs, total
end

local function ValidateVCC (vcc)
	assert(type(vcc) == "string")
	if vcc:lower() == "system" then
		return false, {code = 404, reason = "Unable to assign/unassign subscription key to system vcc", errType = "inconcert"}
	end
	local vccExists = luis_models.VCCs.VCCExists(vcc)
	if not vccExists then
		return false, {code = 404, reason = "VCC does not exist", errType = "inconcert"}
	end
	return true
end

--[[
]]--
function AssignSubscriptionKeyToVCC (info)
	assert(type(info) == "table")
	assert(type(info.vcc) == "string")
	assert(type(info.subscriptionKey) == "string")

	local success, response = ValidateVCC(info.vcc)
	if not success then
		return false, response
	end

	local hasConfiguredDemo, demoSubscription = HasConfiguredDemoSubscriptionsInVcc(info.vcc)
	--LogDebug("1 - AssignSubscriptionKeyToVCC -> hasConfiguredDemo " .. tostring(hasConfiguredDemo))
	--LogDebug("2 - AssignSubscriptionKeyToVCC -> demoSubscription.subscriptionKey " .. tostring(demoSubscription and demoSubscription.subscriptionKey or ""))
	--LogDebug("3 - AssignSubscriptionKeyToVCC -> info.subscriptionKey " .. tostring(info.subscriptionKey))

	if hasConfiguredDemo and (demoSubscription.subscriptionKey or ""):lower() == info.subscriptionKey:lower() then
	--LogDebug("4 - AssignSubscriptionKeyToVCC")
		return false, {code = 400, reason = "The key is already entered in the VCC in the DEMO list.", errType = "inconcert", demoSubscription = demoSubscription}
	end
	--LogDebug("5 - AssignSubscriptionKeyToVCC")

	-- Asigna al vcc el subscription key
	---- {"subscriptionRegion":"westus","subscriptionName":"Subscription key","subscriptionKey":"d24518b086a248c4ae64afd6e3eb475c"}
	local currentSubscription = {
		subscriptionKey = info.subscriptionKey,
		subscriptionName = info.subscriptionName or info.subscriptionKey,
		subscriptionRegion = info.westus or "westus",
		isDemo = false
	}
	luis_models.VCCs.AssignSubscriptionKeyToVCC(info.vcc, info.subscriptionKey, currentSubscription)
	return true, {result = "OK"}
end

--[[
]]--
function AssignDemoSubscriptionKeyToVCC (info)
	assert(type(info) == "table")
	assert(type(info.vcc) == "string")
	assert(type(info.subscriptionKey) == "string")

	local success, response = ValidateVCC(info.vcc)
	if not success then
		return false, response
	end

	local hasConfiguredDemo, demoSubscription = HasConfiguredDemoSubscriptionsInVcc(info.vcc)
	if hasConfiguredDemo then
		return false, {code = 400, reason = "VCC has already configured DEMO Subscription Key.", errType = "inconcert", demoSubscription = demoSubscription}
	end

	local subscriptionsKeysWithDemo, totalWithDemo = LoadVCCSubscriptionsWithoutDemo(info.vcc)
	if totalWithDemo > 0 then
		for key, currentSubscription in pairs(subscriptionsKeysWithDemo or {}) do
			if (currentSubscription.subscriptionKey or ""):lower() == info.subscriptionKey:lower() then
				return false, {code = 400, reason = "The key is already entered in the VCC in the NON-DEMO list.", errType = "inconcert", currentSubscription = currentSubscription}
			end
		end
	end

	-- Asigna al vcc el subscription key
	---- {"subscriptionRegion":"westus","subscriptionName":"Subscription key","subscriptionKey":"d24518b086a248c4ae64afd6e3eb475c"}
	local currentSubscription = {
		subscriptionKey = info.subscriptionKey,
		subscriptionName = "DEMO",
		subscriptionRegion = info.westus or "westus",
		isDemo = true
	}

	luis_models.VCCs.AssignSubscriptionKeyToVCC(info.vcc, info.subscriptionKey, currentSubscription)
	return true, {result = "OK"}
end

local function __IsAssignedSubscriptionKeyInApp (app, subscriptionKey)
	assert(type(app) == "table")
	assert(type(subscriptionKey) == "string")

	--LogDebug("0.5 __IsAssignedSubscriptionKeyInApp app.id: " .. app.id .. " - subscriptionKey: " .. subscriptionKey)

	local isAssigned = false
	if app.inconcertInfo then
		--LogDebug("1 __IsAssignedSubscriptionKeyInApp app.id: " .. app.id .. " - app.inconcertInfo.subscriptionKey: " .. app.inconcertInfo.subscriptionKey)
		if subscriptionKey:lower() == (app.inconcertInfo.subscriptionKey or ""):lower() then
			isAssigned = true
		end
	end

	--LogDebug("2 __IsAssignedSubscriptionKeyInApp app.id: " .. app.id .. " - subscriptionKey: " .. subscriptionKey .. " - isAssigned: " .. tostring(isAssigned))
	return isAssigned
end

--[[
]]--
function IsAssignedSubscriptionKeyInSomeApp (vcc, subscriptionKey)
	assert(type(vcc) == "string")
	assert(type(subscriptionKey) == "string")

	local isAssigned = false
	local appsWithSubscriptionsKeys = {}
	local vccApps = luis_models.VCCs.GetApplicationsMsCognitiveServices(vcc)

	for _, app in pairs(vccApps) do
		if __IsAssignedSubscriptionKeyInApp(app, subscriptionKey) then
			isAssigned = true
			table.insert(appsWithSubscriptionsKeys, app)
		end
	end

	if isAssigned then
		LogWarning("[LUIS_API:IsAssignedSubscriptionKeyInSomeApp] VCC: '%s', SubscriptionKey: '%s' is assigned on '%d' apps.", vcc, subscriptionKey, #appsWithSubscriptionsKeys)
	else
		LogInfo("[LUIS_API:IsAssignedSubscriptionKeyInSomeApp] VCC: '%s', SubscriptionKey: '%s' not is assigned in apps.", vcc, subscriptionKey)
	end

	return isAssigned, appsWithSubscriptionsKeys
end

--[[
]]--
function UnAssignSubscriptionKeyOfVCC (self, vcc, subscriptionKey)
	assert(type(vcc) == "string")
	assert(type(subscriptionKey) == "string")

	local success, response = ValidateVCC(vcc)
	if not success then
		return false, response
	end

	local _, totalVCCSubscriptionsKeys = LoadVCCSubscriptions(vcc)
	local updatedApps = {}

	if totalVCCSubscriptionsKeys == 0 then
	-- Este es el caso en el cual el VCC NO tiene subscriptions asignadas
		LogInfo("[LUIS_API:UnAssignSubscriptionKeyOfVCC] VCC " .. vcc .. " not has configured any Subscriptions Keys.")
		return false, {}

	else
	-- Este es el caso en el cual el VCC SI tiene subscriptions asignadas

		local hasConfiguredDemo, demoSubscription = HasConfiguredDemoSubscriptionsInVcc(vcc)

		-- Esta funcion valida si la App puede ser actualizada.
		local function CanUpdateApp (app)
			if __IsAssignedSubscriptionKeyInApp(app, subscriptionKey) then
				return true
			end
			return false
		end

		local function RemovePublishAppInfo (app, subscriptionKey)
			local success = luis_models.VCCs.RemoveApplicationPublishAppInfo(vcc, app.id)
			if success then
				local isDemo = hasConfiguredDemo and (demoSubscription.subscriptionKey or ""):lower() == subscriptionKey:lower()
				LogDebug("[LUIS_API:UnAssignSubscriptionKeyOfVCC] subscriptionKey: '%s' isDemo: '%s'.", subscriptionKey, isDemo)
				if not isDemo then
					UnassignAzureAccountsResourceOfApp(self, app.id, vcc)
				end
			end
			return success
		end

		local isOk = true
		local err
		local vccApps = luis_models.VCCs.GetApplicationsMsCognitiveServices(vcc)

		for _, app in pairs(vccApps) do
			if CanUpdateApp(app) then
				LogInfo("[LUIS_API:UnAssignSubscriptionKeyOfVCC] Updating Publish Info in App: '%s' of VCC: '%s', for SubscriptionKey: '%s'.", app.id, vcc, subscriptionKey)
				local success = RemovePublishAppInfo(app, subscriptionKey)
				if success then
					LogInfo("[LUIS_API:UnAssignSubscriptionKeyOfVCC] Success Updated Publish Info in App: '%s' of VCC: '%s', for assigned to SubscriptionKey: '%s'.", app.id, vcc, subscriptionKey)
					table.insert(updatedApps, app)
				else
					LogError("[LUIS_API:UnAssignSubscriptionKeyOfVCC] Error Updating Publish Info in App: '%s' of VCC: '%s', for assigned to SubscriptionKey: '%s'.", app.id, vcc, subscriptionKey)
					isOk = false
					err = {}
					break
				end
			end
		end

		if not isOk then
		-- Si falla la actualizacion de la App cancelo el Unsubscription, pero las apps que ya fueron actualizadas quedan asi.
			err.updatedApps = updatedApps
			return false, err
		end
	end
	--LogDebug("[LUIS_API:UnAssignSubscriptionKeyOfVCC] Updated Apps: %s", json.encode(updatedApps))
	-- Quita del vcc la subscription key
	luis_models.VCCs.UnAssignSubscriptionKeyOfVCC(vcc, subscriptionKey)
	return true, {result = "OK", updatedApps = updatedApps}
end


-- *******************************************************************************
-- API FEATURES
-- *******************************************************************************

--[[
]]--
function LoadAllAppFeatures (self, appId, versionId, encodedPaginated, vcc) 
	assert(type(appId) == "string")
	versionId = MakeTaskVersion(versionId)

	local app = luis_models.VCCs.GetApplicationMsCognitiveService(vcc, appId)

	if app == nil then
		return false, {code = 400, reason = "App not found"}
	end

	local app_obj = json.decode(app)
	local api_version = app_obj.api_version or "2.0"

	return self.apps.LoadAllAppFeatures(appId, versionId, encodedPaginated, vcc, api_version)
end

--[[
]]--
function LoadAllAppFeaturesPhraselist (self, appId, versionId, encodedPaginated)
	assert(type(appId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.LoadAllAppFeaturesPhraselist(appId, versionId, encodedPaginated)
end

--[[
]]--
function CreatePhraselist (self, info)
	assert(type(info) == "table")
	info.versionId = MakeTaskVersion(info.versionId)
	return self.apps.CreatePhraselist(info)
end

--[[
]]--
function UpdatePhraselist (self, info)
	assert(type(info) == "table")
	info.versionId = MakeTaskVersion(info.versionId)
	return self.apps.UpdatePhraselist(info)
end

--[[
]]--
function DeletePhraselist (self, appId, phraseListId, versionId)
	assert(type(appId) == "string")
	assert(type(phraseListId) == "string")
	versionId = MakeTaskVersion(versionId)
	return self.apps.DeletePhraselist(appId, phraseListId, versionId)
end

-- *******************************************************************************
-- API AZURE ACCOUNTS
-- *******************************************************************************

--[[
]]--
function LoadDefaultTenantAzureAccount (self)
	return self.apps.LoadDefaultTenantAzureAccount()
end

--[[
]]--
function RestoreDefaultTenantAzureAccountInVCC (self, vcc)
	assert(vcc)
	LogDebug("[LUIS_API:RestoreDefaultTenantAzureAccountInVCC] Restoring Default Tenant Azure Account In VCC '%s'.", vcc)

	local defaultTenantInfo = LoadDefaultTenantAzureAccount(self)
	local vccInfo = {
		vcc = vcc,
		tenantInfo = defaultTenantInfo
	}
	return AssignTenantAzureAccountToVCC(vccInfo)
end


--[[
]]--
function RestoreDefaultLanguageStudioAccountInVCC (self, vcc) 
	assert(vcc)
	LogDebug("[LUIS_API:RestoreDefaultLanguageStudioAccountInVCC] Restoring Default Azure Language Studio Account In VCC '%s'.", vcc)

	local vccInfo = {
		vcc = vcc,
		languageStudioInfo = {
			resource_name = "",
			azure_subscription_id = "",
			subscription_key_1 = "",
			subscription_key_2 = ""
		}
	}
	return AssignAzureLanguageStudioAccountToVCC(vccInfo)
end

--[[
]]--
function LoadVCCTenantAzureAccount (vcc)
	assert(vcc)
	local vccTenantAzureAccounts = luis_models.VCCs.GetTenantAzureAccountsMsCognitiveServices(vcc)
	local total = utils.GetRowCount(vccTenantAzureAccounts)
	return vccTenantAzureAccounts, total
end

--[[
]]--
function LoadTenantAndAzureAccountAndToken (self, vcc)
	assert(type(vcc) == "string")
	return self.apps.LoadAzureAccountAndToken(vcc)
end

--[[
]]--
function LoadAzureAccounts (self, info)
	assert(type(info) == "table")
	assert(type(info.tenant_id) == "string")
	assert(type(info.client_id) == "string")
	assert(type(info.client_secret) == "string")
	assert(type(info.resource) == "string")
	return self.apps.LoadAzureAccounts(info)
end

function AssignTenantAzureAccountToVCC (info)
	assert(type(info) == "table")
	assert(type(info.vcc) == "string")
	assert(type(info.tenantInfo) == "table")
	assert(type(info.tenantInfo.tenant_id) == "string")
	assert(type(info.tenantInfo.client_id) == "string")
	assert(type(info.tenantInfo.client_secret) == "string")
	assert(type(info.tenantInfo.resource) == "string")

	local success, response = ValidateVCC(info.vcc)
	if not success then
		return false, response
	end

	local vccTenantAzureAccounts, totalVCCTenantAzureAccounts = LoadVCCTenantAzureAccount(info.vcc)
	if totalVCCTenantAzureAccounts > 0 then
		local vccTenantId = next(vccTenantAzureAccounts)
		-- Quita del vcc la Tenant Azure Account
		luis_models.VCCs.UnAssignTenantAzureAccountOfVCC(info.vcc, vccTenantId)
	end
	-- Agrega al vcc la Tenant Azure Account
	luis_models.VCCs.AssignTenantAzureAccountToVCC(info.vcc, info.tenantInfo)
	return true, {result = "OK"}
end


function AssignAzureLanguageStudioAccountToVCC (info) 
	assert(type(info) == "table")
	assert(type(info.vcc) == "string")
	assert(type(info.languageStudioInfo) == "table")
	assert(type(info.languageStudioInfo.resource_name) == "string")
	assert(type(info.languageStudioInfo.azure_subscription_id) == "string")
	assert(type(info.languageStudioInfo.subscription_key_1) == "string")
	assert(type(info.languageStudioInfo.subscription_key_2) == "string")

	local success, response = ValidateVCC(info.vcc)
	if not success then
		return false, response
	end

	local vccAzureLanguageStudioAccounts, totalVCCAzureLanguageStudioAccounts = LoadVCCAzureLanguageStudioAccount(info.vcc)

	LogDebug("--> total " .. tostring(totalVCCAzureLanguageStudioAccounts))
	LogDebug("--> vccAzureLanguageStudioAccounts " .. json.encode(vccAzureLanguageStudioAccounts))

	if totalVCCAzureLanguageStudioAccounts > 0 then

		local vccLanguageStudioId = next(vccAzureLanguageStudioAccounts)

		LogDebug("[LUIS_API:AssignAzureLanguageStudioAccountToVCC] vccLanguageStudioId: '%s'.", vccLanguageStudioId)

		-- Quita del vcc la Tenant Azure Account
		luis_models.VCCs.UnAssignAzureLanguageStudioAccountOfVCC(info.vcc, vccLanguageStudioId)
	end

	if info.languageStudioInfo.resource_name ~= "" then
		-- Agrega al vcc la Tenant Azure Account
		luis_models.VCCs.AssignAzureLanguageStudioAccountToVCC(info.vcc, info.languageStudioInfo)
	end
	
	return true, {result = "OK"}
end


--[[
]]--
function UnAssignTenantAzureAccountOfVCC (vcc)
	assert(type(vcc) == "string")

	local success, response = ValidateVCC(vcc)
	if not success then
		return false, response
	end

	local vccTenantAzureAccounts, totalVCCTenantAzureAccounts = LoadVCCTenantAzureAccount(vcc)
	if totalVCCTenantAzureAccounts == 0 then
	-- Este es el caso en el cual el VCC NO tiene Tenant Azure Accounts asignadas
		LogInfo("[LUIS_API:UnAssignTenantAzureAccountOfVCC] VCC " .. vcc .. " not has configured any Tenant Azure Accounts.")
		return false, {code = 400, reason = "VCC not has configured any Tenant Azure Accounts.", errType = "inconcert"}
	end

	local vccTenantId = next(vccTenantAzureAccounts)
	-- Quita del vcc la Tenant Azure Account
	luis_models.VCCs.UnAssignTenantAzureAccountOfVCC(vcc, vccTenantId)

	return true, {result = "OK"}
end

--[[
]]--
function UnAssignAzureLanguageStudioAccountOfVCC (vcc) 
	assert(type(vcc) == "string")

	local success, response = ValidateVCC(vcc)
	if not success then
		return false, response
	end

	local vccAzureLanguageStudioAccounts, totalVCCAzureLanguageStudioAccounts = LoadVCCAzureLanguageStudioAccount(vcc)
	if totalVCCAzureLanguageStudioAccounts == 0 then
	-- Este es el caso en el cual el VCC NO tiene Tenant Azure Accounts asignadas
		LogInfo("[LUIS_API:UnAssignAzureLanguageStudioAccountOfVCC] VCC " .. vcc .. " not has configured any Azure Language Studio Accounts.")
		return false, {code = 400, reason = "VCC not has configured any Azure Language Studio Accounts.", errType = "inconcert"}
	end

	local vccLanguageStudioId = next(vccAzureLanguageStudioAccounts)
	-- Quita del vcc la Azure Language Studio Account
	luis_models.VCCs.UnAssignAzureLanguageStudioAccountOfVCC(vcc, vccLanguageStudioId)

	return true, {result = "OK"}
end

function LoadVCCAzureLanguageStudioAccount (vcc, vcc_id) 
	
	if type(vcc) == "table" then
		vcc = vcc_id
	end

	local vccAzureLanguageStudioAccounts = luis_models.VCCs.GetAzureLanguageStudioAccountsMsCognitiveServices(vcc)

	local total = utils.GetRowCount(vccAzureLanguageStudioAccounts)
	return vccAzureLanguageStudioAccounts, total
end

--[[
	Valida si los datos del Azure Account son validos.
	Ej:
		{"AzureSubscriptionId":"dc506e4a-ff4a-4178-9e18-1750b4247451","vcc":"borrame11","ResourceGroup":"inconcert_luis_grp","AccountName":"inconcert_luis"}
]]--
function IsValidAzureAccount (self, info)
	assert(type(info) == "table")
	assert(type(info.vcc) == "string")
	assert(type(info.AzureSubscriptionId) == "string")
	assert(type(info.AccountName) == "string")
	assert(type(info.ResourceGroup) == "string")

	local vccTenantAzureAccounts, totalVCCTenantAzureAccounts = LoadVCCTenantAzureAccount(info.vcc)
	if totalVCCTenantAzureAccounts == 0 then
		LogWarning("[LUIS_API:IsValidAzureAccount] VCC " .. info.vcc .. " not has configured any Tenant Azure Accounts.")
		return false, {code = 400, reason = "VCC not has configured any Tenant Azure Accounts.", errType = "inconcert"}
	end

	local vccTenantId, tenantInfo = next(vccTenantAzureAccounts)
	local success, response = self.apps.LoadAzureAccounts(tenantInfo)
	if not success then
		LogWarning("[LUIS_API:IsValidAzureAccount] VCC " .. info.vcc .. " not has configured any Tenant Azure Accounts.")
		return false, response
	end

	local encodedAzureAccounts = response
	local azureAccounts = json.decode(encodedAzureAccounts)

	local isValid = false
	local reason = "YOUR AZURE ACCOUNT INFORMATION IS INVALID"
	local result = {code = 400, reason = reason, errType = "inconcert"}

	for _, azureAccount in ipairs(azureAccounts) do
		if azureAccount.AzureSubscriptionId:lower() == info.AzureSubscriptionId:lower() and
				azureAccount.AccountName:lower() == info.AccountName:lower() and
				azureAccount.ResourceGroup:lower() == info.ResourceGroup:lower() then
			isValid = true
			result = {code = 200, reason = "Is Ok"}
			break
		end
	end

	if not isValid then
		LogWarning("[LUIS_API:IsValidAzureAccount] " .. result.reason)
	end

	return isValid, result
end

function AssignAzureAccountToVCC (self, info)
	assert(type(info) == "table")
	assert(type(info.vcc) == "string")
	assert(type(info.AzureSubscriptionId) == "string")
	assert(type(info.AccountName) == "string")
	assert(type(info.ResourceGroup) == "string")

	local success, response = ValidateVCC(info.vcc)
	if not success then
		return false, response
	end

	local success, response = IsValidAzureAccount(self, info)
	if not success then
		return false, response
	end

	luis_models.VCCs.AssignAzureAccountToVCC(info.vcc, info)
	return true, {result = "OK"}
end

--[[
]]--
function LoadVCCsByAzureAccount (self, key)
	assert(key)
	local vccs = luis_models.VCCs.LoadVCCsByAzureAccount(key)
	local total = utils.GetRowCount(vccs)
	return vccs, total
end

--[[
]]--
function LoadVCCAzureAccounts (vcc)
	assert(vcc)
	local azureAccounts = luis_models.VCCs.GetAzureAccountsMsCognitiveServices(vcc)
	local total = utils.GetRowCount(azureAccounts)
	return azureAccounts, total
end

--[[
]]--
function UnAssignAzureAccountOfVCC (vcc, azureAccountId)
	assert(type(vcc) == "string")
	assert(type(azureAccountId) == "string")

	local success, response = ValidateVCC(vcc)
	if not success then
		return false, response
	end

	local azureAccounts, totalVCCAzureAccounts = LoadVCCAzureAccounts(vcc)
	local deletedApps = {}

	if totalVCCAzureAccounts == 0 then
	-- Este es el caso en el cual el VCC NO tiene Azure Accounts asignadas
		LogInfo("[LUIS_API:UnAssignAzureAccountOfVCC] VCC " .. vcc .. " not has configured any Azure Accounts.")
		return false, {code = 400, reason = "VCC not has configured any Azure Accounts.", errType = "inconcert"}
	else
		if not azureAccounts[azureAccountId] then
			LogInfo("[LUIS_API:UnAssignAzureAccountOfVCC] Azure Acount " .. azureAccountId .. " not has assigned in VCC " .. vcc)
			return false, {code = 400, reason = "Azure Acount not has assigned in VCC.", errType = "inconcert"}
		end
	-- Este es el caso en el cual el VCC SI tiene Azure Accounts asignadas

		-- Esta funcion valida si la App puede ser eliminada.
		local function CanDeleteApp(app)
			-- TODO: Ver si hay que realizar alguna validacion !!!
			return true
		end

		local isOk = true
		local err
		--local vccApps = luis_models.VCCs.GetApplicationsMsCognitiveServices(vcc)

		--for _, app in pairs(vccApps) do
			-- if CanDeleteApp(app) then
				-- LogInfo("[LUIS_API:UnAssignAzureAccountOfVCC] Deleting App: '%s' of VCC: '%s'.", app.id, vcc)
				-- local success, response = DeleteApp(app.id, vcc)
				-- if success then
					-- LogInfo("[LUIS_API:UnAssignAzureAccountOfVCC] Success deleted App: '%s' of VCC: '%s'.", app.id, vcc)
					-- table.insert(deletedApps, app)
				-- else
					-- LogError("[LUIS_API:UnAssignAzureAccountOfVCC] Error deleting App: '%s' of VCC: '%s'.", app.id, vcc)
					-- isOk = false
					-- err = response
					-- break
				-- end
			-- end
		--end

		if not isOk then
		-- Si falla el delete de la App cancelo el Unsubscription, pero las apps que ya fueron eliminadas quedan asi.
			err.deletedApps = deletedApps
			err.errType = "inconcert" -- NO es exactamente un error de "inconcert", pero agrego el atributo para que el mismo pueda ser visualizado en el log del provisioningmanager
			return false, err
		end
	end

	-- Quita del vcc la Azure Account
	luis_models.VCCs.UnAssignAzureAccountOfVCC(vcc, azureAccountId)
	return true, {result = "OK", deletedApps = deletedApps}
end

function ExecuteApiLCU (self, endpointUrl, message, appId, subscriptionKey, language, verbose, timezone)

	assert(type(endpointUrl) == "string")
	assert(type(message) == "string")
	assert(type(appId) == "string")
	assert(type(subscriptionKey) == "string")
	assert(type(language) == "string")
	assert(type(verbose) == "boolean")
	assert(type(timezone) == "string")

	return self.apps.ExecuteApiLCU(endpointUrl, message, appId, subscriptionKey, language, verbose, timezone)
end

-----------------------------------
function new (self) -- la versión ya no llega como parámetro
	local apiName = "Luis"

	LogInfo("[LUIS_API:new] LUIS New Instance.")

	if not (_M.m_apiInstances) then
		_M.m_apiInstances = {}
	end

	if _M.m_apiInstances[apiName] then
		return _M.m_apiInstances[apiName]
	else
		_M.m_apiInstances[apiName] = _M.m_apiInstances[apiName] or {
			apiName = apiName
		}

		for n, v in pairs(_M) do
			if type(v) == "function" then
				--LogDebug("----------> _M.n = " .. tostring(n) .. " " .. type(v))
				_M.m_apiInstances[apiName][n] = v
			end
		end

	end

	InitApiVersionsModules(apiName) 
	SetVersionBaseURIs() 

	return _M.m_apiInstances[apiName]
end