require "LuaOrm.Date"
local utils = require "rasa.utils"

module(..., package.seeall)

local m_datasource

function GetName ()
	return "VCCs"
end

function GetDefinition (datasource)
	m_datasource = datasource
	return _M
end

function GetVccColumn (vcc, column, keyspace)
	assert(vcc)
	assert(column)
	return utils.GetColumnValue({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCs",
		key = vcc,
		column = column
	})
end

function VCCExists (vcc, keyspace)
	assert(vcc)
	return GetVccColumn(vcc, "Id", keyspace)
end

function LoadVCCs (columns, keyspace)
	return utils.GetAllColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCs",
		startKey = "",
		endKey = "",
		columns = columns,
		rowCount = 200000
	}) or {}
end

--[[
	APPLICATIONS -------------------------------------
]]--

function GetApplication (vcc, appId, keyspace)
	assert(vcc)
	assert(appId)
	vcc = vcc:lower()
	appId = appId:lower()
	--LogDebug("[VCCs:GetApplication] ------> vcc: '%s', appId: '%s'", vcc, appId)
	return utils.GetColumnValue({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaApplications",
		key = vcc,
		column = appId
	})
end

function GetApplications (vcc, keyspace)
	assert(vcc)
	vcc = vcc:lower()
	return utils.GetAllColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaApplications",
		key = vcc,
		columns = {}
	}) or {}
end

function GetApplicationsAllVCCs (keyspace)
	return utils.GetAllColumns {
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaApplications",
		startKey = "",
		endKey = "",
		rowCount = 200000
	}
end

local function SaveApplications (vcc, apps, keyspace)
	vcc = vcc:lower()
	local rows = {}
	rows[vcc] = apps
	utils.SaveRows({keyspace = keyspace or "Agent", columnFamily = "VCCRasaApplications", rows = rows})
end

function GetApplicationFilePart (vcc, appId, part, keyspace)
	assert(vcc)
	assert(appId)
	assert(part)

	local vccAppPartId = utils.MakeAppId(vcc, appId) .. ("::part_%d"):format(part)
	LogDebug("[VCCs:GetApplicationFilePart] --> vccAppPartId '%s'", vccAppPartId)

	local filePartInfo = utils.GetAllColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaApplicationsFilesParts",
		key = vccAppPartId,
		columns = {}
	})
	--LogDebug("[VCCs:GetApplicationFilePart] --> vccAppPartId '%s'\nfilePartInfo: %s", vccAppPartId, filePartInfo)
	return filePartInfo
end

function MakeContentFileFromParts (fileInfo, keyspace)
	assert(fileInfo)
	assert(fileInfo.vcc)
	assert(fileInfo.appId)

	--LogDebug("[VCCs:MakeContentFileFromParts] --> fileInfo '%s'", json.encode(fileInfo))

	local totalFileParts = tonumber(fileInfo.totalFileParts) or 0
	local contentModelFile
	local parts = {}

	if totalFileParts > 0 then
		local vcc = fileInfo.vcc
		local appId = fileInfo.appId
		--local vccAppId = utils.MakeAppId(vcc, appId)
		local i = 1

		while i <= totalFileParts do
			local filePartInfo = GetApplicationFilePart(vcc, appId, i, keyspace)
			--LogDebug("[VCCs:MakeContentFileFromParts] part '%s' filePartInfo: %s, blob", i, filePartInfo, (filePartInfo or {}).blob)
			if filePartInfo and filePartInfo.blob then
				table.insert(parts, filePartInfo.blob)
			end
			i = i + 1
		--	break
		end
	end

	if parts then
		contentModelFile = table.concat(parts)
	end

	--LogDebug("[VCCs:MakeContentFileFromParts] --> vccAppPartId '%s' parts: %s\ncontentModelFile: %s", utils.MakeAppId(fileInfo.vcc, fileInfo.appId), #parts, contentModelFile)

	return contentModelFile, parts
end

function GetApplicationFile (vcc, appId, withParts, keyspace)
	assert(vcc)
	assert(appId)

	local vccAppId = utils.MakeAppId(vcc, appId)
	local fileInfo = utils.GetAllColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaApplicationsFiles",
		key = vccAppId,
		columns = {}
	})
	--LogDebug("[VCCs:GetApplicationFile] --> vccAppId '%s'\nfileInfo: %s", vccAppId, fileInfo)
	if fileInfo and withParts then
		local contentModelFile, parts = MakeContentFileFromParts(fileInfo, keyspace)
		if contentModelFile then
			fileInfo.contentModelFile = contentModelFile
		end
	end

	return fileInfo
