module(..., package.seeall)

local m_icuregex = require "icu.regex"
local m_json     = require("json")
local m_table    = require "helpers.table"
local m_ustring  = require "icu.ustring"
local m_utils = require "helpers.utils"

-- Funciones de validación
local function result_match (inputString, strPattern, regexFlags)
	if not inputString then
		return true
	end
	if type(inputString) == "string" then inputString = m_ustring(inputString) end --Si no es string lo convierto
	local pattern = m_icuregex.compile(strPattern, regexFlags or "i")
	for _ in m_icuregex.gmatch(pattern, inputString) do
		return true
	end
	return false
end

local function ValidateRequired (val)
	return val and val ~= ""
end

local function ValidateID (val)
	return result_match(val, "^[\\-a-zA-Z0-9_.@]+$")
end

local function ValidateIDWithoutDots (val)
	return result_match(val, "^[\\-a-zA-Z0-9_\\@]+$")
end

local function ValidateHTMLMaliciousCode (val, options)
	if type(val) ~= "string" then
		return true
	end
	options = type(options) =="table" and options or nil
    local sanitized = m_utils.CleanUpHtml(val, options )
	--LogDebug("[HELPERS:Validation:ValidateHTMLMaliciousCode] original text %s, sanitized %s", val, sanitized)
	return sanitized:lower() == val:lower()
end

local function ValidateURL (val)
	local strPattern = [[^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$]]
	return result_match(val, strPattern)
end

local function ValidateIDWithSpaces (val)
	return result_match(val, "^(?!\\s)[\\-a-zA-Z0-9_\\s]+$")
end

local function ValidateIDWithSpacesAndCustomsCharacters (val)
	return result_match(val, "^(?!\\s)[\\-a-zA-Z0-9_\\u00C0-\\u017FñÑ\\s]+$")
end

local function ValidateIDWithoutDotsUndescore (val)
	return result_match(val, "^[\\-a-zA-Z0-9]+$")
end

local function ValidateMaxLength (value, info)
	assert(value)
	assert(type(info) == "table")
	assert(type(info.max) == "number")
	assert(type(info.reason) == "string")
	local currentLength = tostring(value):len()
	if currentLength <= info.max then
		--ok
		return true
	end
	-- err
	msgErr = info.reason:format(info.max, currentLength)
	return false, msgErr
end

-- Otras funciones de validación aquí...

-- Validaciones por tipos
local m_validationsTypes = {
	-- Agrega aquí otras funciones de validación por tipo...
	html_malicious_code             = ValidateHTMLMaliciousCode,
	id                              = ValidateID,
	id_with_spaces_and_without_dots = ValidateIDWithSpaces,
	id_with_spaces_customs_characters = ValidateIDWithSpacesAndCustomsCharacters,
	id_without_dots                 = ValidateIDWithoutDots,
	id_without_dots_underscore      = ValidateIDWithoutDotsUndescore,
	maxLength                       = ValidateMaxLength,
	required                        = ValidateRequired,
	url                             = ValidateURL
}

--[[
	Funcion para realizar un conjunto de validaciones sobre un el valor de un campo.
	Parametros:
		value: Valor del campo. (string)
		field_id: Id del campo. (Optional)
		validations: Tipos de validaciones y mensaje de error.
		{
			id = "Only letters, numbers, dashes and dots are allowed in the field %s.",
			html_malicious_code = "Possible malicious code in the field %s",
			custom = function( val )
				-- do something
			end
		}
]]--
function ValidateValue (self, value, validations, field_id)
	assert(validations)
	--LogDebug("[HELPERS:ValidateValue] field_id %s, value %s, validations %s", field_id, value, m_json.encode(validations))
	for val_id, val_reason in pairs(validations) do
		local result = true
		local msgErr
		if val_id == "custom" then
			result = val_reason(value)
		elseif m_validationsTypes[val_id] then
			result, msgErr = m_validationsTypes[val_id](value, val_reason)
		end
		if not result then
			msgErr = msgErr or (type(val_reason) == "table" and val_reason.reason) or (type(val_reason) == "string" and val_reason) or ""
			return false, {
				field_id      = field_id,
				reason        = type(msgErr) == "string" and msgErr:format(field_id) or nil,
				result        = "ERROR_FIELDS_VALIDATION",
				validation_id = val_id,
				value         = value or "NO_VALUE"
			}
		end
	end
	return true
