local utf8 = require "icu.utf8"
local m_helpers = require "fullcontact.helpers"
local m_blackList = {}
m_blackList.Global = {}
local m_fullcontact -- fullcontact module
local m_dialer -- Dialer All API Methods
local m_model -- modelo de cassandra
local m_configValues = m_helpers.GetConfigValues()
--[[
	Variable/flag utilizado para habilitar/desactivar la validacion contra las blacklists, en las importaciones de contactos.
	Utilizado para el desarrollo.
]]--
local m_enabledContactsImportsValidations = true

-- tope maximo de contactos fallidos que se almacenaran por cada importacion.
local MAXIMUM_NUMBER_OF_FAILED_ADDRESSES_TO_STORE = m_configValues.importation.maximumNumberFailedContactsToStore
-- Si los primeros N contactos "maximumFirstFailedContactsForBreakImport" de la importacion son fallidos, debe abortar la misma.
local MAXIMUM_FIRST_FAILED_ADDRESSES_FOR_BREAK_IMPORT = m_configValues.importation.maximumFirstFailedContactsForBreakImport

-- INDICES Blacklist .........................................................
--
m_blackList.Init = function(self, fullcontact)
	--LogDebug("[FULLCONTACT.blacklist:Init] Initializing module ............. fullcontact: %s", fullcontact)
	m_fullcontact = fullcontact
	m_dialer = m_fullcontact.Dialer:GetDialerApi()
	m_model = m_fullcontact:GetModel()
end

m_blackList.CreateESTemplate = function(self)
	return m_fullcontact.CreateESTemplate("BLACKLIST_DIALER")
end

m_blackList.CreateESIndex = function(self, vcc)
	return m_fullcontact.CreateESIndex(vcc, "BLACKLIST_DIALER")
end

m_blackList.DropESIndex = function(self, vcc)
	return m_fullcontact.DropESIndex(vcc, "BLACKLIST_DIALER")
end

m_blackList.GetESIndexName = function(self, vcc)
	return m_fullcontact.GetESIndexName(vcc, "BLACKLIST_DIALER")
end

m_blackList.CanStartImport = function(self, importInfo)
	importInfo.VCC = importInfo.VCC or "system"
	return m_dialer:CanStartImport(importInfo)
end

m_blackList.GetVCCStatusProcessBatch = function(vcc)
	vcc = vcc or "system"
	return m_dialer.GetVCCStatusProcessBatch(vcc)
end

m_blackList.ValidateContactLimitsInBatch = function(self, importInfo)
	return m_dialer:ValidateContactLimitsInBatch(importInfo)
end

--[[
	Aca podemos habilitar mas canales.
]]--
m_blackList.GetImportFormat = function(self, addressType)
	addressType = addressType and addressType:upper() or nil
	local importFormatInfo = {
		ImportFormatFields = {
			{
				Name = "ID",
				DisplayName = "Id" ,
				FieldType = "ADDRESS_ID",
				IsRequired = true,
			},
			{
				Name = "CHANNELS",
				DisplayName = "Channels",
				FieldType = "CHANNELS_TYPE",
				IsRequired = false,
				IsHidden = true, -- para saber si debe de visualizar en la GUI (de momento que existe un único canal)
				AllowedChannelsByAddressType = {
					EMAIL = {
						Default = {"EMAIL"},
						Channels = {EMAIL = false}
					},
					PHONE = {
						Default = {"CALL"},
						Channels = {CALL = true, SMS = false}
					}
				}
			}
		}
	}
	if addressType then
		for _, info in ipairs(importFormatInfo.ImportFormatFields) do
			if info.FieldType == "CHANNELS_TYPE" then
				if not info.AllowedChannelsByAddressType[addressType] then
					return false, "UNSUPPORTED ADDRESS TYPE"
				end
			end
		end
	end
	return true, importFormatInfo
end

m_blackList.MakeAddressId = function(self, id, addressType)
	assert(type(id) == "string", "id must be a string")
	assert(type(addressType) == "string", "addressType must be a string")
	addressType = m_helpers.CapitalizeUpperCase(addressType)
	return ("%s:%s"):format(addressType, id:lower())
end

m_blackList.MakeScopeInfo = function(self, scopeType, vcc, blacklistId)
	assert(type(scopeType) == "string", "scopeType must be a string")
	scopeType = scopeType:lower()
	scopeType = scopeType == "global" and "global" or "vcc"
	if scopeType == "global" then
		return "global"
	end
	assert(type(vcc) == "string", "vcc is required for 'vcc' scopeType")
	assert(type(blacklistId) == "string", "blacklistId is required for 'vcc' scopeType")
	return "vcc", ("%s:%s"):format(vcc, blacklistId):lower()
end