end

function UpdateApplicationFile (vcc, appId, fileInfo, keyspace)
	assert(vcc)
	assert(appId)
	assert(fileInfo)
	assert(type(fileInfo.contentModelFile) == "table")
	assert(#fileInfo.contentModelFile > 0)
	assert(fileInfo.modelFileName)

	local vccAppId = utils.MakeAppId(vcc, appId)
	local totalFileParts = #fileInfo.contentModelFile

	-- LogDebug("[VCCs:UpdateApplicationFile] 1--> vccAppId '%s'", vccAppId)
	-- LogDebug("[VCCs:UpdateApplicationFile] 2--> fileInfo.modelFileName '%s'", fileInfo.modelFileName)
	-- LogDebug("[VCCs:UpdateApplicationFile] 3--> fileInfo.contentModelFile '%s'", fileInfo.contentModelFile)
	-- LogDebug("[VCCs:UpdateApplicationFile] 4--> totalFileParts '%d'", totalFileParts)
	-- LogDebug("[VCCs:UpdateApplicationFile] 5--> fileInfo.oldModelFileName '%s'", fileInfo.oldModelFileName)

	-- 1 - Remuevo las partes del archivo que se habia subido anteriormente (en el caso que este actualizando)
	RemoveApplicationFileParts(vcc, appId, keyspace)

	-- 2- Actualizo la info del Archivo (la metadata) en la CF VCCRasaApplicationsFiles
	local rows = {}
	local file = {
		vcc = vcc,
		appId = appId,
		lastModifiedDateTime = LuaOrm.Date:time(),
		modelFileName        = fileInfo.modelFileName,
		oldModelFileName     = fileInfo.oldModelFileName,
		totalFileParts       = totalFileParts
	}

	rows[vccAppId] = file
	utils.SaveRows({keyspace = keyspace or "Agent", columnFamily = "VCCRasaApplicationsFiles", rows = rows})

	-- 3- Actualizo las partes del Archivo en la CF VCCRasaApplicationsFilesParts
	local rowParts = {}
	local i = 1

	while i <= totalFileParts do
		local fileContentPart = fileInfo.contentModelFile[i]
		local rowPartId = ("%s::part_%d"):format(vccAppId, i)
		local fileData = {
			blob = fileContentPart
		}
		rowParts[rowPartId] = fileData
		--LogDebug("[VCCs:UpdateApplicationFile]  5.%d--> rowPartId '%s'", i, rowPartId)
		i = i + 1
	end

	--LogDebug("[VCCs:UpdateApplicationFile] 6--> rowParts '%s'", rowParts)

	utils.SaveRows({keyspace = keyspace or "Agent", columnFamily = "VCCRasaApplicationsFilesParts", rows = rowParts})
end

function UpdateApplication (vcc, appId, info, keyspace, excludeFields)
	assert(vcc)
	assert(appId)
	assert(info)

	excludeFields = excludeFields or {} -- Lista de Columns que no se deben actualizar (opcional)

	vcc = vcc:lower()
	appId = appId:lower()

	local apps = GetApplications(vcc, keyspace)
	apps[appId] = apps[appId] or {}

	for name, value in pairs(info or {}) do
		if not excludeFields[name] then
			apps[appId][name] = value
		end
	end
	-- Actualizo la ultima fecha de actualizacion de la App
	if not excludeFields["lastModifiedDateTime"] then
		apps[appId].lastModifiedDateTime = LuaOrm.Date:time()
	end

	local contentModelFile, modelFileName, oldModelFileName
	--LogDebug("[VCCs:UpdateApplication] 1------> vcc: '%s', appId: '%s', app.lastTrain: '%s'.", vcc, appId, info.lastTrain)
	if info.lastTrain and info.lastTrain.contentModelFile then
		contentModelFile = info.lastTrain.contentModelFile
		modelFileName = info.lastTrain.model_file
		oldModelFileName = info.lastTrain.oldModelFile

		info.lastTrain.contentModelFile = nil
		info.lastTrain.oldModelFile = nil
	end

	-- Actualizo la App
	SaveApplications(vcc, apps, keyspace)

	if contentModelFile then
		-- Actualizo el model file del train (cuando se esta actualizando la app via un train)
		UpdateApplicationFile(vcc, appId, {
				contentModelFile = contentModelFile,
				modelFileName    = modelFileName,
				oldModelFileName = oldModelFileName
			},
			keyspace
		)
	end
end

function RemoveApplicationFileParts (vcc, appId, keyspace)
	local vccAppId = utils.MakeAppId(vcc, appId)

	--LogDebug("[VCCs:RemoveApplicationFileParts] ------> vccAppId '%s'", vccAppId)

	local withParts = false
	local fileInfo = GetApplicationFile(vcc, appId, withParts, keyspace)
	if not fileInfo then
		return
	end

	local totalFileParts = tonumber(fileInfo.totalFileParts) or 0
	if totalFileParts <= 0 then
		return
	end

	local filePartsToRemove = {}
	local i = 1

	while i <= totalFileParts do
		local vccAppIdPart = vccAppId .. ("::part_%d"):format(i)
		table.insert(filePartsToRemove, vccAppIdPart)
		i = i + 1
	end

	--LogDebug("[VCCs:RemoveApplicationFileParts] --> vccAppId '%s' parts: %s", vccAppId, json.encode(filePartsToRemove))

	utils.RemoveColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaApplicationsFilesParts",
		rows = filePartsToRemove
	})
