/*
** 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 "zbxmocktest.h"
#include "zbxmockdata.h"
#include "zbxmockassert.h"
#include "zbxmockutil.h"
#include "zbxalgo.h"
#include "zbxself.h"
#include "../../../src/zabbix_server/server_constants.h"

#include "mock_service.h"

zbx_uint64_t __wrap_zbx_dc_get_nextid(const char *table_name, int num);
void	*__wrap_zbx_add_event(unsigned char source, unsigned char object, zbx_uint64_t objectid,
		const zbx_timespec_t *timespec, int value, const char *trigger_description,
		const char *trigger_expression, const char *trigger_recovery_expression, unsigned char trigger_priority,
		unsigned char trigger_type, const zbx_vector_ptr_t *trigger_tags,
		unsigned char trigger_correlation_mode, const char *trigger_correlation_tag,
		unsigned char trigger_value, const char *trigger_opdata, const char *event_name, const char *error);
int	__wrap_zbx_process_events(zbx_vector_ptr_t *trigger_diff, zbx_vector_uint64_t *triggerids_lock);
void	__wrap_zbx_clean_events(void);
int	__wrap_zbx_interface_availability_is_set(const void *ia);
void	__wrap_zbx_rtc_subscribe(unsigned char proc_type, int proc_num, zbx_uint32_t *msgs, int msgs_num,
		int config_timeout, void *rtc);
void	__wrap_zbx_rtc_notify_finished_sync(int config_timeout, zbx_uint32_t code, const char *process_name,
		void *rtc);
void	__wrap_zbx_dc_set_itservices_num(int num);

void	__wrap_zbx_dc_set_itservices_num(int num)
{
	ZBX_UNUSED(num);
}

/* stubs to satisfy hard link dependenceies */

pid_t	*threads;
int	threads_num;

void	zbx_update_selfmon_counter(const zbx_thread_info_t *info, unsigned char state)
{
	ZBX_UNUSED(state);
	ZBX_UNUSED(info);
}

/* service tree mock */

typedef struct
{
	zbx_hashset_t	services;
}
zbx_mock_service_cache_t;

static zbx_mock_service_cache_t	cache;

static zbx_hash_t	service_hash_func(const void *d)
{
	const zbx_service_t	*s = (const zbx_service_t *)d;

	return ZBX_DEFAULT_STRING_HASH_FUNC(s->name);
}

static int	service_compare_func(const void *d1, const void *d2)
{
	const zbx_service_t	*s1 = (const zbx_service_t *)d1;
	const zbx_service_t	*s2 = (const zbx_service_t *)d2;

	return strcmp(s1->name, s2->name);
}

zbx_service_t	*mock_get_service(const char *name)
{
	zbx_service_t	service_local;

	service_local.name = (char *)name;
	return zbx_hashset_search(&cache.services, &service_local);
}

