/*
** 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 "zbxmockassert.h"
#include "zbxmockutil.h"
#include "../../../../src/libs/zbxsysinfo/agent/modbtype.h"
#include <modbus.h>

modbus_t	*__wrap_modbus_new_rtu(char *port, int baudrate,
		unsigned char parity, unsigned char data_bits,
		unsigned char stop_bits)
{
	zbx_mock_assert_str_eq("endpoint protocol", zbx_mock_get_parameter_string("out.endpoint.protocol"), "rtu");
	zbx_mock_assert_str_eq("endpoint port", zbx_mock_get_parameter_string("out.endpoint.port"), port);
	zbx_mock_assert_int_eq("endpoint parity", (int)(*zbx_mock_get_parameter_string("out.endpoint.parity")),
			(int)parity);
	zbx_mock_assert_int_eq("endpoint baudrate", zbx_mock_get_parameter_int("out.endpoint.baudrate"), baudrate);
	zbx_mock_assert_int_eq("endpoint data_bits", zbx_mock_get_parameter_int("out.endpoint.data_bits"),
			(int)data_bits);
	zbx_mock_assert_int_eq("endpoint stop_bits", zbx_mock_get_parameter_int("out.endpoint.stop_bits"),
			(int)stop_bits);

	return zbx_malloc(NULL, sizeof(int));
}

modbus_t	*__wrap_modbus_new_tcp_pi(char *ip, char *port)
{
	zbx_mock_assert_str_eq("endpoint protocol", zbx_mock_get_parameter_string("out.endpoint.protocol"), "tcp");
	zbx_mock_assert_str_eq("endpoint ip", zbx_mock_get_parameter_string("out.endpoint.ip"), ip);
	zbx_mock_assert_str_eq("endpoint port", zbx_mock_get_parameter_string("out.endpoint.port"), port);

	return zbx_malloc(NULL, sizeof(int));
}

void	__wrap_modbus_free(modbus_t *mdb_ctx)
{
	zbx_free(mdb_ctx);
}

void	__wrap_modbus_close(modbus_t *mdb_ctx)
{
	ZBX_UNUSED(mdb_ctx);
}

int	__wrap_modbus_connect(modbus_t *mdb_ctx)
{
	ZBX_UNUSED(mdb_ctx);

	return 0;
}

int	__wrap_modbus_set_slave(modbus_t *mdb_ctx, int slaveid)
{
	ZBX_UNUSED(mdb_ctx);

	zbx_mock_assert_int_eq("slaveid", zbx_mock_get_parameter_int("out.slaveid"), slaveid);

	return 0;
}

#ifdef HAVE_LIBMODBUS_3_0
void	__wrap_modbus_set_response_timeout(modbus_t *mdb_ctx, struct timeval *timeout)
{
	ZBX_UNUSED(mdb_ctx);
	ZBX_UNUSED(timeout);
}
#else
int	__wrap_modbus_set_response_timeout(modbus_t *mdb_ctx, uint32_t to_sec, uint32_t to_usec)
{
	ZBX_UNUSED(mdb_ctx);
	ZBX_UNUSED(to_sec);
	ZBX_UNUSED(to_usec);

	return 0;
}
#endif

static char	*read_common(int func, int addr, int nb, int chr_num)
{
	char	*data;
	size_t	len;

	zbx_mock_assert_int_eq("function", zbx_mock_get_parameter_int("out.function"), func);
	zbx_mock_assert_int_eq("address", zbx_mock_get_parameter_int("out.address"), addr);
	zbx_mock_assert_int_eq("total_count", zbx_mock_get_parameter_int("out.total_count"), nb);

	data = zbx_strdup(NULL, zbx_mock_get_parameter_string("in.data"));
	zbx_remove_chars(data, " ");
	len = strlen(data);

	zbx_mock_assert_int_eq("invalid length of data", len, nb * chr_num);

	return data;
}

static int	read_bits_common(int func, int addr, int nb, uint8_t *dest)
{
	char	*data;

	data = read_common(func, addr, nb, 1);

	for (int i = 0; i < nb; i++)
	{
		if ('0' == data[i])
			dest[i] = 0;
		else if ('1' == data[i])
			dest[i] = 1;
		else
			fail_msg("Invalid data value: %s", data);
	}

	zbx_free(data);

	return nb;
}

static int	read_registers_common(int func, int addr, int nb, uint16_t *dest)
{
	char	*data;
	int	k = 0, chr_num = sizeof(uint16_t) * 2;

	data = read_common(func, addr, nb, chr_num);

	for (int i = 0; i < nb * chr_num; i += chr_num)
	{
		if (SUCCEED != zbx_is_hex_n_range(data + i, (size_t)chr_num, dest + k++, sizeof(uint16_t), 0, 0xFFFF))
			fail_msg("Invalid data value: %s", data);
	}

	zbx_free(data);

	return nb;
}

int	__wrap_modbus_read_bits(modbus_t *mdb_ctx, int addr, int nb, uint8_t *dest)
{
	ZBX_UNUSED(mdb_ctx);

	return read_bits_common(1, addr, nb, dest);
}

int	__wrap_modbus_read_input_bits(modbus_t *mdb_ctx, int addr, int nb, uint8_t *dest)
{
	ZBX_UNUSED(mdb_ctx);

	return read_bits_common(2, addr, nb, dest);
}

int	__wrap_modbus_read_input_registers(modbus_t *mdb_ctx, int addr, int nb, uint16_t *dest)
{
	ZBX_UNUSED(mdb_ctx);

	return read_registers_common(4, addr, nb, dest);
}

int	__wrap_modbus_read_registers(modbus_t *mdb_ctx, int addr, int nb, uint16_t *dest)
{
	ZBX_UNUSED(mdb_ctx);

	return read_registers_common(3, addr, nb, dest);
}

void	zbx_mock_test_entry(void **state)
{
	AGENT_REQUEST	request;
	AGENT_RESULT	result;
	char		*item_key;
	int		ret;

	ZBX_UNUSED(state);

	item_key = zbx_mock_get_parameter_string("in.key");
	zbx_init_agent_request(&request);

	if (SUCCEED != zbx_parse_item_key(item_key, &request))
		fail_msg("Cannot parse item key: '%s'", item_key);

	zbx_init_agent_result(&result);
	ret = modbus_get(&request, &result);

	zbx_mock_assert_sysinfo_ret_eq("Return value",
			zbx_mock_str_to_return_code(zbx_mock_get_parameter_string("out.return")), ret);

	if (SUCCEED != ret)
	{
		zbx_mock_handle_t	handle;

		if (ZBX_MOCK_SUCCESS == zbx_mock_out_parameter("msg", &handle))
		{
			if (0 != ZBX_ISSET_MSG(&result))
			{
				zbx_mock_assert_str_eq("Error message", zbx_mock_get_parameter_string("out.msg"),
						*ZBX_GET_MSG_RESULT(&result));
			}
			else
				fail_msg("No error message");
		}

		goto out;
	}

	if (0 != ZBX_ISSET_STR(&result))
	{
		zbx_mock_assert_str_eq("result (str)", zbx_mock_get_parameter_string("out.result"),
				*ZBX_GET_STR_RESULT(&result));
	}
	else if (0 != ZBX_ISSET_UI64(&result))
	{
		zbx_mock_assert_uint64_eq("result (ui64)", zbx_mock_get_parameter_uint64("out.result"),
				*ZBX_GET_UI64_RESULT(&result));
	}
	else if (0 != ZBX_ISSET_DBL(&result))
	{
		zbx_mock_assert_double_eq("result (dbl)", zbx_mock_get_parameter_float("out.result"),
				*ZBX_GET_DBL_RESULT(&result));
	}
	else
		fail_msg("Invalid result type");
out:
	zbx_free_agent_request(&request);
	zbx_free_agent_result(&result);
}