end

local function RemoveApplicationFile (vcc, appId, keyspace)
	RemoveApplicationFileParts(vcc, appId, keyspace)

	local apps = {}
	local vccAppId = utils.MakeAppId(vcc, appId)
	table.insert(apps, vccAppId)

	--LogDebug("[VCCs:RemoveApplicationFile] ------> vccAppId '%s'", vccAppId)
	utils.RemoveColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaApplicationsFiles",
		rows = apps
	})
end

function RemoveApplication (vcc, appId, keyspace)
	local apps = {}
	appId = appId:lower()
	table.insert(apps, appId)
	--LogDebug("[VCCs:RemoveApplication] ------> vcc: '%s', apps: '%s'", vcc, json.encode(apps))
	utils.RemoveColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaApplications",
		rows = {
			[vcc] = apps
		}
	})
	-- Elimino todos los Intents de la App
	RemoveAllAppIntents(vcc, appId, keyspace)
	-- Elimino todos los Entities de la App
	RemoveAllAppEntities(vcc, appId, keyspace)
	-- Elimino todos los Intents de la App
	RemoveApplicationFile(vcc, appId, keyspace)
end

--[[
	INTENTS -------------------------------------
]]--

function GetIntent (vcc, appId, intentId, keyspace)
	assert(vcc)
	assert(appId)
	assert(intentId)

	local vccAppId = utils.MakeAppId(vcc, appId)
	local encodedIntent = utils.GetColumnValue({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaIntents",
		key = vccAppId,
		column = intentId:lower()
	})

	--LogDebug("[GetIntent]---> vccAppId '%s', intentId '%s' intent '%s'", vccAppId, intentId, encodedIntent)
	return encodedIntent
end

function GetIntentByName (vcc, appId, intentName, keyspace)
	assert(vcc)
	assert(appId)
	assert(intentName)

	local vccAppId = utils.MakeAppId(vcc, appId)
	local intentId = utils.GetColumnValue({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaIntentsByName",
		key = vccAppId,
		column = intentName:lower()
	})

	--LogDebug("[GetIntentByName]---> vccAppId '%s', intentName '%s' intentId '%s'", vccAppId, intentName, intentId)

	if not intentId then
		--TODO: message
		return false
	end
	return GetIntent(vcc, appId, intentId, keyspace)
end

function GetIntents (vcc, appId, keyspace)
	assert(vcc)
	assert(appId)
	vcc = vcc:lower()
	appId = appId:lower()

	local vccAppId = utils.MakeAppId(vcc, appId):lower()
	return utils.GetAllColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaIntents",
		key = vccAppId,
		columns = {},
		columnCount = 50000
	}) or {}
end

local function SaveIntents (vccAppId, intents, keyspace)
	local rows = {}
	rows[vccAppId] = intents
	utils.SaveRows({keyspace = keyspace or "Agent", columnFamily = "VCCRasaIntents", rows = rows})
end

local function SaveIntentByName (vcc, appId, intentId, intentName, keyspace)
	local vccAppId = utils.MakeAppId(vcc, appId)
	intentName = intentName:lower()
	local rows = {}
	rows[vccAppId] = {}
	rows[vccAppId][intentName] = intentId
	--LogDebug("----> rows %s ", json.encode(rows))
	utils.SaveRows({keyspace = keyspace or "Agent", columnFamily = "VCCRasaIntentsByName", rows = rows})
