/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/

#include "pp_error.h"

#include "zbxdbhigh.h"
#include "zbxstr.h"
#include "zbxvariant.h"
#include "zbxalgo.h"
#include "zbxdbschema.h"

/* mock field to estimate how much data can be stored in characters, bytes or both, */
/* depending on database backend                                                    */
typedef struct
{
	int	bytes_num;
	int	chars_num;
}
zbx_db_mock_field_t;

ZBX_PTR_VECTOR_IMPL(pp_result_ptr, zbx_pp_result_t *)

/******************************************************************************
 *                                                                            *
 * Purpose: set result value                                                  *
 *                                                                            *
 * Parameters: result    - [OUT] result to set                                *
 *             value     - [IN] field type in database schema                 *
 *             action    - [IN] on fail action                                *
 *             value_raw - [IN] value before applying on fail action if       *
 *                              non-default action was applied. This value is *
 *                              'moved' over to result.                       *
 *                                                                            *
 ******************************************************************************/
void	pp_result_set(zbx_pp_result_t *result, const zbx_variant_t *value, int action, zbx_variant_t *value_raw)
{
	zbx_variant_copy(&result->value, value);
	result->value_raw = *value_raw;
	zbx_variant_set_none(value_raw);
	result->action = action;
}

void	zbx_pp_result_free(zbx_pp_result_t *result)
{
	zbx_variant_clear(&result->value);
	zbx_variant_clear(&result->value_raw);
	zbx_free(result);
}

void	pp_free_results(zbx_pp_result_t *results, int results_num)
{
	for (int i = 0; i < results_num; i++)
	{
		zbx_variant_clear(&results[i].value);
		zbx_variant_clear(&results[i].value_raw);
	}

	zbx_free(results);
}

/******************************************************************************
 *                                                                            *
 * Purpose: initializes mock field                                            *
 *                                                                            *
 * Parameters: field      - [OUT] field data                                  *
 *             field_type - [IN] field type in database schema                *
 *             field_len  - [IN] field size in database schema                *
 *                                                                            *
 ******************************************************************************/