void	mock_init_service_cache(const char *path)
{
	zbx_mock_handle_t	hservices, hservice, hchildren, hparents, hname, hevents, hevent, halgo, hweight, hprop,
				hrules, hrule;
	int			service_num = 0;
	zbx_mock_error_t	err;
	zbx_service_t		*service, service_local, *child, *parent;
	const char		*value;
	zbx_hashset_iter_t	iter;

	zbx_hashset_create(&cache.services, 100, service_hash_func, service_compare_func);

	/* load service objects in cache */

	hservices = zbx_mock_get_parameter_handle(path);
	while (ZBX_MOCK_END_OF_VECTOR != (err = (zbx_mock_vector_element(hservices, &hservice))))
	{
		if (ZBX_MOCK_SUCCESS != err)
			fail_msg("cannot read service #%d", service_num);

		memset(&service_local, 0, sizeof(zbx_service_t));
		service_local.name = zbx_strdup(NULL, zbx_mock_get_object_member_string(hservice, "name"));
		service = (zbx_service_t *)zbx_hashset_insert(&cache.services, &service_local, sizeof(service_local));

		zbx_vector_service_ptr_create(&service->children);
		zbx_vector_service_ptr_create(&service->parents);
		zbx_vector_service_problem_tag_ptr_create(&service->service_problem_tags);
		zbx_vector_service_problem_ptr_create(&service->service_problems);
		zbx_vector_service_rule_ptr_create(&service->status_rules);
		zbx_vector_service_tag_ptr_create(&service->tags);

		service->status = zbx_mock_get_object_member_int(hservice, "status");

		if (ZBX_MOCK_SUCCESS == zbx_mock_object_member(hservice, "algorithm", &halgo))
		{
			if (ZBX_MOCK_SUCCESS != zbx_mock_string(halgo, &value))
				fail_msg("cannot read service '%s' algorithm", service->name);

			if (0 == strcmp(value, "MIN"))
				service->algorithm = ZBX_SERVICE_STATUS_CALC_MOST_CRITICAL_ONE;
			else if (0 == strcmp(value, "MAX"))
				service->algorithm = ZBX_SERVICE_STATUS_CALC_MOST_CRITICAL_ALL;
			else if (0 == strcmp(value, "OK"))
				service->algorithm = ZBX_SERVICE_STATUS_CALC_SET_OK;
			else
				fail_msg("unknown service '%s' algorithm '%s'", service->name, value);
		}
		else
			service->algorithm = ZBX_SERVICE_STATUS_CALC_MOST_CRITICAL_ONE;

		if (ZBX_MOCK_SUCCESS == zbx_mock_object_member(hservice, "weight", &hweight))
		{
			if (ZBX_MOCK_SUCCESS != zbx_mock_int(hweight, &service->weight))
				fail_msg("cannot read service '%s' weight", service->name);
		}

		if (ZBX_MOCK_SUCCESS == zbx_mock_object_member(hservice, "propagation", &hprop))
		{
			value = zbx_mock_get_object_member_string(hprop, "action");

			if (0 == strcmp(value, "SET"))
			{
				service->propagation_rule = ZBX_SERVICE_STATUS_PROPAGATION_FIXED;
				service->propagation_value = zbx_mock_get_object_member_int(hprop, "value");
			}
			else if (0 == strcmp(value, "KEEP"))
			{
				service->propagation_rule = ZBX_SERVICE_STATUS_PROPAGATION_AS_IS;
			}
			else if (0 == strcmp(value, "INCREASE"))
			{
				service->propagation_rule = ZBX_SERVICE_STATUS_PROPAGATION_INCREASE;
				service->propagation_value = zbx_mock_get_object_member_int(hprop, "value");
			}
			else if (0 == strcmp(value, "DECREASE"))
			{
				service->propagation_rule = ZBX_SERVICE_STATUS_PROPAGATION_DECREASE;
				service->propagation_value = zbx_mock_get_object_member_int(hprop, "value");
			}
			else if (0 == strcmp(value, "IGNORE"))
				service->propagation_rule = ZBX_SERVICE_STATUS_PROPAGATION_IGNORE;
			else
				fail_msg("unknown service '%s' propagation action '%s'", service->name, value);
		}

		if (ZBX_MOCK_SUCCESS == zbx_mock_object_member(hservice, "rules", &hrules))
		{
			zbx_service_rule_t	*rule;

			while (ZBX_MOCK_END_OF_VECTOR != (err = (zbx_mock_vector_element(hrules, &hrule))))
			{
				if (ZBX_MOCK_SUCCESS != err)
					fail_msg("cannot read service '%s' status rules", service->name);

				rule = (zbx_service_rule_t *)zbx_malloc(NULL, sizeof(zbx_service_rule_t));
				memset(rule, 0, sizeof(zbx_service_rule_t));
				value = zbx_mock_get_object_member_string(hrule, "type");
				if (0 == strcmp(value, "N_GE"))
					rule->type = ZBX_SERVICE_STATUS_RULE_TYPE_N_GE;
				else if (0 == strcmp(value, "NP_GE"))
					rule->type = ZBX_SERVICE_STATUS_RULE_TYPE_NP_GE;
				else if (0 == strcmp(value, "N_LT"))
					rule->type = ZBX_SERVICE_STATUS_RULE_TYPE_N_L;
				else if (0 == strcmp(value, "NP_LT"))
					rule->type = ZBX_SERVICE_STATUS_RULE_TYPE_NP_L;
				else if (0 == strcmp(value, "W_GE"))
					rule->type = ZBX_SERVICE_STATUS_RULE_TYPE_W_GE;
				else if (0 == strcmp(value, "WP_GE"))
					rule->type = ZBX_SERVICE_STATUS_RULE_TYPE_WP_GE;
				else if (0 == strcmp(value, "W_LT"))
					rule->type = ZBX_SERVICE_STATUS_RULE_TYPE_W_L;
				else if (0 == strcmp(value, "WP_LT"))
					rule->type = ZBX_SERVICE_STATUS_RULE_TYPE_WP_L;
				else
					fail_msg("unsupported service '%s' rule type '%s'", service->name, value);
				rule->limit_status = zbx_mock_get_object_member_int(hrule, "limit");
				rule->limit_value = zbx_mock_get_object_member_int(hrule, "value");
				rule->new_status = zbx_mock_get_object_member_int(hrule, "status");
				zbx_vector_service_rule_ptr_append(&service->status_rules, rule);
			}
		}

		if (ZBX_MOCK_SUCCESS == zbx_mock_object_member(hservice, "events", &hevents))
		{
			zbx_service_problem_t	*problem;

			while (ZBX_MOCK_END_OF_VECTOR != (err = (zbx_mock_vector_element(hevents, &hevent))))
			{
				if (ZBX_MOCK_SUCCESS != err)
					fail_msg("cannot read service '%s' events", service->name);

				problem = (zbx_service_problem_t *)zbx_malloc(NULL, sizeof(zbx_service_problem_t));
				memset(problem, 0, sizeof(zbx_service_problem_t));
				problem->eventid = zbx_mock_get_object_member_uint64(hevent, "id");
				problem->severity = zbx_mock_get_object_member_int(hevent, "severity");
				zbx_vector_service_problem_ptr_append(&service->service_problems, problem);
			}
		}

		service_num++;
	}

	/* set service relations */

	hservices = zbx_mock_get_parameter_handle(path);
	while (ZBX_MOCK_END_OF_VECTOR != (err = (zbx_mock_vector_element(hservices, &hservice))))
	{
		if (ZBX_MOCK_SUCCESS != err)
			fail_msg("cannot read service");

		if (NULL == (service = mock_get_service(zbx_mock_get_object_member_string(hservice, "name"))))
			fail_msg("failed to cache service '%s'", zbx_mock_get_object_member_string(hservice, "name"));

		if (ZBX_MOCK_SUCCESS == zbx_mock_object_member(hservice, "children", &hchildren))
		{
			while (ZBX_MOCK_END_OF_VECTOR != (err = (zbx_mock_vector_element(hchildren, &hname))))
			{
				if (ZBX_MOCK_SUCCESS != err || ZBX_MOCK_SUCCESS != zbx_mock_string(hname, &value))
					fail_msg("cannot read service '%s' children", service->name);

				if (NULL == (child = mock_get_service(value)))
				{
					fail_msg("cannot set service '%s' child '%s': no such service", service->name,
							value);
				}

				zbx_vector_service_ptr_append(&service->children, child);
				zbx_vector_service_ptr_append(&child->parents, service);
			}
		}

		if (ZBX_MOCK_SUCCESS == zbx_mock_object_member(hservice, "parents", &hparents))
		{
			while (ZBX_MOCK_END_OF_VECTOR != (err = (zbx_mock_vector_element(hparents, &hname))))
			{
				if (ZBX_MOCK_SUCCESS != err || ZBX_MOCK_SUCCESS != zbx_mock_string(hname, &value))
					fail_msg("cannot read service '%s' parents", service->name);

				if (NULL == (parent = mock_get_service(value)))
				{
					fail_msg("cannot set service '%s' parent '%s': no such service", service->name,
							value);
				}

				zbx_vector_service_ptr_append(&service->parents, parent);
				zbx_vector_service_ptr_append(&parent->children, service);
			}
		}

		service_num++;
	}

	zbx_hashset_iter_reset(&cache.services, &iter);

	/* remove duplicate parent/children references */
	while (NULL != (service = (zbx_service_t *)zbx_hashset_iter_next(&iter)))
	{
		zbx_vector_service_ptr_sort(&service->parents, ZBX_DEFAULT_PTR_COMPARE_FUNC);
		zbx_vector_service_ptr_uniq(&service->parents, ZBX_DEFAULT_PTR_COMPARE_FUNC);

		zbx_vector_service_ptr_sort(&service->children, ZBX_DEFAULT_PTR_COMPARE_FUNC);
		zbx_vector_service_ptr_uniq(&service->children, ZBX_DEFAULT_PTR_COMPARE_FUNC);
	}
}