end

local function RemoveRelationIntentsByName (vcc, appId, intentName, keyspace)
	local vccAppId = utils.MakeAppId(vcc, appId)
	local rows = {}

	if intentName then
	-- Si viene el valor intentName, se elimina esa columna especifica.
		local intentByName = {}
		intentName = intentName:lower()
		table.insert(intentByName, intentName)

		rows[vccAppId] = intentByName
	else
	-- Si NO viene el valor intentName, se elimina todas las columnas de la key especifica.
		table.insert(rows, vccAppId)
	end

	utils.RemoveColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaIntentsByName",
		rows = rows
	})
end

function UpdateIntent (vcc, appId, intentId, info, keyspace, excludeFields)
	assert(vcc)
	assert(appId)
	assert(intentId)
	assert(info)

	excludeFields = excludeFields or {} -- Lista de Columns que no se deben actualizar (opcional)

	vcc = vcc:lower()
	appId = appId:lower()
	intentId = intentId:lower()

	local intents = GetIntents(vcc, appId, keyspace)
	intents[intentId] = intents[intentId] or {}

	for name, value in pairs(info or {}) do
		if not excludeFields[name] then
			intents[intentId][name] = value
		end
	end

	-- Actualizo la ultima fecha de actualizacion del Intent
	if not excludeFields["lastModifiedDateTime"] then
		intents[intentId].lastModifiedDateTime = LuaOrm.Date:time()
	end

	-- Guarda el Intent
	local vccAppId = utils.MakeAppId(vcc, appId)
	SaveIntents(vccAppId, intents, keyspace)

	-- Elimino el indexado de la Intent con el Nombre anterior.
	if info.oldName then
		RemoveRelationIntentsByName(vcc, appId, info.oldName, keyspace)
	end

	-- Guarda la relacion "nombre"->"id" del intent
	SaveIntentByName(vcc, appId, intentId, info.name, keyspace)

	-- Actualizo en la CF de Applications la ultima fecha de actualizacion.
	-- La tabla que corresponde a la app debe ir vacia ya que esta funcion se encarga de actualizar el valor
	UpdateApplication(vcc, appId, {}, keyspace)
end

function RemoveIntent (vcc, appId, intentId, intentName, keyspace)
	local intents = {}
	intentId = intentId:lower()
	table.insert(intents, intentId)
	local vccAppId = utils.MakeAppId(vcc, appId)
	--LogDebug("[VCCs:RemoveIntent] ------> intentName: '%s', vccAppId: '%s', intents: '%s'", intentName, vccAppId, json.encode(intents))
	utils.RemoveColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaIntents",
		rows = {
			[vccAppId] = intents
		}
	})
	-- Elimino todos los Utterances del Intent
	RemoveAllAppIntentUtterances(vcc, appId, intentId, keyspace)

	-- 1.1 - Elimino el indexado de Intents por nombre.
	RemoveRelationIntentsByName(vcc, appId, intentName, keyspace)

	-- Actualizo en la CF de Applications la ultima fecha de actualizacion.
	-- La tabla que corresponde a la app debe ir vacia ya que esta funcion se encarga de actualizar el valor
	UpdateApplication(vcc, appId, {}, keyspace)
end

function RemoveAllAppIntents (vcc, appId, keyspace)
	local vccAppId = utils.MakeAppId(vcc, appId)
	--LogDebug("[VCCs:RemoveAllAppIntents] ------> Remove All Intents vccAppId: %s", vccAppId)
	local intents = GetIntents(vcc, appId, keyspace)
	utils.RemoveColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaIntents",
		rows = {vccAppId}
	})

	-- Elimino el indexado de todos los Intents de la App
	local intentId = nil -- para que elimine todos los intents de la App el IntentId tiene que ir en NIL
	RemoveRelationIntentsByName(vcc, appId, intentId, keyspace)

	-- Recorro los intents de la app y elimino todos los utterances de cada intent
	for _, intent in pairs(intents or {}) do
		RemoveAllAppIntentUtterances(vcc, appId, intent.id, keyspace)
	end
end

function LoadIntentsAndUtterances (vcc, appId, keyspace)
	assert(vcc)
	assert(appId)
	local intents = GetIntents(vcc, appId, keyspace)
	-- Recorro los intents de la app y cargo los utterances de cada intent
	for _, intent in pairs(intents or {}) do
		intent.utterances = LoadUtterances(vcc, appId, intent.id, keyspace)
	end
	return intents