m_blackList.StartImport = function(self, importInfo, csvContent, onImported, ksOE)
	assert(type(importInfo) == "table", "vcc must be a table")
	assert(type(importInfo.ID) == "string", "ID must be a string")
	assert(type(importInfo.ScopeType) == "string", "ScopeType must be a string")
	assert(type(importInfo.AddressType) == "string", "AddressType must be a string")
	assert(type(csvContent) == "string", "csvContent data must be a string")
	onImported = onImported or function() return true end

	importInfo.ID = utf8.lower(importInfo.ID)
	m_helpers.memory:ShowMemory()

	local addressType = importInfo.AddressType:upper()
	local scopeType, scopeId = m_blackList:MakeScopeInfo(importInfo.ScopeType, importInfo.VCC, importInfo.ID)
	local msg = string.format("[FULLCONTACT.blacklist:StartImport] + STARTING BLACKLIST - ADDING FROM CSV. scopeType: '%s', scopeId: '%s', addressType: '%s'", scopeType, (scopeId or ""), addressType)
	local processId = m_helpers.elapsedTime:Start(msg)

	local vcc = importInfo.VCC
	local canAdd, err = m_blackList.CanStartImport(m_blackList, importInfo)
	if not canAdd then
		return false, err
	end

	local success, importFormat = m_blackList:GetImportFormat(addressType)
	if not success then
		m_helpers.memory:CollectAllMemory()
		local errType = importFormat
		local err = {reason = errType .. " " .. addressType, type = errType}
		LogError("[FULLCONTACT.blacklist:StartImport] Error Starting Importation. Err: %s", err.reason)
		return false, err
	end
	if type(importFormat.ImportFormatFields) ~= "table" or #importFormat.ImportFormatFields == 0 then
		m_helpers.memory:CollectAllMemory()
		local reason = "INVALID IMPORT FORMAT - MISSING FIELDS"
		local err = {reason = reason, type = reason}
		LogError("[FULLCONTACT.blacklist:StartImport] Error Starting Importation. Err: %s", err.reason)
		return false, err
	end
	--LogDebug("")
	--LogDebug("2.1   - (array) importFormat.ImportFormatFields      : " .. m_helpers.JSON_Encode(importFormat.ImportFormatFields))
	--LogDebug("")
	--LogDebug("0 - importInfo: " .. m_helpers.JSON_Encode(importInfo))
	--LogDebug("")
	--LogDebug("1 - csv content: " .. csvContent)

	-- start parsing in validating
	local alreadyExistImport = m_blackList:GetImportation(importInfo.ID, vcc, ksOE)
	--LogDebug("[FULLCONTACT.blacklist:StartImport]   -> ImportId '%s@%s', already exist: '%s'.", importInfo.ID, vcc, alreadyExistImport)
	if importInfo.isNew and alreadyExistImport then
		m_helpers.memory:CollectAllMemory()
		local reason = string.format("UNABLE TO CREATE IMPORTATION '%s' - ALREADY EXIST.", importInfo.ID)
		local err = {reason = reason, type = "UNABLE TO CREATE IMPORTATION - ALREADY EXIST"}
		LogError("[FULLCONTACT.blacklist:StartImport] Error Starting Importation. Err: %s", err.reason)
		return false, err
	end

	local csvHeader = m_helpers.csv:ParseCSVHeader(csvContent)
	if not csvHeader then
		m_helpers.memory:CollectAllMemory()
		local reason = "ERROR PARSING CSV HEADER"
		local err = {reason = reason, type = reason}
		LogError("[FULLCONTACT.blacklist:StartImport] Error Starting Importation. Err: %s", err.type)
		return false, err
	end

	-- LogDebug("")
	-- LogDebug("2 -csvHeader: " .. m_helpers.JSON_Encode(csvHeader))3

	-- valido los campos requeridos
	local missingRequiredFields = {}
	local _, csvAllowedColumns = m_blackList:GetCsvAllowedColumns()
	for _, allowedColumn in ipairs(csvAllowedColumns) do
		--LogDebug("  allowedColumn: %s", json.encode(allowedColumn))
		if allowedColumn.IsRequired then
			local exist = false
			for _, header in ipairs(csvHeader) do
				--LogDebug("     header: %s", header)
				if allowedColumn.Name:upper() == header:upper() then
					exist = true
					break
				end
			end
			if not exist then
				table.insert(missingRequiredFields, allowedColumn.Name)
			end
		end
	end
	if #missingRequiredFields > 0 then
		m_helpers.memory:CollectAllMemory()
		local reason = "ERROR INVALID CSV HEADER - MISSING REQUIRED COLUMNS"
		local err = {reason = reason, type = reason, missingRequiredFields = missingRequiredFields}
		LogError("[FULLCONTACT.blacklist:StartImport] Error Starting Importation. Err: %s", err.type)
		return false, err
	end
	----

	local mappingFields = m_helpers.ArrayToHash(importFormat.ImportFormatFields, "Name", function(item, key)
		if type(key) == "string" then
			key = key:upper()
		end
		return key
	end)
	importFormat.mappingFields = mappingFields

	--LogDebug("3   - (hash) mappingFields      : " .. m_helpers.JSON_Encode(mappingFields))

	LogInfo("[FULLCONTACT.blacklist:StartImport]    + START Parsing & Validating CSV.")
	local ROWS_PER_BULK = 500
	local logCsvRow = ROWS_PER_BULK

	local statusInfo = {
		status = "RUNNING",
		action = "PARSING & VALIDATING CSV",
		parsed = 0,
		added = 0,
		updated = 0,
		errors = 0,
		deleted = 0,
		total = 0
	}
	local failedAddressesList = {}
	m_model.BlackList:SaveImportation(importInfo, statusInfo, nil, failedAddressesList, ksOE)
	--

	--[[
		Callback invocado cuando termina de parsear una linea del CSV.
	]]--
	local function OnCompletedLine (blackRow)
		-- Si no ha cargado ningun channel desde el CSV, tengo que agregar los por defecto para el canal.
		if #blackRow.Channels == 0 then
			local fieldInfo = mappingFields["CHANNELS"]
			if fieldInfo then
				local addressType = blackRow.Type:upper()
				local allowedChannels = fieldInfo.AllowedChannelsByAddressType[addressType] or {}
				blackRow.Channels = allowedChannels.Default or {}
				--LogDebug("[FULLCONTACT.blacklist:StartImport]  ------>  OnCompletedLine. Assign default channels: %s", m_helpers.JSON_Encode(blackRow.Channels))
			end
		end
	end

	local parsedCSV, parsedRawCSV = m_helpers.csv:Csv2Table(csvContent,
		function(csvFieldName, csvFieldValue, blackRow, rowIndex, columnIndex, rawRow)
			csvFieldName = m_helpers.toUTF8(csvFieldName)
			csvFieldValue = m_helpers.toUTF8(csvFieldValue)

			local fieldInfo = mappingFields[m_helpers.alltrim(utf8.upper(csvFieldName))]
			csvFieldValue = m_helpers.alltrim(csvFieldValue)

			if rowIndex == logCsvRow then
				LogInfo("[FULLCONTACT.blacklist:StartImport]         -> " .. tostring(rowIndex) .. " rows validated of CSV.")
				logCsvRow = logCsvRow + ROWS_PER_BULK
				--
				local currentProcessBatch = m_blackList.GetVCCStatusProcessBatch(importInfo.VCC)
				if currentProcessBatch then
					currentProcessBatch.parsed = (currentProcessBatch.parsed or 0) + ROWS_PER_BULK
					m_helpers.dialer.UpdateVCCStatusProcessBatch(currentProcessBatch)
				end
			end

			--LogDebug("1 - line: " .. tostring(rowIndex) .. " -  [Csv2Table] csvFieldName: ".. csvFieldName .. ", csvFieldValue: " .. tostring(csvFieldValue) .. ", fieldInfo: " .. tostring(fieldInfo))
			--LogDebug("1.1 - temporal blackRow: " .. m_helpers.JSON_Encode(blackRow))
			--LogDebug("1.2 - rawRow: " .. m_helpers.JSON_Encode(rawRow))
			--LogDebug("-------------------------------------")

			if not fieldInfo then
				-- Si la columna del CSV, NO es una columna valida
				return
			end

			if not blackRow.Scope then
				blackRow.Scope = scopeId or scopeType
			end
			if not blackRow.Channels then
				blackRow.Channels = {}
			end

			local fieldType = (fieldInfo.FieldType or ""):upper()
			--LogDebug("2 -    [Csv2Table]  addressType: '%s', fieldInfo.fieldType: '%s' categoryTypeName: '%s' fieldInfo: %s", addressType, fieldType, fieldInfo.TypeName, m_helpers.JSON_Encode(fieldInfo))

			if fieldType == "ADDRESS_ID" then
				-- "PHONE" "EMAIL"
				if addressType == "PHONE" or addressType == "EMAIL" then
					if #csvFieldValue == 0 then
						return nil
					end
					local addressInfo = {
						OriginalId = csvFieldValue
					}
					if addressType == "PHONE" then
					-- "PHONEDATA" (7)
						--LogDebug("[FULLCONTACT.blacklist:StartImport] - Creo PHONE")
						--addressInfo.type = "PHONE"
						local phoneNumber = csvFieldValue
						local isValidPhone, response =
							m_helpers.validation:IsPhone(phoneNumber, fieldInfo.PhoneCountry, fieldInfo.PhoneArea, vcc)
						if isValidPhone then
							-- la validacion que retorna "lua_telephonyproxyclient":
								-->  response:  {"number":"1234","dialLocal":"1234","dialGlobal":"5981234","dialSameCountry":"1234","countryCode":"598","areaCode":""}
							-- los datos como los espera "address_mapper.lua"
							addressInfo.Number = response.dialGlobal
							addressInfo.LocalNumber = response.dialLocal
							addressInfo.Country = response.Country
							addressInfo.Area = response.Area
							addressInfo.SimpleNumber = response.number
						end
					else
					-- "EMAIL"
						--LogDebug("[FULLCONTACT.blacklist:StartImport] - Creo EMAIL")
						--addressInfo.type = "MAIL"
					end
					blackRow.Type = m_helpers.CapitalizeUpperCase(addressType)
					local addressInstance = m_helpers.BuildAddressInstance(addressType, addressInfo, vcc)
					if addressInstance then
						blackRow.Id = m_blackList:MakeAddressId(addressInstance.SanitizedId, blackRow.Type)
						blackRow.AddressInfo = addressInstance
					end
					-- para que no agregue el valor parseado sin ser procesado
					return nil
				end
			elseif fieldType == "CHANNELS_TYPE" then
				local parsedChannels = m_helpers.csv:ParseCSVLine(csvFieldValue)
				if #(parsedChannels or {}) > 0 then
					local allowedChannels = fieldInfo.AllowedChannelsByAddressType[addressType] or {}
					--LogDebug("[FULLCONTACT.blacklist:StartImport] - allowedChannels: %s'", m_helpers.JSON_Encode(allowedChannels))
					for _, channel in ipairs(parsedChannels) do
						channel = channel:upper()
						if allowedChannels.Channels[channel] then
							table.insert(blackRow.Channels, channel)
						--else
							--LogDebug("[FULLCONTACT.blacklist:StartImport] - NOT Allowed Channel: %s.", channel)
						end
					end
				--else
					--LogDebug("[FULLCONTACT.blacklist:StartImport] - CSV Without Channels.")
				end
				-- para que no agregue el valor parseado sin ser procesado
				return nil
			end

			return fieldType
		end,
		OnCompletedLine -- callback
	)

	LogInfo("[FULLCONTACT.blacklist:StartImport]    - END Parsed & Validated CSV. Total Rows: " .. tostring(#parsedCSV))
	--LogDebug("4 ------------> despues m_helpers.csv:Csv2Table - \n1-parsedCSV: %s,\n2-parsedRawCSV: %s", m_helpers.JSON_Encode(parsedCSV), m_helpers.JSON_Encode(parsedRawCSV))

	-- Los contactos parseados del CSV en una tabla
	local blackListAddresses = parsedCSV
	importInfo.totalContacts = #blackListAddresses
	local canAdd, err = m_blackList.ValidateContactLimitsInBatch(m_blackList, importInfo)
	if not canAdd then
		if not alreadyExistImport then
			--LogDebug("4.1 ------------> Removing aborted New Importation: %s@s", importInfo.ID, vcc)
			local currentProcessBatch = m_blackList.GetVCCStatusProcessBatch(importInfo.VCC)
			if currentProcessBatch then
				currentProcessBatch.Status = "COMPLETED"
				m_helpers.dialer.UpdateVCCStatusProcessBatch(currentProcessBatch)
			end
			m_blackList:RemoveImport(importInfo.ID, vcc, nil, ksOE)
		end
		return false, err
	end

	--LogDebug("5 ------------> PARSED blackListAddresses: " .. m_helpers.JSON_Encode(blackListAddresses or {}))
	local success, response = m_blackList.AddBlackListAddresses(m_blackList, importInfo, blackListAddresses, parsedRawCSV, importFormat)
	--LogDebug("6 ------------> processId: " .. processId)

	if success then
		onImported(contactsByProcesses)
	end

	local msg = "[FULLCONTACT.blacklist:StartImport] - END IMPORTATION - ADD DIALER ADDRESSES BATCH FROM CSV"
	m_helpers.elapsedTime:End(processId, msg)
	m_helpers.memory:CollectAllMemory()

	return success, response
end

m_blackList.SanitizeAddressToSave = function(self, id, vcc, addressData, now)
	assert(type(id) == "string", "id must be a string")
	assert(type(vcc) == "string", "vcc must be a string")
	assert(type(addressData) == "table", "address data must be a table")
	-- Queda la funcion por si tenemos que realizar alguna accion.
	--
	--
	-- local address = pl.tablex.deepcopy(addressData)
	-- address.Id = m_helpers.AddVCC(id, vcc)
	-- address.VCC = vcc
	-- address.CreatedAt = now
	-- address.LastUpdate = now
	-- address.isNew = nil
	-- return address
	--
	return addressData
end

m_blackList.CanAddAddress = function(self, addressId, vcc, importFormat, csvData, importInfo)
	assert(type(addressId) == "string", "vcc must be a string")
	assert(type(vcc) == "string", "vcc must be a string")
	assert(type(importFormat) == "table", "importFormat must be a table")
	assert(type(importFormat.mappingFields) == "table", "importFormat.mappingFields must be a table")
	assert(type(csvData) == "table", "csvData must be a table")
	assert(type(importInfo) == "table", "importInfo must be a table")

	-- LogDebug("[ULLCONTACT.blacklist:CanAddAddress] ----------> Validating can add address: '%s', vcc: '%s'", addressId, vcc)
	-- LogDebug("[ULLCONTACT.blacklist:CanAddAddress] 1-----------------> importFormat.mappingFields: '%s'", m_helpers.JSON_Encode(importFormat.mappingFields))
	-- LogDebug("[ULLCONTACT.blacklist:CanAddAddress] 2-----------------> csvData: '%s'", m_helpers.JSON_Encode(csvData))

	local addressType = importInfo.AddressType:upper()
	local success = true
	local errorType = nil

	local function MakeErrType (err, csvFieldName)
		if not errorType then
			errorType = err
		else
			errorType = errorType .. " - AND " .. err
		end
		errorType = errorType .. string.format(" (Header Name: %s)", csvFieldName)
	end

	for csvFieldName, csvFieldValue in pairs(csvData) do
		local fieldInfo = importFormat.mappingFields[m_helpers.alltrim(csvFieldName):upper()]
		if fieldInfo then
			--LogDebug("[ULLCONTACT.blacklist:CanAddAddress]    *****************> fieldInfo: '%s'", m_helpers.JSON_Encode(fieldInfo))
			local fieldType = (fieldInfo.FieldType or ""):upper()
			if fieldType == "ADDRESS_ID" then
				-- "PHONE"
				if addressType == "PHONE" then
					local phoneNumber = csvFieldValue
					local isValidPhone, response =
						m_helpers.validation:IsPhone(phoneNumber, fieldInfo.PhoneCountry, fieldInfo.PhoneArea, vcc)
					--LogDebug("[ULLCONTACT.blacklist:CanAddAddress]     a -----------------> validated PHONEDATA csvFieldName: '%s', csvFieldValue: '%s', success: '%s', response: '%s'.", csvFieldName, phoneNumber, success, m_helpers.JSON_Encode(response))
					if not isValidPhone then
						success = false
						local errType = response.result and response.result .. (response.reason and ": INVALID PHONE - " .. response.reason or "") or "INVALID PHONE"
						MakeErrType(errType, csvFieldName)
					end
				-- "EMAIL"
				elseif addressType == "EMAIL" then
					local isValidEmail = m_helpers.validation:IsEmail(csvFieldValue)
					--LogDebug("[ULLCONTACT.blacklist:CanAddAddress]     b -----------------> validated EMAIL csvFieldName: '%s', csvFieldValue: '%s', success: '%s'.", csvFieldName, csvFieldValue, success)
					if not isValidEmail then
						success = false
						MakeErrType("INVALID EMAIL", csvFieldName)
					end
				else
					assert(false, "Unrecognized addressType: " .. tostring(addressType))
				end
			elseif fieldType == "CHANNELS_TYPE" then
				local parsedChannels = m_helpers.csv:ParseCSVLine(csvFieldValue)
				if #(parsedChannels or {}) > 0 then
					local allowedChannels = fieldInfo.AllowedChannelsByAddressType[addressType] or {}
					--LogDebug("[FULLCONTACT.blacklist:CanAddAddress] - allowedChannels: %s'", m_helpers.JSON_Encode(allowedChannels))
					local invalidChannels = {}
					for _, channel in ipairs(parsedChannels) do
						channel = channel:upper()
						if not allowedChannels.Channels[channel] then
							table.insert(invalidChannels, channel)
						end
					end
					if #invalidChannels > 0 then
						success = false
						MakeErrType("UNSUPPORTED CHANNELS", table.concat(invalidChannels, ","))
					end
				end
			else
				assert(false, "unrecognized CSV fieldType: " .. tostring(fieldType))
			end
		-- else
		-- 	assert(false, "unsupported CSV Header Name: " .. tostring(csvFieldName))
		end
	end
	if not success then
		-- error
		local err = {reason = errorType, type = errorType}
		LogWarning("[ULLCONTACT.blacklist:CanAddAddress] ==> Unable to Add address '%s' vcc '%s'. Err: %s", addressId, vcc, err.type)
		return false, err
	end
	-- ok
	return true
end

--[[
	"Id": "Phone:+59898100199",
	"Type": "Phone",
	"Scope": "vcc1:blacklist_name"
		Channels: {"SMS", "CALL", ALGO MAS}
	"ImportId" : "fecha",

					blackRow.Type = m_helpers.CapitalizeUpperCase(addressType)
					blackRow.Id = m_blackList:MakeAddressId(csvFieldValue, blackRow.Type)
					blackRow.AddressInfo = addressInfo
]]--
m_blackList.MakeAddressInfo = function(self, addressData, importInfo)
	assert(type(addressData) == "table", "addressData data must be a table")
	assert(type(addressData.AddressInfo) == "table", "addressData.AddressInfo data must be a table")
	assert(type(importInfo) == "table", "importInfo must be a table")
	--LogDebug("1----------> m_blackList.MakeAddressInfo -> addressId:  '%s', source addressData: '%s'", addressData.ID, m_helpers.JSON_Encode(addressData))
	local addressType = addressData.Type:upper()
	local addressByType = {
		PHONE = function(row)
			return row.AddressInfo.Number
		end
	}
	local address = addressByType[addressType] and addressByType[addressType](addressData) or aaa
	local addressInfo = {
		Id          = addressData.Id,
		VCC         = utf8.lower(importInfo.VCC),
		Type        = addressData.Type,
		Scope       = addressData.Scope,
		Channels    = addressData.Channels or {},
		AddressInfo = addressData.AddressInfo,
		Key         = self:MakeKey(addressData, importInfo),
		ImportId    = utf8.lower(importInfo.ID),
		Address     = address
	}
	--LogDebug("2----------> m_blackList.MakeAddressInfo -> addressId:  '%s', target addressInfo: '%s'", addressData.ID, m_helpers.JSON_Encode(addressInfo))
	return addressInfo
end

m_blackList.MakeKey = function(self, addressData, importInfo)
	assert(type(addressData) == "table", "address data must be a table")
	assert(type(importInfo) == "table", "importInfo must be a table")
	assert(type(importInfo.ID) == "string", "importInfo.ID must be a string")
	--LogDebug("1----------> m_blackList.MakeKey -> addressData: '%s' importInfo: %s", m_helpers.JSON_Encode(addressData), m_helpers.JSON_Encode(importInfo))
	local vcc = utf8.lower(importInfo.VCC)
	--local scope = utf8.lower(vcc == "system" and addressData.Scope or importInfo.ID)
	local scope = importInfo.ID
	local key = ("%s:%s:%s"):format(
		vcc,
		scope,
		addressData.Id
	)
	--LogDebug("2----------> m_blackList.MakeKey -> key: '%s'", key)
	return key
end

m_blackList.AssignAddressErrorToCSV = function(self, csvData, errorType)
	assert(type(csvData) == "table", "csvData must be a table")
	assert(type(errorType) == "string", "errorType must be a string")
	--LogDebug("[FULLCONTACT.blacklist:AssignAddressErrorToCSV] Assign error: '%s' to CSV.", tostring(errorType))
	if csvData.INVALIDATION_TYPE then
		csvData.INVALIDATION_TYPE = csvData.INVALIDATION_TYPE .. ". AND - "
	else
		csvData.INVALIDATION_TYPE = ""
	end
	csvData.INVALIDATION_TYPE = csvData.INVALIDATION_TYPE .. tostring(errorType)
end

m_blackList.PrepareAddressForBatch = function(self, vcc, addressData, importInfo, importFormat, csvData)
	assert(type(vcc) == "string", "vcc must be a string")
	assert(type(addressData) == "table", "address data must be a table")
	assert(type(importInfo) == "table", "importInfo must be a table")
	assert(type(importInfo.ID) == "string", "importInfo.ID info must be a string")
	assert(type(importInfo.VCC) == "string", "importInfo.VCC info must be a string")
	assert(type(importFormat) == "table", "importFormat must be a table")
	assert(type(csvData) == "table", "csvData must be a table")
	--LogDebug("PREPARING CONTACT FOR BATCH DIALER addressId: '%s', addressData.Name: '%s', importId: '%s', importVCC: '%s'.", addressData.ID, addressData.NAME, importInfo.ID, importInfo.VCC)
	--LogDebug("----------> PREPARING New DIALER address: addressId:  '%s', address: '%s'", addressData.ID, m_helpers.JSON_Encode(address))
	local mustAdd, err = self:CanAddAddress(addressData.ID, vcc, importFormat, csvData, importInfo)
	if not mustAdd then
		-- no paso la validacion, no lo puedo agregar
		self:AssignAddressErrorToCSV(csvData, err.type)
		return false, addressData.ID, err
	end
	-- paso la validacion, genero la info del address para agregarlo.
	local address = self:MakeAddressInfo(addressData, importInfo)
	return true, addressData.ID, address
end

m_blackList.SaveAddressesBatch = function(self, vcc, addressesData, importInfo)
	assert(type(vcc) == "string", "vcc must be a string")
	assert(type(addressesData) == "table", "addresses data must be a table")
	--LogInfo("[FULLCONTACT.blacklist:SaveAddressesBatch] 1---> Adding Dialer - %s Addresses BATCH - vcc: %s.", #addressesData, vcc)
	-- LogDebug("[FULLCONTACT.blacklist:SaveAddressesBatch] 2 --> Adding Dialer Addresses BATCH - vcc: %s addressesData: %s", vcc, m_helpers.JSON_Encode(addressesData))
	if #addressesData == 0 then
		return false, {}
	end
	local now = LuaOrm.Date():toString()
	local addresses = {}
	for _, addressData in ipairs(addressesData) do
		local address = self:SanitizeAddressToSave(addressData.Id, vcc, addressData, now)
		table.insert(addresses, address)
	end
	--LogDebug("[FULLCONTACT.blacklist:SaveAddressesBatch] 3---> Adding Dialer - %s Sanitized Addresses BATCH - vcc: %s.", #addresses, vcc)
	local options = {
		DuplicateCheck  = importInfo.DuplicateCheck,
		DuplicateSolver = importInfo.DuplicateSolver
	}
	return m_model.BlackList:SaveAddressesBatch(vcc, addresses, options)
end

m_blackList.AddBlackListAddresses = function(self, importInfo, addressesData, parsedRawCSV, importFormat)
	assert(type(importInfo) == "table", "importInfo must be a table")
	assert(type(addressesData) == "table", "addressesData must be a table")
	assert(type(parsedRawCSV) == "table", "parsedRawCSV must be a table")
	assert(type(importFormat) == "table", "importFormat must be a table")

	local totalContacts = #addressesData
	LogInfo("[FULLCONTACT.blacklist:AddBlackListAddresses] ADD BLACKLIST BATCH. totalContacts: '%s'", totalContacts)

	--LogDebug("====> importInfo: %s", m_helpers.JSON_Encode(importInfo))

	-- start
	local statusInfo = {
		status = "RUNNING",
		action = "START PROCESS",
		added = 0,
		updated = 0,
		errors = 0,
		deleted = 0,
		total = totalContacts
	}

	local failedAddressesList = {}
	local keyspace = m_model.GetDatasource().cassandra:CassandraConnection("OutboundEngine")
	m_model.BlackList:SaveImportation(importInfo, statusInfo, nil, failedAddressesList, keyspace)

	local vcc = importInfo.VCC
	local newContacts = 0
	local updatedContacts = 0
	local contactErrors = 0
	local updateStatusCounter = m_configValues.importation.updateStatusCounter
	local counter = 0
	local timeStamp = os.time()
	--[[
		Funcion que inserta en la tabla de failed contacts, validando que NO supere el limite de failed contacts soportados por importacion.
	]]--
	local function InsertFailedContact (csvData)
		if not csvData then
			return
		end
		if #failedAddressesList >= MAXIMUM_NUMBER_OF_FAILED_ADDRESSES_TO_STORE then
			--LogDebug("[FULLCONTACT.blacklist:AddBlackListAddresses] .. InsertFailedContact --> Unable to store more failed contacts in importation: '%s'. MAXIMUM_NUMBER_OF_FAILED_ADDRESSES_TO_STORE: %s", importInfo.ID, MAXIMUM_NUMBER_OF_FAILED_ADDRESSES_TO_STORE)
			return
		end
		csvData.timeStamp = timeStamp
		table.insert(failedAddressesList, csvData)
	end

	local addressesBatch = {}
	local csvContactsDataBatch = {}
	local duplicatedContactsDataBatch = {} -- tabla utilizada para poder realizar reload de los contactos en el controller de la app "outbound_engine."
	local batchNumber = 0

	local function SaveAddressesBatch ()
		batchNumber = batchNumber + 1
		local rowsInBatch = #addressesBatch
		local totalRows = rowsInBatch * batchNumber

		LogInfo("[FULLCONTACT.blacklist:AddBlackListAddresses] SaveAddressesBatch - batchNumber: '%s', rowsInBatch: '%s', total: '%s'.", batchNumber, rowsInBatch, totalRows)
		--LogDebug("[FULLCONTACT.blacklist:AddBlackListAddresses] 2--> SaveAddressesBatch - batchNumber: %s', addressesBatch: '%s'.", batchNumber, json.encode(addressesBatch))
		--LogDebug("[FULLCONTACT.blacklist:AddBlackListAddresses] 3--> SaveAddressesBatch - failedAddressesList: '%s'.", json.encode(failedAddressesList))

		--1. envio a guardar el batch de N contactos
		--
		local success, result, detail = m_blackList:SaveAddressesBatch(vcc, addressesBatch, importInfo)
		--LogDebug("[FULLCONTACT.blacklist:AddBlackListAddresses]  -- SaveAddressesBatch - success '%s' result: '%s' detail: '%s'", success, json.encode(result or {}), json.encode(detail or {}))
		if success then
			-- 2. si es ok, actualiza los contadores (created, updated y errors + los invalid para descargar el CSV)
			--
			--LogDebug("[FULLCONTACT.blacklist:AddBlackListAddresses]  success --> result: '%s'.", json.encode(result))
			--
			local previousContactHasDeleted = {isDeleted = false, index = -1}
			for i, item in ipairs(result.items or {}) do
				--LogDebug("[FULLCONTACT.blacklist:AddBlackListAddresses]     %d success --> item: '%s'.", i, json.encode(item))
				if item.delete then
					if (item.delete.result or ""):lower() == "deleted" then
						previousContactHasDeleted.isDeleted = true
						previousContactHasDeleted.index = i
					end
				elseif item.create then
					if (item.create.result or ""):lower() == "created" then
						if previousContactHasDeleted.isDeleted and previousContactHasDeleted.index == (i -1) then
							-- como contra ES en la actualizacion de un address, primero se elimina y se vuelve a crear, tenemos que realizar esta pisada para los contadores.
							statusInfo.updated = statusInfo.updated + 1
							previousContactHasDeleted.isDeleted = false
							previousContactHasDeleted.index = -1
						else
							statusInfo.added = statusInfo.added + 1
						end
					else
						statusInfo.errors = statusInfo.errors + 1
						--LogDebug("  1 - invoke InsertFailedContact. item.create: %s ", json.encode(item.create))
						self:AssignAddressErrorToCSV(csvContactsDataBatch[i], "FAIL: ALREADY EXISTS")
						InsertFailedContact(csvContactsDataBatch[i])
					end
				elseif item.update then
					if (item.update.result or ""):lower() == "updated" then
						statusInfo.updated = statusInfo.updated + 1
					else
						statusInfo.errors = statusInfo.errors + 1
						--LogDebug("  2 - invoke InsertFailedContact. item.update: %s ", json.encode(item.update))
						self:AssignAddressErrorToCSV(csvContactsDataBatch[i], "FAIL: UNABLE TO UPDATE")
						InsertFailedContact(csvContactsDataBatch[i])
					end
				end
			end
			-- caso KEEP OLD
			if detail then
				statusInfo.updated = statusInfo.updated + (detail.updated or 0)
				if type(detail.duplicatedContactsInfo) == "table" then
					table.insert(duplicatedContactsDataBatch, detail.duplicatedContactsInfo)
				end
			end
		else
			-- 3. si es error, actualiza los contadores (errors + los invalid para descargar el CSV)
			--
			statusInfo.errors = statusInfo.errors + #addressesBatch
			for i, csvFailedContact in ipairs(csvContactsDataBatch) do
				--LogDebug("  3 - invoke InsertFailedContact. %d - csvFailedContact: %s", i, json.encode(csvFailedContact))
				self:AssignAddressErrorToCSV(csvFailedContact, "FAIL: UNABLE TO SAVE BATCH ADDRESSES")
				InsertFailedContact(csvFailedContact)
			end
		end

		-- 4. actualiza el status de la importacion (redis, cassandra)
		addressesBatch = {}
		csvContactsDataBatch = {}
		m_model.BlackList:SaveImportation(importInfo, statusInfo, nil, failedAddressesList, keyspace)
		--
		return
	end

	local currentContactIndex = 0
	local function AddAddressToImportBatch (address, csvData)
		local ok, addressId, contactInfo = m_blackList:PrepareAddressForBatch(vcc, address, importInfo, importFormat, csvData)
		--LogDebug("[FULLCONTACT.blacklist:AddBlackListAddresses] 4--> AddAddressToImportBatch --> ok %s, addressId: %s of batchNumber: %s", ok, addressId, (batchNumber + 1))
		if ok then
			table.insert(addressesBatch, contactInfo)
			table.insert(csvContactsDataBatch, csvData) -- utilizado para el download de failed csv
		else
			--LogDebug("  4 - invoke InsertFailedContact.")
			InsertFailedContact(csvData)
			statusInfo.errors = statusInfo.errors + 1
		end
		--LogDebug("[FULLCONTACT.blacklist:AddBlackListAddresses] 4.1--> AddAddressToImportBatch - batchNumber: %s', statusInfo.errors: '%s'.", (batchNumber + 1), statusInfo.errors)
		if (batchNumber + 1) == 1 and #addressesBatch == 0 and statusInfo.errors >= MAXIMUM_FIRST_FAILED_ADDRESSES_FOR_BREAK_IMPORT then
			-- Si los primeros N contactos de la importacion han fallado, debe cancelar la misma.
			LogWarning("[FULLCONTACT.blacklist:AddBlackListAddresses]--> BREAK Import '%s'. The first %s contacts have been unsuccessful. BatchNumber: %s", importInfo.ID, MAXIMUM_FIRST_FAILED_ADDRESSES_FOR_BREAK_IMPORT, (batchNumber + 1))
			return {status = "BREAK IMPORT", reason = "THE FIRST ADDRESSES HAVE BEEN UNSUCCESSFUL", MAXIMUM_FIRST_FAILED_ADDRESSES_FOR_BREAK_IMPORT = MAXIMUM_FIRST_FAILED_ADDRESSES_FOR_BREAK_IMPORT}
		end
		if #addressesBatch == updateStatusCounter then
			statusInfo.action = "UPDATED ADDRESSES: " .. tostring(currentContactIndex)
			-- cuando llega al limite del bulk (ej: 500) envia a guardar.
			SaveAddressesBatch()
		end
		return statusInfo
	end

	local function Start ()
		for i, address in ipairs(addressesData) do
			currentContactIndex = i
			local csvAddressData = parsedRawCSV[i]
			address.ID = utf8.lower(address.ID or uuid.new())
			local result = AddAddressToImportBatch(address, csvAddressData)
			--LogDebug("[FULLCONTACT.blacklist:AddBlackListAddresses] --> result: %s", m_helpers.JSON_Encode(result))
			if result and result.status == "BREAK IMPORT" then
				return result
			end
		end
	end

	local result = Start()
	--LogDebug("[FULLCONTACT.blacklist:AddBlackListAddresses] --> RESULT: %s", m_helpers.JSON_Encode(result))

	statusInfo.status = "COMPLETE"
	statusInfo.action = "ENDING"
	local success

	if not result or result.status ~= "BREAK IMPORT" then
		success = true
		SaveAddressesBatch()
		LogInfo("[FULLCONTACT.blacklist:AddBlackListAddresses] RESULT: Total DIALER Contacts: %s - newContacts: %s - updatedContacts: %s - contactErrors: %s", statusInfo.total, statusInfo.added, statusInfo.updated, statusInfo.errors)
	else
		success = false
		statusInfo.code = -106
		statusInfo.result = result
		m_model.BlackList:SaveImportation(importInfo, statusInfo, nil, failedAddressesList, keyspace)
		LogInfo("[FULLCONTACT.blacklist:AddBlackListAddresses] ABORTING IMPORT. statusInfo.result: %s ", json.encode(statusInfo.result or {}))
	end

	keyspace:Close()
	statusInfo.duplicatedContactsDataBatch = duplicatedContactsDataBatch
	return success, statusInfo
end

--------------------------------------------------
-- cassandra model
--------------------------------------------------

m_blackList.GetImportation = function(self, importId, vcc, ksOE)
	assert(type(importId) == "string", "importId must be a string")
	vcc = type(vcc) == "string" and vcc or "system"
	importId = m_helpers.AddVCC(importId, vcc)
	local success, importInfo = m_model.BlackList.GetImportation(importId, nil, ksOE)
	if not success then
		return false
	end
	return true, importInfo
end

--[[
]]--
m_blackList.ListImportations = function(self, params)
	assert(type(params) == "table", "parmas must be a table")
	return m_model.BlackList.ListImportations(params)
end

--[[
]]--
m_blackList.ListVccAndSystemImportations = function(self, params)
	assert(type(params) == "table", "parmas must be a table")
	assert(type(params.vcc) == "string", "parmas must be a string")
	params.Type = params.Type or "PHONE"
	-- funcion que mergea los arrays
	local mergedBlacklists = {}
	local function Merge (success, list)
		if not success then
			return
		end
		for i, info in ipairs(list) do
			table.insert(mergedBlacklists, info)
		end
	end
	-- 1. lista las importaciones del VCC
	local success, vccBlacklists = m_blackList:ListImportations(params)
	--LogDebug("[FULLCONTACT.blacklist:ListVccAndSystemImportations] 1- Vcc: %s,  blacklists: %s", params.vcc, json.encode(vccBlacklists or {}))
	-- 2. mergea las importaciones del VCC
	Merge(success, vccBlacklists)
	-- 3. lista las importaciones del Sistema (global)
	params.vcc = "system"
	local success, systemBlacklists = m_blackList.Global:ListImportations()
	--LogDebug("[FULLCONTACT.blacklist:ListVccAndSystemImportations] 2- System Vcc, blacklists: %s", json.encode(systemBlacklists or {}))
	-- 3. mergea las importaciones del Sistema
	Merge(success, systemBlacklists)
	--LogDebug("[FULLCONTACT.blacklist:ListVccAndSystemImportations] 3- mergedBlacklists: %s", json.encode(mergedBlacklists or {}))
	-- 4. retonra las 3 listas (mergeadas y por separado)
	return true, mergedBlacklists, vccBlacklists, systemBlacklists
end

--[[
]]--
m_blackList.ParseScopeId = function(self, scopeId)
	assert(type(scopeId) == "string")
	local separatorPos = string.find(scopeId, ":")
	if not separatorPos then
		--LogDebug("[FULLCONTACT.blacklist:ParseScopeId] *--> scopeId: %s', vcc: %s', id: %s'.", scopeId, scopeId, scopeId)
		return scopeId, scopeId
	end
	local vcc = string.sub(scopeId, 1, separatorPos - 1)
	local id = string.sub(scopeId, separatorPos + 1)
	--LogDebug("[FULLCONTACT.blacklist:ParseScopeId] --> scopeId: %s', vcc: %s', id: %s'.", scopeId, vcc, id)
	return vcc, id
end

--[[
	funcion que valida la lista de blacklists posteada por el usuario, si existe en la base datos (vcc o system)
]]--
m_blackList.ValidateBlacklists = function(self, vcc, postedBlacklists)
	LogInfo("[FULLCONTACT.blacklist:ValidateBlacklists] Validating posted blacklist. Vcc: %s, enabled Validation: %s, postedBlacklists: %s", vcc, m_enabledContactsImportsValidations, json.encode(postedBlacklists or {}))
	if not m_enabledContactsImportsValidations then
		-- ok - NO valida - la validacion se encuentra desactivada
		return true, {}, {}
	end
	if type(postedBlacklists) ~= "table" then
		return true, {}, {}
	end
	-- 1. a la blacklist posteada del usuario, la paso a minusculas para realizar validaciones
	local userBlacklists = {}
	for scopeId, info in pairs(postedBlacklists) do
		local scopeVcc, scopeId = m_blackList:ParseScopeId(scopeId)
		local fullBlackListId = ("%s@%s"):format(scopeId, scopeVcc):lower()
		--LogDebug("[FULLCONTACT.blacklist:ValidateBlacklists] --> fullBlackListId: %s'.", fullBlackListId)
		userBlacklists[fullBlackListId] = info
	end
	-- 2. obtengo de la BD las blacklists del vcc y las del sistema
	local success, mergedBlacklists, vccBlacklists, systemBlacklists = self:ListVccAndSystemImportations({vcc = vcc})
	local sanitizedBlacklists = {} -- lista con las blacklist posteadas que existen en la base de datos
	-- funcion que valida si los user blacklists posteados existen en la base de datos
	local function Validate (list)
		for _, info in ipairs(list) do
			local fullBlackListId = m_helpers.AddVCC(info.ID, info.VCC):lower()
			if userBlacklists[fullBlackListId] then
				local blackListInfo = m_helpers.MergeTables(info, userBlacklists[fullBlackListId])
				sanitizedBlacklists[fullBlackListId] = blackListInfo
				userBlacklists[fullBlackListId] = nil
			end
		end
	end
	-- 3. valido las listas
	Validate(vccBlacklists)    -- la lista del vcc
	Validate(systemBlacklists) -- la lista del sistema
	-- 4 - me quedo con la lista de blacklists del usuario posteado, que NO existen en la base de datos.
	local unrecognizedBlacklists = userBlacklists
	LogInfo("[FULLCONTACT.blacklist:ValidateBlacklists] 1 --> sanitizedBlacklists: %s", json.encode(sanitizedBlacklists or {}))
	if next(unrecognizedBlacklists) then
		LogWarning("[FULLCONTACT.blacklist:ValidateBlacklists] Unrecognized Blacklists: %s", json.encode(unrecognizedBlacklists or {}))
	end
	-- ok
	return true, sanitizedBlacklists, unrecognizedBlacklists
end

--[[

]]--
m_blackList.ValidateAddressInBlackLists = function(self, addressId, blacklists)
	if not m_enabledContactsImportsValidations then
		-- ok - NO valida - la validacion se encuentra desactivada
		return false, {}
	end
	assert(addressId)
	--LogDebug("---------------------------------")
	--LogDebug("[FULLCONTACT:blacklist.ValidateAddressInBlackLists] Validating if the addressId '%s' is on blacklists: %s", addressId, json.encode(blacklists))
	for fullBlackListId, blacklistInfo in pairs(blacklists or {}) do
		--LogDebug("[FULLCONTACT:blacklist.ValidateAddressInBlackLists]   1 --> fullBlackListId: '%s' is on blacklistInfo: %s", fullBlackListId, json.encode(blacklistInfo))
		local blackListId = blacklistInfo.ID
		local blackListVCC = blacklistInfo.VCC
		local blackListAddressType = blacklistInfo.AddressType
		local success, response = m_blackList:GetAddress(addressId, blackListId, blackListVCC, blackListAddressType)
		--LogDebug("[FULLCONTACT:blacklist.ValidateAddressInBlackLists]  2 success: '%s' response: %s", success, json.encode(response))
		-- if success and response.total.value > 0 then
		if success and response then
			---- en uso
			--LogDebug("---------------------------------")
			--local info = (response.addresses or {})[1]
			local addressInfo = response
			return true, {
				reason = addressInfo and ("blacklistId: %s@%s"):format(addressInfo.ImportId, addressInfo.VCC) or ""
			}
		end
	end
	---- ok - NO se encuentra en uso
	--LogDebug("---------------------------------")
	return false, {}
end

--[[
]]--
m_blackList.RemoveImport = function(self, importId, vcc, userId, ksOE)
	assert(type(importId) == "string", "importId must be a string")
	vcc = type(vcc) == "string" and vcc or "system"
	importId = m_helpers.AddVCC(importId, vcc):lower()
	m_helpers.memory:ShowMemory()

	local msg = string.format("[FULLCONTACT.blacklist:RemoveImport] + STARTING REMOVING IMPORTATION '%s'", importId)
	local processId = m_helpers.elapsedTime:Start(msg)
	local success, err = m_model.BlackList.RemoveImport(importId, userId, ksOE)
	local msg = string.format("[FULLCONTACT.blacklist:RemoveImport] - END REMOVING IMPORTATION '%s'", importId)
	m_helpers.elapsedTime:End(processId, msg)

	m_helpers.memory:CollectAllMemory()
	if not success then
		return false, err
	end
	return true
end

--[[
]]--
m_blackList.SearchAddresses = function(self, params)
	assert(type(params) == "table", "parmas must be a table")
	assert(type(params.filter) == "table", "params.filter must be a table")
	return m_model.BlackList:SearchAddresses(params)
end

--[[
]]--
m_blackList.GetAddress = function(self, id, blackListId, vcc, addressType)
	assert(type(id) == "string", "id must be a string")
	assert(type(blackListId) == "string", "fullBlackListId must be a string")
	assert(type(vcc) == "string", "vcc must be a string")
	assert(type(addressType) == "string", "addressType must be a string")
	--LogDebug("[FULLCONATCT.blacklist:GetAddress] 1 - id: '%s', blackListId: '%s', vcc: '%s', addressType: '%s'.", id, blackListId, vcc, addressType)
	local addressData = {
		Id = m_blackList:MakeAddressId(id, addressType)--,
		--Scope = "algo"
	}
	local importInfo = {
		VCC = vcc,
		ID = blackListId:lower()
	}
	--LogDebug("[FULLCONATCT.blacklist:GetAddress] 2 - addressData: '%s', importInfo: '%s'.", json.encode(addressData), json.encode(importInfo))
	local addressESKey = m_blackList:MakeKey(addressData, importInfo)
	--LogDebug("[FULLCONATCT.blacklist:GetAddress] 3 - addressESKey: '%s'.", addressESKey)
	local success, addressInfo = m_model.BlackList:GetAddress(addressESKey)
	--LogDebug("[FULLCONATCT.blacklist:GetAddress] 4 - success: '%s', addressInfo: '%s'.", success, json.encode(addressInfo))
	return success, addressInfo
end

--[[
]]--
m_blackList.GetImportFailedAddresses = function(self, importId, vcc, updateId)
	assert(type(importId) == "string", "importId must be a string")
	assert(type(updateId) == "string", "updateId must be a string")
	vcc = type(vcc) == "string" and vcc or "system"
	importId = m_helpers.AddVCC(importId, vcc)
	m_helpers.memory:ShowMemory()
	local success, importInfo = m_model.BlackList.GetImportation(importId)
	if not success then
		local err = {
			result = "IMPORT DOES NOT EXIST",
			importId = importId,
			type = "1"
		}
		return false, err
	end
	if #(importInfo.Detail or {}) == 0 then
		local err = {
			result = "IMPORT NOT HAS UPDATES",
			importId = importId,
			type = "2"
		}
		return false, err
	end
	updateId = updateId:lower()
	local currentUpdate
	for _, detail in ipairs(importInfo.Detail) do
		if (detail.DetailId or ""):lower() == updateId then
			currentUpdate = detail
			break
		end
	end
	if not currentUpdate then
		local err = {
			result = "IMPORT UPDATE DOES NOT EXIST",
			importId = importId,
			updateId = updateId,
			type = "3"
		}
		return false, err
	end
	-- obtengo los failed addresses de una actualización específica de la importación.
	local success, failedAddressesList = m_model.BlackList.GetImportFailedAddresses(importId, updateId)
	if not success or m_helpers.TableIsEmpty(failedAddressesList) then
		local err = {
			result = "IMPORT UPDATE NOT HAS FAILED ADDRESSES",
			importId = importId,
			updateId = updateId,
			type = "4"
		}
		return false, err
	end
	-- ok
	local csvFailedAddresses = m_helpers.csv:Table2Csv(failedAddressesList)
	m_helpers.memory:CollectAllMemory()
	local info = {
		updateInfo          = currentUpdate,
		failedAddressesList = failedAddressesList,
		csvFailedAddresses  = csvFailedAddresses,
		csvSize             = #csvFailedAddresses
	}
	return true, info
end

--[[
]]--
m_blackList.GetCsvAllowedColumns = function(self)
	--LogDebug("[FULLCONATCT.blacklist:GetCsvAllowedColumns] Loading csv allowed columns")
	local success, importFormat = m_blackList:GetImportFormat()
	if not success then
		local errType = importFormat
		local err = {reason = errType .. " " .. addressType, type = errType}
		LogError("[FULLCONTACT.blacklist:GetCsvAllowedColumns] Error retrieving csv allowed columns. Err: %s", err.reason)
		return false, err
	end
	local columns = {}
	for _, field in ipairs(importFormat.ImportFormatFields) do
		table.insert(columns, {
			Name        = field.Name,
			DisplayName = field.DisplayName,
			FieldType   = field.FieldType,
			IsRequired  = field.IsRequired,
			IsHidden    = field.IsHidden
		})
	end
	return true, columns
end

--[[
]]--
m_blackList.AddRelatedCampaignsAccounts = function(self, vcc, campaignId, accountId, oldBlacklists, newBlacklists, userId, ksOE)
	assert(type(vcc) == "string", "vcc must be a string")
	assert(type(campaignId) == "string", "campaignId must be a string")
	assert(type(accountId) == "string", "accountId must be a string")
	assert(type(oldBlacklists) == "table", "oldBlacklists must be a table")
	assert(type(newBlacklists) == "table", "newBlacklists must be a table")
	LogInfo("[FULLCONATCT.blacklist:AddRelatedCampaignsAccounts] ----> vcc: %s, campaignId: %s, accountId: %s, oldBlacklists: %s, newBlacklists: %s.", vcc, campaignId, accountId, json.encode(oldBlacklists), json.encode(newBlacklists))
	return m_model.BlackList:AddRelatedCampaignsAccounts(vcc, campaignId, accountId, oldBlacklists, newBlacklists, userId, ksOE)
end

--[[
]]--
m_blackList.RemoveRelatedCampaignsAccounts = function(self, vcc, campaignId, accountId, blacklists, userId, ksOE)
	assert(type(vcc) == "string", "vcc must be a string")
	assert(type(campaignId) == "string", "campaignId must be a string")
	assert(type(blacklists) == "table", "blacklists must be a table")
	LogInfo("[FULLCONATCT.blacklist:RemoveRelatedCampaignsAccounts] ----> vcc: %s, campaignId: %s, accountId: %s, blacklists: %s.", vcc, campaignId, accountId, json.encode(blacklists))
	return m_model.BlackList:RemoveRelatedCampaignsAccounts(vcc, campaignId, accountId, blacklists, userId, ksOE)
end

--[[
 -------------------------------------------------------------------
	GLOBAL
 -------------------------------------------------------------------
]]--

--[[
]]--
m_blackList.Global.StartImport = function(self, importInfo, csvContent, onImported)
	assert(type(importInfo) == "table", "vcc must be a table")
	importInfo.vcc = "system"
	importInfo.AddressType = importInfo.AddressType or "PHONE"
	importInfo.ScopeType = "global"
	LogInfo("[FULLCONATCT.blacklist.Global:StartImport] ---> START Blacklist GLOBAL importId: '%s', addresses Type: '%s'.", importInfo.ID, importInfo.AddressType)
	return m_blackList:StartImport(importInfo, csvContent, onImported)
end

--[[
]]--
m_blackList.Global.ListImportations = function(self, params)
	params = params or {}
	params.vcc = "system"
	params.AddressType = params.AddressType or "PHONE"
	LogInfo("[FULLCONATCT.blacklist.Global:ListImportations] ---> List Importations of Blacklist GLOBAL.")
	return m_blackList:ListImportations(params)
end

--[[
]]--
m_blackList.Global.RemoveImport = function(self, importId, userId, ksOE)
	assert(type(importId) == "string", "importId must be a string")
	LogInfo("[FULLCONATCT.blacklist.Global:RemoveImport] ---> Removing GLOBAL Blacklist importation: %s.", importId)
	vcc = "system"
	return m_blackList:RemoveImport(importId, vcc, userId, ksOE)
end

--[[
]]--
m_blackList.Global.SearchAddresses = function(self, params)
	assert(type(params) == "table", "parmas must be a table")
	assert(type(params.filter) == "table", "params.filter must be a table")
	LogInfo("[FULLCONATCT.blacklist.Global:SearchAddresses] ---> Searching GLOBAL Blacklists addresses. params: %s", json.encode(params))
	params.filter.VCC = {exact = true, values = {"system"}}
	return m_blackList:SearchAddresses(params)
end

------------------------------------------
--
-- Exported functions
--
return {
	Init                           = m_blackList.Init,
	-- indices
	CreateESTemplate               = m_blackList.CreateESTemplate,
	CreateESIndex                  = m_blackList.CreateESIndex,
	DropESIndex                    = m_blackList.DropESIndex,
	GetESIndexName                 = m_blackList.GetESIndexName,
	-- importaciones
	StartImport                    = m_blackList.StartImport,
	RemoveImport                   = m_blackList.RemoveImport,
	GetImportation                 = m_blackList.GetImportation,
	ListImportations               = m_blackList.ListImportations,
	ListVccAndSystemImportations   = m_blackList.ListVccAndSystemImportations,
	GetImportFailedAddresses       = m_blackList.GetImportFailedAddresses,
	-- addresses
	GetAddress                     = m_blackList.GetAddress,
	SearchAddresses                = m_blackList.SearchAddresses,
	-- csv
	GetCsvAllowedColumns           = m_blackList.GetCsvAllowedColumns,
	-- relations
	AddRelatedCampaignsAccounts    = m_blackList.AddRelatedCampaignsAccounts,
	RemoveRelatedCampaignsAccounts = m_blackList.RemoveRelatedCampaignsAccounts,
	-- validate
	ValidateBlacklists             = m_blackList.ValidateBlacklists,
	ValidateAddressInBlackLists    = m_blackList.ValidateAddressInBlackLists,
	-- scope
	MakeScopeInfo                  = m_blackList.MakeScopeInfo,
	-- global / system
	Global = {
		StartImport                = m_blackList.Global.StartImport,
		RemoveImport               = m_blackList.Global.RemoveImport,
		ListImportations           = m_blackList.Global.ListImportations,
		SearchAddresses            = m_blackList.Global.SearchAddresses
	}
}
