/*
** Copyright (C) 2001-2026 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 "service_actions.h"
#include "zbxexpression.h"

#include "zbxnum.h"
#include "zbxdbhigh.h"
#include "zbxexpr.h"
#include "zbxstr.h"
#include "zbxalgo.h"

/******************************************************************************
 *                                                                            *
 * Purpose: matches service update by service id                              *
 *                                                                            *
 ******************************************************************************/
static int	condition_match_service(const zbx_service_action_condition_t *condition,
		const zbx_service_update_t *update)
{
	zbx_uint64_t	serviceid;

	if (SUCCEED != zbx_is_uint64(condition->value, &serviceid))
		return FAIL;

	return zbx_uint64match_condition(serviceid, update->service->serviceid, condition->op);
}

/******************************************************************************
 *                                                                            *
 * Purpose: matches service update by service name                            *
 *                                                                            *
 ******************************************************************************/
static int	condition_match_service_name(const zbx_service_action_condition_t *condition,
		const zbx_service_update_t *update)
{
	return  zbx_strmatch_condition(update->service->name, condition->value, condition->op);
}

/******************************************************************************
 *                                                                            *
 * Purpose: matches tag/tag+value using specified operator                    *
 *                                                                            *
 * Parameters: tags  - [IN] tags to match                                     *
 *             name  - [IN] target tag name                                   *
 *             value - [IN] target tag value (NULL if only tag name is being  *
 *                          matched                                           *
 *             op    - [IN] matching operator (ZBX_CONDITION_OPERATOR_*)      *
 *                                                                            *
 * Return value: SUCCEED - tags match                                         *
 *               FAIL    - otherwise                                          *
 *                                                                            *
 * Comments: When matching tag+value the operator is using only to match      *
 *           value - the tag name will be always matched as 'equal'.          *
 *                                                                            *
 ******************************************************************************/