end

--[[
	UTTERANCES -------------------------------------
]]--

function GetUtterance (vcc, appId, intentId, utteranceId, keyspace)
	assert(vcc)
	assert(appId)
	assert(intentId)
	assert(utteranceId)
	local vccAppIntentId = utils.MakeIntentId(vcc, appId, intentId)
	return utils.GetColumnValue({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaUtterances",
		key = vccAppIntentId,
		column = utteranceId:lower()
	})
end

function LoadUtterances (vcc, appId, intentId, keyspace)
	assert(vcc)
	assert(appId)
	assert(intentId)
	local vccAppIntentId = utils.MakeIntentId(vcc, appId, intentId)
	return utils.GetAllColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaUtterances",
		key = vccAppIntentId,
		columns = {},
		columnCount = 50000
	}) or {}
end

local function SaveUtterances (vccAppIntentId, utterances, keyspace)
	local rows = {}
	rows[vccAppIntentId] = utterances
	utils.SaveRows({keyspace = keyspace or "Agent", columnFamily = "VCCRasaUtterances", rows = rows})
end

local function LoadIntentsAndUtterancesByEntity (vcc, appId, entityId, keyspace)
	assert(vcc)
	assert(appId)
	assert(entityId)
	local vccAppEntityId = utils.MakeEntityId(vcc, appId, entityId)
	return utils.GetAllColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaUtterancesByEntity",
		key = vccAppEntityId,
		superColumns = true,
		columns = {},
		columnCount = 50000
	}) or {}
end

local function SaveRelationEntityIntentsUtterances (utterancesByEntities, keyspace)
	local rows = utterancesByEntities
	utils.SaveRows({keyspace = keyspace or "Agent", columnFamily = "VCCRasaUtterancesByEntity", rows = rows, superColumns = true})
end

local function RemoveRelationEntityIntentsUtterances (vccAppEntityId, vccAppIntentId, utteranceId, keyspace)
	local relation = {}
	if vccAppIntentId then
		relation[vccAppIntentId] = {}
		if utteranceId then
			table.insert(relation[vccAppIntentId], utteranceId)
		end
	end
	--LogDebug("[VCCs:RemoveRelationEntityIntentsUtterances] ------> vccAppEntityId: '%s', relation: '%s'", vccAppEntityId, json.encode(relation))
	utils.RemoveColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaUtterancesByEntity",
		superColumns = true,
		rows = {
			[vccAppEntityId] = relation
		}
	})
end

function UpdateUtterance (vcc, appId, intentId, utteranceId, info, keyspace, excludeFields, oldUtterance)
	assert(vcc)
	assert(appId)
	assert(intentId)
	assert(utteranceId)

	excludeFields = excludeFields or {} -- Lista de Columns que no se deben actualizar (opcional)

	vcc = vcc:lower()
	appId = appId:lower()
	intentId = intentId:lower()
	utteranceId = utteranceId:lower()

	local utterances = LoadUtterances(vcc, appId, intentId, keyspace)
	utterances[utteranceId] = utterances[utteranceId] or {}

	for name, value in pairs(info or {}) do
		if not excludeFields[name] then
			utterances[utteranceId][name] = value
		end
	end

	-- Actualizo la ultima fecha de actualizacion del Utterance
	if not excludeFields["lastModifiedDateTime"] then
		utterances[utteranceId].lastModifiedDateTime = LuaOrm.Date:time()
	end

	local vccAppIntentId = utils.MakeIntentId(vcc, appId, intentId)
	SaveUtterances(vccAppIntentId, utterances, keyspace)

	-- Actualizo la relacion del Utterance con el etiquetado de Entidades.
	if oldUtterance and oldUtterance.entityLabels then
		for _, lbl in pairs(oldUtterance.entityLabels) do
			--LogDebug("[models.VCCs:UpdateUtterance] ----------> 1 - lbl: %s", json.encode(lbl))
			local entityId = lbl.entityId or lbl.entityName
			assert(entityId)
			local vccAppEntityId = utils.MakeEntityId(vcc, appId, entityId)
			RemoveRelationEntityIntentsUtterances(vccAppEntityId, vccAppIntentId, utteranceId, keyspace)
		end
	end

	if info.entityLabels then
		local utterancesByEntities = {}
		for _, lbl in pairs(info.entityLabels) do
			--LogDebug("[models.VCCs:UpdateUtterance]-----------> 2 - lbl: %s", json.encode(lbl))
			local entityId = lbl.entityId or lbl.entityName
			assert(entityId)
			local vccAppEntityId = utils.MakeEntityId(vcc, appId, entityId)
			utterancesByEntities[vccAppEntityId] = utterancesByEntities[vccAppEntityId] or {}
			utterancesByEntities[vccAppEntityId][vccAppIntentId] = utterancesByEntities[vccAppEntityId][vccAppIntentId] or {}
			utterancesByEntities[vccAppEntityId][vccAppIntentId][utteranceId] = lbl
		end
		SaveRelationEntityIntentsUtterances(utterancesByEntities)
	end

	-- Actualizo en la CF de Applications la ultima fecha de actualizacion.
	-- La tabla que corresponde a la app debe ir vacia ya que esta funcion se encarga de actualizar el valor
	UpdateApplication(vcc, appId, {}, keyspace)
