/*
** 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 "console.h"
#include "embed.h"

#include "zbxjson.h"
#include "duktape.h"
#include "zbxstr.h"
#include "zbxtime.h"

/******************************************************************************
 *                                                                            *
 * Purpose: console destructor                                                *
 *                                                                            *
 ******************************************************************************/
static duk_ret_t	es_console_dtor(duk_context *ctx)
{
	ZBX_UNUSED(ctx);

	return 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: console constructor                                               *
 *                                                                            *
 ******************************************************************************/
static duk_ret_t	es_console_ctor(duk_context *ctx)
{
	if (!duk_is_constructor_call(ctx))
		return DUK_RET_TYPE_ERROR;

	duk_push_this(ctx);

	duk_push_c_function(ctx, es_console_dtor, 1);
	duk_set_finalizer(ctx, -2);

	return 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: Write message to centralized Zabbix log                           *
 *                                                                            *
 ******************************************************************************/
static duk_ret_t	es_log_message(duk_context *ctx, int level)
{
	zbx_es_env_t		*env;
	const char		*msg_raw;
	char			*msg_output = NULL;
	int			err_index = -1;
	duk_memory_functions	out_funcs;

	if (0 == duk_is_string(ctx, -1))
		msg_raw = duk_json_encode(ctx, -1);
	else
		msg_raw = duk_get_string(ctx, -1);

	if (0 == duk_is_null_or_undefined(ctx, -1))
	{
		if (SUCCEED != es_duktape_string_decode(msg_raw, &msg_output))
		{
			msg_output = zbx_strdup(msg_output, msg_raw);
			zbx_replace_invalid_utf8(msg_output);
		}
	}
	else
		msg_output = zbx_strdup(msg_output, "undefined");

	duk_get_memory_functions(ctx, &out_funcs);
	env = (zbx_es_env_t *)out_funcs.udata;

	if (ZBX_ES_LOG_MSG_LIMIT <= env->logged_msgs)
	{
		err_index = duk_push_error_object(ctx, DUK_RET_EVAL_ERROR,
				"maximum count of logged messages was reached");
		goto out;
	}

	zabbix_log(level, "%s", msg_output);

	if (NULL == env->json)
		goto out;

	if (ZBX_ES_LOG_MEMORY_LIMIT < env->json->buffer_size)	/* approximate limit */
	{
		err_index = duk_push_error_object(ctx, DUK_RET_EVAL_ERROR, "log exceeds the maximum size of "
				ZBX_FS_UI64 " bytes.", ZBX_ES_LOG_MEMORY_LIMIT);
		goto out;
	}

	zbx_json_addobject(env->json, NULL);
	zbx_json_adduint64(env->json, "level", (zbx_uint64_t)level);
	zbx_json_adduint64(env->json, "ms", zbx_get_duration_ms(&env->start_time));
	zbx_json_addstring(env->json, "message", msg_output, ZBX_JSON_TYPE_STRING);
	zbx_json_close(env->json);
out:
	env->logged_msgs++;
	zbx_free(msg_output);

	if (-1 != err_index)
		return duk_throw(ctx);

	return 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: console.log method                                                *
 *                                                                            *
 ******************************************************************************/
static duk_ret_t	es_console_log(duk_context *ctx)
{
	return es_log_message(ctx, LOG_LEVEL_DEBUG);
}

/******************************************************************************
 *                                                                            *
 * Purpose: console.warn method                                               *
 *                                                                            *
 ******************************************************************************/
static duk_ret_t	es_console_warn(duk_context *ctx)
{
	return es_log_message(ctx, LOG_LEVEL_WARNING);
}

/******************************************************************************
 *                                                                            *
 * Purpose: console.error method                                              *
 *                                                                            *
 ******************************************************************************/
static duk_ret_t	es_console_error(duk_context *ctx)
{
	return es_log_message(ctx, LOG_LEVEL_ERR);
}

static const duk_function_list_entry	console_methods[] = {
	{"log", es_console_log, 1},
	{"warn", es_console_warn, 1},
	{"error", es_console_error, 1},
	{NULL, NULL, 0}
};

static int	es_console_create_object(duk_context *ctx)
{
	duk_push_c_function(ctx, es_console_ctor, 0);
	duk_push_object(ctx);

	duk_put_function_list(ctx, -1, console_methods);

	if (1 != duk_put_prop_string(ctx, -2, "prototype"))
		return FAIL;

	duk_new(ctx, 0);
	duk_put_global_string(ctx, "console");

	return SUCCEED;
}

int	zbx_es_init_console(zbx_es_t *es, char **error)
{
	if (0 != setjmp(es->env->loc))
	{
		*error = zbx_strdup(*error, es->env->error);
		return FAIL;
	}

	if (FAIL == es_console_create_object(es->env->ctx))
	{
		*error = zbx_strdup(*error, duk_safe_to_string(es->env->ctx, -1));
		duk_pop(es->env->ctx);
		return FAIL;
	}

	return SUCCEED;
}