static int	match_tags(const zbx_vector_service_tag_ptr_t *tags, const char *name, const char *value,
		unsigned char op)
{
	int	ret, expected_ret;

	if (ZBX_CONDITION_OPERATOR_EQUAL == op || ZBX_CONDITION_OPERATOR_LIKE == op)
	{
		expected_ret = SUCCEED;
		ret = FAIL;
	}
	else
	{
		expected_ret = FAIL;
		ret = SUCCEED;
	}

	for (int i = 0; i < tags->values_num; i++)
	{
		zbx_service_tag_t	*tag = (zbx_service_tag_t *)tags->values[i];

		if (NULL != value)
		{
			if (0 == strcmp(tag->name, name))
				ret = zbx_strmatch_condition(tag->value, value, op);
			else
				continue;
		}
		else
			ret = zbx_strmatch_condition(tag->name, name, op);

		if (expected_ret == ret)
			return ret;
	}

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: matches service update by service tag name                        *
 *                                                                            *
 ******************************************************************************/
static int	condition_match_service_tag(const zbx_service_action_condition_t *condition,
		const zbx_service_update_t *update)
{
	return match_tags(&update->service->tags, condition->value, NULL, condition->op);
}

/******************************************************************************
 *                                                                            *
 * Purpose: matches service update by service tag and its value               *
 *                                                                            *
 ******************************************************************************/
static int	condition_match_service_tag_value(const zbx_service_action_condition_t *condition,
		const zbx_service_update_t *update)
{
	return match_tags(&update->service->tags, condition->value2, condition->value, condition->op);
}

/******************************************************************************
 *                                                                            *
 * Purpose: matches service update by the specified condition                 *
 *                                                                            *
 ******************************************************************************/
static const char	*service_update_match_condition(const zbx_service_update_t *update,
		const zbx_service_action_condition_t *condition)
{
	int	ret;

	switch (condition->conditiontype)
	{
		case ZBX_CONDITION_TYPE_SERVICE:
			ret = condition_match_service(condition, update);
			break;
		case ZBX_CONDITION_TYPE_SERVICE_NAME:
			ret = condition_match_service_name(condition, update);
			break;
		case ZBX_CONDITION_TYPE_EVENT_TAG:
			ret = condition_match_service_tag(condition, update);
			break;
		case ZBX_CONDITION_TYPE_EVENT_TAG_VALUE:
			ret = condition_match_service_tag_value(condition, update);
			break;
		default:
			THIS_SHOULD_NEVER_HAPPEN;
			ret = FAIL;
	}

	return SUCCEED == ret ? "1" : "0";
}

/******************************************************************************
 *                                                                            *
 * Purpose: matches service update against specified action                   *
 *                                                                            *
 ******************************************************************************/
static int	service_update_match_action(const zbx_service_update_t *update, const zbx_service_action_t *action)
{
	int		index;
	size_t		pos = 0, last_pos = 0, expr_alloc = 0, expr_offset = 0;
	char		*expr = NULL, error[256];
	const char	*value;
	zbx_token_t	token;
	zbx_uint64_t	id;
	double		res;

	if (0 == action->conditions.values_num)
		return SUCCEED;

	for (; SUCCEED == zbx_token_find(action->formula, (int)pos, &token, ZBX_TOKEN_SEARCH_FUNCTIONID); pos++)
	{
		switch (token.type)
		{
			case ZBX_TOKEN_OBJECTID:
				if (SUCCEED == zbx_is_uint64_n(action->formula + token.data.objectid.name.l,
						token.data.objectid.name.r - token.data.objectid.name.l + 1, &id))
				{
					zbx_strncpy_alloc(&expr, &expr_alloc, &expr_offset,
							action->formula + last_pos, token.loc.l - last_pos);
					zbx_service_action_condition_t	zbx_service_action_condition_local =
							{.conditionid = id};

					if (FAIL != (index = zbx_vector_service_action_condition_ptr_search(
							&action->conditions, &zbx_service_action_condition_local,
							ZBX_DEFAULT_UINT64_PTR_COMPARE_FUNC)))
					{
						value = service_update_match_condition(update,
								action->conditions.values[index]);
					}
					else
						value = "0";

					zbx_strcpy_alloc(&expr, &expr_alloc, &expr_offset, value);

					last_pos = token.loc.r + 1;
				}
				pos = token.loc.r;
				break;
			case ZBX_TOKEN_MACRO:
			case ZBX_TOKEN_USER_MACRO:
			case ZBX_TOKEN_LLD_MACRO:
				pos = token.loc.r;
				break;
		}
	}

	zbx_strcpy_alloc(&expr, &expr_alloc, &expr_offset, action->formula + last_pos);

	if (FAIL == zbx_evaluate(&res, expr, error, sizeof(error), NULL))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "cannot evaluate action \"" ZBX_FS_UI64 "\" formula \"%s\": %s",
			action->actionid, action->formula, error);
		res = 0;
	}

	zbx_free(expr);

	return SUCCEED == zbx_double_compare(res, 0) ? FAIL : SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: matches service update against service actions                    *
 *                                                                            *
 * Parameters: update    - [IN] service update generated when service state   *
 *                              changes                                       *
 *             actions   - [IN] service actions                               *
 *             actionids - [OUT] matched action identifiers                   *
 *                                                                            *
 ******************************************************************************/
void	service_update_process_actions(const zbx_service_update_t *update, zbx_hashset_t *actions,
		zbx_vector_uint64_t *actionids)
{
	zbx_hashset_iter_t	iter;
	zbx_service_action_t	*action;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() serviceid:" ZBX_FS_UI64, __func__, update->service->serviceid);

	zbx_hashset_iter_reset(actions, &iter);
	while (NULL != (action = (zbx_service_action_t *)zbx_hashset_iter_next(&iter)))
	{
		if (SUCCEED == service_update_match_action(update, action))
			zbx_vector_uint64_append(actionids, action->actionid);
	}

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s() matched:%d", __func__, actionids->values_num);
}