end

function RemoveUtterance (vcc, appId, intentId, utteranceId, mustUpdateAppInfo, keyspace)
	assert(vcc)
	assert(appId)
	assert(intentId)
	assert(utteranceId)

	local utterances = {}
	utteranceId = utteranceId:lower()
	table.insert(utterances, utteranceId)
	local vccAppIntentId = utils.MakeIntentId(vcc, appId, intentId)

	--LogDebug("[VCCs:RemoveUtterance] ------> Remove Utterance of Intent. vccAppIntentId: %s", vccAppIntentId)

	local encodedUtterance = GetUtterance(vcc, appId, intentId, utteranceId)
	--LogDebug("--> encodedUtterance %s", encodedUtterance)
	if encodedUtterance then
		local currentUtterance = json.decode(encodedUtterance)
		-- Elimino la relacion del Utterance con el etiquetado de Entidades.
		if currentUtterance.entityLabels then
			for _, lbl in pairs(currentUtterance.entityLabels) do
				local entityId = lbl.entityId or lbl.entityName
				assert(entityId)
				local vccAppEntityId = utils.MakeEntityId(vcc, appId, entityId)
				RemoveRelationEntityIntentsUtterances(vccAppEntityId, vccAppIntentId, utteranceId, keyspace)
			end
		end
	end

	--LogDebug("[VCCs:RemoveUtterance] ------> vccAppIntentId: '%s', utterances: '%s'", vccAppIntentId, json.encode(utterances))
	utils.RemoveColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaUtterances",
		rows = {
			[vccAppIntentId] = utterances
		}
	})
	-- Actualizo en la CF de Applications la ultima fecha de actualizacion.
	-- La tabla que corresponde a la app debe ir vacia ya que esta funcion se encarga de actualizar el valor
	--
	if mustUpdateAppInfo then
		-- A la info en la App la actualiza solo si se ha eliminado un Utterance de forma manual.
		-- No se actualiza si esta funcion fue invocada cuando se elimina un Intent y todos sus Utterances.
		UpdateApplication(vcc, appId, {}, keyspace)
	end
end

function RemoveAllAppIntentUtterances (vcc, appId, intentId, keyspace)
	--LogDebug("[VCCs:RemoveAllAppIntentUtterances] ------> Remove All Utterances of Intent. vccAppIntentId: %s", utils.MakeIntentId(vcc, appId, intentId))

	local mustUpdateAppInfo = false -- a la info de la App la actualiza quien ha invodado a la funcion "RemoveAllAppIntentUtterances"
	local utterances = LoadUtterances(vcc, appId, intentId, keyspace)
	for _, utterance in pairs(utterances or {}) do
		RemoveUtterance(vcc, appId, intentId, utterance.id, keyspace)
	end
end

--[[
	ENTITIES -------------------------------------
]]--

function GetEntity (vcc, appId, entityId, keyspace)
	assert(vcc)
	assert(appId)
	assert(entityId)

	local vccAppId = utils.MakeAppId(vcc, appId)
	local encodedEntity = utils.GetColumnValue({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaEntities",
		key = vccAppId,
		column = entityId:lower()
	})

	--LogDebug("[GetEntity]---> vccAppId '%s', entityId '%s' entity '%s'", vccAppId, entityId, encodedEntity)
	return encodedEntity
end

function GetEntityByName (vcc, appId, entityName, keyspace)
	assert(vcc)
	assert(appId)
	assert(entityName)

	local vccAppId = utils.MakeAppId(vcc, appId)
	local entityId = utils.GetColumnValue({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaEntitiesByName",
		key = vccAppId,
		column = entityName:lower()
	})

	--LogDebug("[GetEntityByName]---> vccAppId '%s', entityName '%s' entityId '%s'", vccAppId, entityName, entityId)

	if not entityId then
		--TODO: message
		return false
	end
	return GetEntity(vcc, appId, entityId, keyspace)