void	mock_destroy_service_cache(void)
{
	zbx_hashset_iter_t	iter;
	zbx_service_t		*service;

	zbx_hashset_iter_reset(&cache.services, &iter);

	while (NULL != (service = (zbx_service_t *)zbx_hashset_iter_next(&iter)))
	{
		zbx_vector_service_ptr_destroy(&service->children);
		zbx_vector_service_ptr_destroy(&service->parents);
		zbx_vector_service_problem_tag_ptr_destroy(&service->service_problem_tags);
		zbx_vector_service_tag_ptr_destroy(&service->tags);
		zbx_vector_service_problem_ptr_clear_ext(&service->service_problems, zbx_service_problem_free);
		zbx_vector_service_problem_ptr_destroy(&service->service_problems);
		zbx_vector_service_rule_ptr_clear_ext(&service->status_rules, zbx_service_rule_free);
		zbx_vector_service_rule_ptr_destroy(&service->status_rules);

		zbx_free(service->name);
	}

	zbx_hashset_destroy(&cache.services);
}

/* function stubs to cut off library dependencies */

zbx_uint64_t	__wrap_zbx_dc_get_nextid(const char *table_name, int num)
{
	ZBX_UNUSED(table_name);
	ZBX_UNUSED(num);

	return 0;
}