end

--[[
	Funcion local para validar teniendo en cuenta si el valor es string o una tabla
]]--
local function validateFieldValues (self, field_value, def_val)
	local validations = def_val.validations
	local field_id = def_val.field_id
	local inDeep = def_val.inDeep

	--Si field_value es una tabla como string, hago decode ( caso disposition code )
	field_value = field_value and m_table:JSON_Decode(field_value) or field_value

	local isTable = type(field_value) == "table"
	local isArray = isTable and #field_value > 0
	--LogDebug("[HELPERS:ValidateFieldValues] field_value %s, validations %s, field_id %s, isTable %s, isArray %s", isTable and m_json.encode(field_value) or field_value, m_json.encode(validations), field_id, isTable, isArray)
	if isArray then
		-- Caso en el que el valor sea un array de objetos, valido para cada objeto
		for _, value in ipairs(field_value) do
			local resultValidation, err = validateFieldValues(self, value, def_val)
			if not resultValidation then
				return false, err
			end 
		end
	elseif isTable then
		-- Si no es profundidad, valido el valor de table.field_id
		if not inDeep then
			return validateFieldValues(self, field_value[field_id], def_val)
		end
		--[[
			Si es en profundidad, opcion 'inDeep', valido para cada campo de forma recursiva, si es igual al fieldId o es una tabla (array, object)
		]]--
		for key, value in pairs(field_value) do
			local resultValidation = true
			local err
			if key == field_id or type(value) == "table" then
				resultValidation, err = validateFieldValues(self, value, def_val)
			end
			if not resultValidation then
				return resultValidation, err
			end
		end
	else
		-- Caso en el que el valor sea string, nil etc
		local resultValidation, err = self:ValidateValue(field_value, validations, field_id)
		if not resultValidation then
			return false, err
		end
	end
	-- ok
	return true
end

--[[
	Función para realizar un conjunto de validaciones sobre diferentes campos de un json.

	Parametros:
		data: tabla LUA a validar
		field_id: Id del campo del json a validar.
		validations: Tipos de validaciones y mensaje de error.
		parent_field: Para indicar que los campos a validar tienen un campo padre. De lo contrario se realizan la validaciones
			a un primer nivel.  

		Ejemplo:
			def_validations :
				{
					{
						parent_field = "",
						field_id = "description",
						validations = {
							id = "Only letters, numbers, dashes and dots are allowed in the field %s.",
							html_malicious_code = "Possible malicious code in the field %s",
							custom = function( val )
								-- do something
							end
						}
					},
					{
						field_id = "name",
						validations = {
							required = "The field %s is required."
						}
					}
				}
]]--
local function ValidateJsonValues (self, data, validations)
	assert(data)
	assert(validations)
	assert(validations.fields)
	local dataValues = type(validations.GetEntity) == "function" and validations.GetEntity(data) or data

	--LogDebug("[HELPERS:ValidateJsonValues] data %s, validations %s", m_json.encode(data), m_json.encode(validations))
	--LogDebug("[HELPERS:ValidateJsonValues] --> dataValues %s", m_json.encode(dataValues))
	for _, def_val in ipairs(validations.fields) do --Itero por lass validaciones a realizar
		local resultValidation, err = validateFieldValues(self, dataValues, def_val)
		if not resultValidation then
			-- error
			return false, err
		end
	end
	-- ok
	return true
end

--[[

]]--
function StartValidateJsonValues (self, data, validations)
	assert(data)
	assert(validations)
	local entities = validations.isSimple and {data} or data
	for _, value in pairs(entities) do
		local isValid, err = ValidateJsonValues(self, value, validations)
		if not isValid then
			-- error
			return false, err
		end
	end
	-- ok
	return true
end