end

function LoadEntities (vcc, appId, keyspace)
	assert(vcc)
	assert(appId)
	vcc = vcc:lower()
	appId = appId:lower()

	local vccAppId = utils.MakeAppId(vcc, appId):lower()
	return utils.GetAllColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaEntities",
		key = vccAppId,
		columns = {},
		columnCount = 50000
	}) or {}
end

local function SaveEntities (vccAppId, entities, keyspace)
	local rows = {}
	rows[vccAppId] = entities
	utils.SaveRows({keyspace = keyspace or "Agent", columnFamily = "VCCRasaEntities", rows = rows})
end

local function SaveEntityByName (vcc, appId, entityId, entityName, keyspace)
	local vccAppId = utils.MakeAppId(vcc, appId)
	entityName = entityName:lower()
	local rows = {}
	rows[vccAppId] = {}
	rows[vccAppId][entityName] = entityId
	--LogDebug("----> rows %s ", json.encode(rows))
	utils.SaveRows({keyspace = keyspace or "Agent", columnFamily = "VCCRasaEntitiesByName", rows = rows})
end

local function RemoveRelationEntitiesByName (vcc, appId, entityName, keyspace)
	local vccAppId = utils.MakeAppId(vcc, appId)
	local rows = {}

	if entityName then
	-- Si viene el valor entityName, se elimina esa columna especifica.
		local entityByName = {}
		entityName = entityName:lower()
		table.insert(entityByName, entityName)

		rows[vccAppId] = entityByName
	else
	-- Si NO viene el valor entityName, se elimina todas las columnas de la key especifica.
		table.insert(rows, vccAppId)
	end

	utils.RemoveColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaEntitiesByName",
		rows = rows
	})
end

function UpdateEntity (vcc, appId, entityId, info, keyspace, excludeFields)
	assert(vcc)
	assert(appId)
	assert(entityId)
	assert(info)

	excludeFields = excludeFields or {} -- Lista de Columns que no se deben actualizar (opcional)

	vcc = vcc:lower()
	appId = appId:lower()
	entityId = entityId:lower()

	local entities = LoadEntities(vcc, appId, keyspace)
	entities[entityId] = entities[entityId] or {}

	for name, value in pairs(info or {}) do
		if not excludeFields[name] then
			entities[entityId][name] = value
		end
	end

	-- Actualizo la ultima fecha de actualizacion de la Entity
	if not excludeFields["lastModifiedDateTime"] then
		entities[entityId].lastModifiedDateTime = LuaOrm.Date:time()
	end

	-- Guarda la Entidad
	local vccAppId = utils.MakeAppId(vcc, appId)
	SaveEntities(vccAppId, entities, keyspace)

	-- Elimino el indexado de la Entidad con el Nombre anterior.
	if info.oldName then
		RemoveRelationEntitiesByName(vcc, appId, info.oldName, keyspace)
	end

	-- Guarda la relacion "nombre"->"id" de la entidad
	SaveEntityByName(vcc, appId, entityId, info.name, keyspace)

	-- Actualizo en la CF de Applications la ultima fecha de actualizacion.
	-- La tabla que corresponde a la app debe ir vacia ya que esta funcion se encarga de actualizar el valor
	UpdateApplication(vcc, appId, {}, keyspace)
end