static void	zbx_db_mock_field_init(zbx_db_mock_field_t *field, int field_type, int field_len)
{
	switch (field_type)
	{
		case ZBX_TYPE_CHAR:
#if defined(HAVE_ORACLE)
			field->chars_num = field_len;
			field->bytes_num = 4000;
#else
			field->chars_num = field_len;
			field->bytes_num = -1;
#endif
			return;
	}

	THIS_SHOULD_NEVER_HAPPEN;

	field->chars_num = 0;
	field->bytes_num = 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: 'appends' text to the field, if successful the character/byte     *
 *           limits are updated                                               *
 *                                                                            *
 * Parameters: field - [IN/OUT] mock field                                    *
 *             text  - [IN] text to append                                    *
 *                                                                            *
 * Return value: SUCCEED - the field had enough space to append the text      *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 ******************************************************************************/
static int	zbx_db_mock_field_append(zbx_db_mock_field_t *field, const char *text)
{
	int	bytes_num, chars_num;

	if (-1 != field->bytes_num)
	{
		bytes_num = (int)strlen(text);
		if (bytes_num > field->bytes_num)
			return FAIL;
	}
	else
		bytes_num = 0;

	if (-1 != field->chars_num)
	{
		chars_num = (int)zbx_strlen_utf8(text);
		if (chars_num > field->chars_num)
			return FAIL;
	}
	else
		chars_num = 0;

	field->bytes_num -= bytes_num;
	field->chars_num -= chars_num;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: format value in text format                                       *
 *                                                                            *
 * Parameters: value     - [IN] value to format                               *
 *             value_str - [OUT] formatted value                              *
 *                                                                            *
 * Comments: Control characters are replaced with '.' and truncated if it's   *
 *           larger than ZBX_PP_VALUE_PREVIEW_LEN characters.                 *
 *                                                                            *
 ******************************************************************************/
static void	pp_error_format_value(const zbx_variant_t *value, char **value_str)
{
	const char	*value_desc;
	size_t		i, len;

#define ZBX_PP_VALUE_PREVIEW_LEN	100

	value_desc = zbx_variant_value_desc(value);

	if (ZBX_PP_VALUE_PREVIEW_LEN < zbx_strlen_utf8(value_desc))
	{
		/* truncate value and append '...' */
		len = zbx_strlen_utf8_nchars(value_desc, ZBX_PP_VALUE_PREVIEW_LEN - ZBX_CONST_STRLEN("..."));
		*value_str = zbx_malloc(NULL, len + ZBX_CONST_STRLEN("...") + 1);
		memcpy(*value_str, value_desc, len);
		memcpy(*value_str + len, "...", ZBX_CONST_STRLEN("...") + 1);
	}
	else
	{
		*value_str = zbx_malloc(NULL, (len = strlen(value_desc)) + 1);
		memcpy(*value_str, value_desc, len + 1);
	}

	/* replace control characters */
	for (i = 0; i < len; i++)
	{
		if (0 != iscntrl((*value_str)[i]))
			(*value_str)[i] = '.';
	}

#undef ZBX_PP_VALUE_PREVIEW_LEN
}

/******************************************************************************
 *                                                                            *
 * Purpose: format one preprocessing step result                              *
 *                                                                            *
 * Parameters: step   - [IN] preprocessing step number                        *
 *             result - [IN] preprocessing step result                        *
 *             out    - [OUT] formatted string                                *
 *                                                                            *
 ******************************************************************************/
static void	pp_error_format_result(int step, const zbx_pp_result_t *result, char **out)
{
	char	*actions[] = {"", " (discard value)", " (set value)", " (set error)"};

	if (ZBX_VARIANT_ERR != result->value.type)
	{
		char	*value_str;

		pp_error_format_value(&result->value, &value_str);
		*out = zbx_dsprintf(NULL, "%d. Result%s: %s\n", step, actions[result->action], value_str);
		zbx_free(value_str);
	}
	else
	{
		*out = zbx_dsprintf(NULL, "%d. Failed%s: %s\n", step, actions[result->action],
				zbx_variant_value_desc(&result->value));
	}
}

/******************************************************************************
 *                                                                            *
 * Purpose: format preprocessing error message                                *
 *                                                                            *
 * Parameters: value        - [IN] input value                                *
 *             results      - [IN] preprocessing step results                 *
 *             results_num  - [IN] number of executed steps                   *
 *             error        - [OUT] formatted error message                   *
 *                                                                            *
 ******************************************************************************/
void	pp_format_error(const zbx_variant_t *value, zbx_pp_result_t *results, int results_num, char **error)
{
	char			*value_str, *err_step;
	int			i;
	size_t			error_alloc = 512, error_offset = 0;
	zbx_vector_str_t	results_str;
	zbx_db_mock_field_t	field;

	zbx_vector_str_create(&results_str);

	/* add header to error message */
	*error = zbx_malloc(NULL, error_alloc);
	pp_error_format_value(value, &value_str);
	zbx_snprintf_alloc(error, &error_alloc, &error_offset, "Preprocessing failed for: %s\n", value_str);
	zbx_free(value_str);

	zbx_db_mock_field_init(&field, ZBX_TYPE_CHAR, ZBX_ITEM_ERROR_LEN);

	zbx_db_mock_field_append(&field, *error);
	zbx_db_mock_field_append(&field, "...\n");

	/* format the last (failed) step */
	pp_error_format_result(results_num, &results[results_num - 1], &err_step);
	zbx_vector_str_append(&results_str, err_step);

	if (SUCCEED == zbx_db_mock_field_append(&field, err_step))
	{
		/* format the first steps */
		for (i = results_num - 2; i >= 0; i--)
		{
			pp_error_format_result(i + 1, &results[i], &err_step);

			if (SUCCEED != zbx_db_mock_field_append(&field, err_step))
			{
				zbx_free(err_step);
				break;
			}

			zbx_vector_str_append(&results_str, err_step);
		}
	}

	/* add steps to error message */

	if (results_str.values_num < results_num)
		zbx_strcpy_alloc(error, &error_alloc, &error_offset, "...\n");

	for (i = results_str.values_num - 1; i >= 0; i--)
		zbx_strcpy_alloc(error, &error_alloc, &error_offset, results_str.values[i]);

	zbx_rtrim(*error, ZBX_WHITESPACE);

	/* truncate formatted error if necessary */
	if (ZBX_ITEM_ERROR_LEN < zbx_strlen_utf8(*error))
	{
		char	*ptr;

		ptr = (*error) + zbx_strlen_utf8_nchars(*error, ZBX_ITEM_ERROR_LEN - 3);
		for (i = 0; i < 3; i++)
			*ptr++ = '.';
		*ptr = '\0';
	}

	zbx_vector_str_clear_ext(&results_str, zbx_str_free);
	zbx_vector_str_destroy(&results_str);
}

/******************************************************************************
 *                                                                            *
 * Purpose: apply 'on fail' preprocessing error handler                       *
 *                                                                            *
 * Parameters: value - [IN/OUT]                                               *
 *             step  - [IN] preprocessing operation that produced error       *
 *                                                                            *
 ******************************************************************************/
int	pp_error_on_fail(zbx_dc_um_shared_handle_t *um_handle, zbx_uint64_t hostid, zbx_variant_t *value,
		const zbx_pp_step_t *step)
{
	char	*error_handler_params;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	switch (step->error_handler)
	{
		case ZBX_PREPROC_FAIL_DISCARD_VALUE:
			zbx_variant_clear(value);
			break;
		case ZBX_PREPROC_FAIL_SET_VALUE:
		case ZBX_PREPROC_FAIL_SET_ERROR:
			zbx_variant_clear(value);

			error_handler_params = zbx_strdup(NULL, step->error_handler_params);

			if (NULL != um_handle)
			{
				char	*error = NULL;

				if (SUCCEED != zbx_dc_expand_user_and_func_macros_from_cache(um_handle->um_cache,
						&error_handler_params, &hostid, 1, ZBX_MACRO_ENV_NONSECURE, &error))
				{
					zabbix_log(LOG_LEVEL_DEBUG, "cannot resolve user macros: %s", error);
					zbx_free(error);
				}
			}

			if (ZBX_PREPROC_FAIL_SET_VALUE == step->error_handler)
			{
				zbx_variant_set_str(value, error_handler_params);
			}
			else if (ZBX_PREPROC_FAIL_SET_ERROR == step->error_handler)
			{
				zbx_variant_set_error(value, error_handler_params);
			}
			else
			{
				THIS_SHOULD_NEVER_HAPPEN;
				zabbix_log(LOG_LEVEL_ERR, "unexpected \"custom on fail\" handler type %d",
						step->error_handler);
				zbx_free(error_handler_params);
			}

			break;
	}

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() value:%s", __func__, zbx_variant_value_desc(value));

	return step->error_handler;
}