void	*__wrap_zbx_add_event(unsigned char source, unsigned char object, zbx_uint64_t objectid,
		const zbx_timespec_t *timespec, int value, const char *trigger_description,
		const char *trigger_expression, const char *trigger_recovery_expression, unsigned char trigger_priority,
		unsigned char trigger_type, const zbx_vector_ptr_t *trigger_tags,
		unsigned char trigger_correlation_mode, const char *trigger_correlation_tag,
		unsigned char trigger_value, const char *trigger_opdata, const char *event_name, const char *error)
{
	ZBX_UNUSED(source);
	ZBX_UNUSED(object);
	ZBX_UNUSED(objectid);
	ZBX_UNUSED(timespec);
	ZBX_UNUSED(value);
	ZBX_UNUSED(trigger_description);
	ZBX_UNUSED(trigger_expression);
	ZBX_UNUSED(trigger_recovery_expression);
	ZBX_UNUSED(trigger_priority);
	ZBX_UNUSED(trigger_type);
	ZBX_UNUSED(trigger_tags);
	ZBX_UNUSED(trigger_correlation_mode);
	ZBX_UNUSED(trigger_correlation_tag);
	ZBX_UNUSED(trigger_value);
	ZBX_UNUSED(trigger_opdata);
	ZBX_UNUSED(event_name);
	ZBX_UNUSED(error);

	return NULL;
}

int	__wrap_zbx_process_events(zbx_vector_ptr_t *trigger_diff, zbx_vector_uint64_t *triggerids_lock)
{
	ZBX_UNUSED(trigger_diff);
	ZBX_UNUSED(triggerids_lock);

	return 0;
}

void	__wrap_zbx_clean_events(void)
{
}

int	__wrap_zbx_interface_availability_is_set(const void *ia)
{
	ZBX_UNUSED(ia);

	return FAIL;
}

void	__wrap_zbx_rtc_subscribe(unsigned char proc_type, int proc_num, zbx_uint32_t *msgs, int msgs_num,
		int config_timeout, void *rtc)
{
	ZBX_UNUSED(proc_type);
	ZBX_UNUSED(proc_num);
	ZBX_UNUSED(msgs);
	ZBX_UNUSED(msgs_num);
	ZBX_UNUSED(config_timeout);
	ZBX_UNUSED(rtc);
}

void	__wrap_zbx_rtc_notify_finished_sync(int config_timeout, zbx_uint32_t code, const char *process_name,
		void *rtc)
{
	ZBX_UNUSED(config_timeout);
	ZBX_UNUSED(code);
	ZBX_UNUSED(process_name);
	ZBX_UNUSED(rtc);
}