function RemoveEntity (vcc, appId, entityId, entityName, keyspace)
	local entities = {}
	entityId = entityId:lower()
	table.insert(entities, entityId)
	local vccAppId = utils.MakeAppId(vcc, appId)


	--LogDebug("[VCCs:RemoveEntity] ------> entityName: '%s', vccAppId: '%s', entities: '%s'", entityName, vccAppId, json.encode(entities))

	-- 1 - Elimino las entidades de la CF.
	--
	utils.RemoveColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaEntities",
		rows = {
			[vccAppId] = entities
		}
	})

	-- 1.1 - Elimino el indexado de Entidades por nombre.
	RemoveRelationEntitiesByName(vcc, appId, entityName, keyspace)

	-- 2 - Si la entidad fue asiganda a Utterances, procedo a eliminar las mismas.
	--
	-- Obtengo la lista de intents y utterances vinculados a la entity a ser eliminada.
	local intentsByEntity = LoadIntentsAndUtterancesByEntity(vcc, appId, entityId, keyspace)
	--LogDebug("[VCCs:RemoveEntity] ------> vccAppId: '%s', intentsByEntity: '%s'", vccAppId, json.encode(intentsByEntity))

	-- Recorro la lista de intents relacionados con la entidad.
	for _, utterances in pairs(intentsByEntity or {}) do
		-- Recorro la lista de Utterances del Intent relacionado con la entidad
		for utteranceId, utterance in pairs(utterances) do
			-- Obtengo informacion y detalle del utterance.
			local encodedUtterance = GetUtterance(vcc, appId, utterance.intentId, utteranceId)
			--LogDebug("[VCCs:RemoveEntity] antes --> encodedUtterance %s", encodedUtterance)
			if encodedUtterance then
				local currentUtterance = json.decode(encodedUtterance)
				-- Recorro la lista de labels del Utterance, para proceder a eliminar la relacion del Utterance con el etiquetado de Entidades.
				if currentUtterance.entityLabels then
					-- Me quedo con la lista del index del array y el id de la entidad que ha etiquetado la palabra del utterance.
					local indexesToRemove = {}
					for index, lbl in pairs(currentUtterance.entityLabels) do
						--LogDebug("[VCCs:RemoveEntity] %s lbl %s", entityId, json.encode(lbl))
						if entityId == lbl.entityName:lower() then
							table.insert(indexesToRemove, {index = index, fullEntityId = lbl.entityId})
						end
					end
					--LogDebug("[VCCs:RemoveEntity] indexesToRemove %s", json.encode(indexesToRemove))
					-- Si al menos la entidad se ha etiquetado una vez en el Utterance, continuo con la eliminación del etiquetado.
					local total = #indexesToRemove
					if total > 0 then
						local vccAppIntentId = utils.MakeIntentId(vcc, appId, currentUtterance.intentName)
						local i = total
						-- Recorro la lista de etiquetas para eliminarlas del array de etiquetas (entityLabels) del Utterance
						while i > 0 do
							local indexToRemove = indexesToRemove[i]
							-- 3 - Remuevo la entidad del Array de etiquetas (entityLabels) del Utterance para que luego pueda ser almacenado en (5)
							table.remove(currentUtterance.entityLabels, indexToRemove.index)
							local vccAppEntityId = utils.MakeEntityId(vcc, appId, entityId)
							-- 4 - Remuevo la relación de la entidad con los Utterances e Intent (CF VCCRasaUtterancesByEntity).
							RemoveRelationEntityIntentsUtterances(vccAppEntityId, vccAppIntentId, utteranceId, keyspace)
							i = i - 1
						end

						-- 5 - Ahora actualizo el Utterance con las etiquetas que quedaron en (entityLabels)
						local utterances = {}
						utterances[utteranceId:lower()] = currentUtterance
						--LogDebug("[VCCs:RemoveEntity] despues --> vccAppIntentId %s - encodedUtterance %s", vccAppIntentId, json.encode(currentUtterance))
						SaveUtterances(vccAppIntentId, utterances, keyspace)
					end
				end
			end
		end
	end

	-- Actualizo en la CF de Applications la ultima fecha de actualizacion.
	-- La tabla que corresponde a la app debe ir vacia ya que esta funcion se encarga de actualizar el valor
	UpdateApplication(vcc, appId, {}, keyspace)
end

function RemoveAllAppEntities (vcc, appId, keyspace)
	local vccAppId = utils.MakeAppId(vcc, appId)
	--LogDebug("[VCCs:RemoveAllAppEntities] ------> Remove All entities vccAppId: %s", vccAppId)
	local entities = LoadEntities(vcc, appId, keyspace)
	utils.RemoveColumns({
		keyspace = keyspace or "Agent",
		columnFamily = "VCCRasaEntities",
		rows = {vccAppId}
	})

	-- Elimino el indexado de todas las Entidades de la App
	local entityId = nil -- para que elimine todas las entidades de la App el entityId tiene que ir en NIL
	RemoveRelationEntitiesByName(vcc, appId, entityId, keyspace)

	-- Recorro los entities de la app y elimino y elimino la Entidad de todas las relaciones con los Intents y Utterances (CF VCCRasaUtterancesByEntity).
	for _, entity in pairs(entities or {}) do
		local vccAppEntityId = entity.fullId
		RemoveRelationEntityIntentsUtterances(vccAppEntityId, nil, nil, keyspace)
	end
end
