/*
** Zabbix
** Copyright (C) 2001-2023 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** 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 General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

package zbxlib

/*
#cgo CFLAGS: -I${SRCDIR}/../../../../../include -I${SRCDIR}/../../../../../build/win32/include

#include "zbxsysinfo.h"
#include "log.h"
#include "../src/zabbix_agent/metrics.h"
#include "../src/zabbix_agent/logfiles/logfiles.h"
#include "zbx_item_constants.h"

void	zbx_config_tls_init_for_agent2(zbx_config_tls_t *config_tls, unsigned int accept, unsigned int connect,
		char *PSKIdentity, char *PSKKey, char *CAFile, char *CRLFile, char *CertFile, char *KeyFile,
		char *ServerCertIssuer, char *ServerCertSubject);

extern int CONFIG_EVENTLOG_MAX_LINES_PER_SECOND;

typedef ZBX_ACTIVE_METRIC* ZBX_ACTIVE_METRIC_LP;
typedef zbx_vector_ptr_t * zbx_vector_ptr_lp_t;
typedef zbx_vector_expression_t * zbx_vector_expression_lp_t;
typedef char * char_lp_t;

void metric_set_refresh(ZBX_ACTIVE_METRIC *metric, int refresh);
void metric_get_meta(ZBX_ACTIVE_METRIC *metric, zbx_uint64_t *lastlogsize, int *mtime);
void metric_set_unsupported(ZBX_ACTIVE_METRIC *metric);
int metric_set_supported(ZBX_ACTIVE_METRIC *metric, zbx_uint64_t lastlogsize_sent, int mtime_sent,
		zbx_uint64_t lastlogsize_last, int mtime_last);

int	process_eventlog_check(zbx_vector_ptr_t *addrs, zbx_vector_ptr_t *agent2_result,
		zbx_vector_expression_t *regexps, ZBX_ACTIVE_METRIC *metric, zbx_process_value_func_t process_value_cb,
		zbx_uint64_t *lastlogsize_sent, const zbx_config_tls_t *config_tls, int config_timeout, char **error);

typedef struct
{
	char *value;
	char *source;
	int timestamp;
	int logeventid;
	int severity;

	int state;
	zbx_uint64_t lastlogsize;
}
eventlog_value_t;

typedef struct
{
	zbx_vector_ptr_t values;
	int slots;
}
eventlog_result_t, *eventlog_result_lp_t;

static eventlog_result_t *new_eventlog_result(int slots)
{
	eventlog_result_t *result;

	result = (eventlog_result_t *)zbx_malloc(NULL, sizeof(eventlog_result_t));
	zbx_vector_ptr_create(&result->values);
	result->slots = slots;
	return result;
}

static void add_eventlog_value(eventlog_result_t *result, const char *value, const char *source, int logeventid, int severity,
	int timestamp, int state, zbx_uint64_t lastlogsize)
{
	eventlog_value_t *log;
	log = (eventlog_value_t *)zbx_malloc(NULL, sizeof(eventlog_value_t));
	log->value = zbx_strdup(NULL, value);
	log->source = zbx_strdup(NULL, source);
	log->logeventid = logeventid;
	log->severity = severity;
	log->timestamp = timestamp;
	log->state = state;
	log->lastlogsize = lastlogsize;
	zbx_vector_ptr_append(&result->values, log);
}

static int get_eventlog_value(eventlog_result_t *result, int index, char **value, char **source, int *logeventid,
	 int *severity, int *timestamp, int *state, zbx_uint64_t *lastlogsize)
{
	eventlog_value_t *log;

	if (index == result->values.values_num)
		return FAIL;

	log = (eventlog_value_t *)result->values.values[index];
	*value = log->value;
	*source = log->source;
	*logeventid = log->logeventid;
	*severity = log->severity;
	*timestamp = log->timestamp;
	*state = log->state;
	*lastlogsize = log->lastlogsize;
	return SUCCEED;
}

static void free_eventlog_value(eventlog_value_t *log)
{
	zbx_free(log->value);
	zbx_free(log->source);
	zbx_free(log);
}

static void free_eventlog_result(eventlog_result_t *result)
{
	zbx_vector_ptr_clear_ext(&result->values, (zbx_clean_func_t)free_eventlog_value);
	zbx_vector_ptr_destroy(&result->values);
	zbx_free(result);
}

int	process_eventlog_value_cb(zbx_vector_ptr_t *addrs, zbx_vector_ptr_t *agent2_result, const char *host,
		const char *key, const char *value, unsigned char state, zbx_uint64_t *lastlogsize, const int *mtime,
		unsigned long *timestamp, const char *source, unsigned short *severity, unsigned long *logeventid,
		unsigned char flags)
{
	ZBX_UNUSED(addrs);

	eventlog_result_t *result = (eventlog_result_t *)agent2_result;
	if (result->values.values_num == result->slots)
		return FAIL;

	add_eventlog_value(result, value, source, *logeventid, *severity, *timestamp, state, *lastlogsize);
	return SUCCEED;
}
*/
import "C"

import (
	"errors"
	"time"
	"unsafe"
	"zabbix.com/internal/agent"
	"zabbix.com/pkg/tls"

	"git.zabbix.com/ap/plugin-support/log"
)

type EventLogItem struct {
	LastTs  time.Time // the last log value timestamp + 1ns
	Results []*EventLogResult
	Output  ResultWriter
}

type EventLogResult struct {
	Value          *string
	EventSource    *string
	EventID        *int
	EventTimestamp *int
	EventSeverity  *int
	Ts             time.Time
	Error          error
	LastLogsize    uint64
	Mtime          int
}

func ProcessEventLogCheck(data unsafe.Pointer, item *EventLogItem, refresh int, cblob unsafe.Pointer) {
	log.Tracef("Calling C function \"metric_set_refresh()\"")
	C.metric_set_refresh(C.ZBX_ACTIVE_METRIC_LP(data), C.int(refresh))

	var clastLogsizeSent, clastLogsizeLast C.zbx_uint64_t
	var cstate, cmtime C.int
	log.Tracef("Calling C function \"metric_get_meta()\"")
	C.metric_get_meta(C.ZBX_ACTIVE_METRIC_LP(data), &clastLogsizeSent, &cmtime)
	clastLogsizeLast = clastLogsizeSent

	log.Tracef("Calling C function \"new_eventlog_result()\"")
	result := C.new_eventlog_result(C.int(item.Output.PersistSlotsAvailable()))

	var tlsConfig *tls.Config
	var err error
	var ctlsConfig C.zbx_config_tls_t;
	var ctlsConfig_p *C.zbx_config_tls_t;

	if tlsConfig, err = agent.GetTLSConfig(&agent.Options); err != nil {
		result := &EventLogResult{
			Ts:    time.Now(),
			Error: err,
		}
		item.Results = append(item.Results, result)

		return
	}
	if (nil != tlsConfig) {
		log.Tracef("Calling C function \"zbx_config_tls_init_for_agent2()\"")
		C.zbx_config_tls_init_for_agent2(&ctlsConfig, (C.uint)(tlsConfig.Accept), (C.uint)(tlsConfig.Connect),
			(C.CString)(tlsConfig.PSKIdentity), (C.CString)(tlsConfig.PSKKey),
			(C.CString)(tlsConfig.CAFile), (C.CString)(tlsConfig.CRLFile), (C.CString)(tlsConfig.CertFile),
			(C.CString)(tlsConfig.KeyFile), (C.CString)(tlsConfig.ServerCertIssuer),
			(C.CString)(tlsConfig.ServerCertSubject));
		ctlsConfig_p = &ctlsConfig
	}

	var cerrmsg *C.char
	log.Tracef("Calling C function \"process_eventlog_check()\"")
	ret := C.process_eventlog_check(nil, C.zbx_vector_ptr_lp_t(unsafe.Pointer(result)),
		C.zbx_vector_expression_lp_t(cblob), C.ZBX_ACTIVE_METRIC_LP(data),
		C.zbx_process_value_func_t(C.process_eventlog_value_cb), &clastLogsizeSent, ctlsConfig_p,
		(C.int)(agent.Options.Timeout), &cerrmsg)

	// add cached results
	var cvalue, csource *C.char
	var clogeventid, cseverity, ctimestamp C.int
	var clastlogsize C.zbx_uint64_t
	logTs := time.Now()
	if logTs.Before(item.LastTs) {
		logTs = item.LastTs
	}
	log.Tracef("Calling C function \"get_eventlog_value()\"")
	for i := 0; C.get_eventlog_value(result, C.int(i), &cvalue, &csource, &clogeventid, &cseverity, &ctimestamp, &cstate,
		&clastlogsize) != C.FAIL; i++ {

		var value, source string
		var logeventid, severity, timestamp int
		var r EventLogResult
		if cstate == C.ITEM_STATE_NORMAL {
			value = C.GoString(cvalue)
			source = C.GoString(csource)
			logeventid = int(clogeventid)
			severity = int(cseverity)
			timestamp = int(ctimestamp)

			r = EventLogResult{
				Value:          &value,
				EventSource:    &source,
				EventID:        &logeventid,
				EventSeverity:  &severity,
				EventTimestamp: &timestamp,
				Ts:             logTs,
				LastLogsize:    uint64(clastlogsize),
			}

		} else {
			r = EventLogResult{
				Error:       errors.New(C.GoString(cvalue)),
				Ts:          logTs,
				LastLogsize: uint64(clastlogsize),
			}

		}

		item.Results = append(item.Results, &r)
		logTs = logTs.Add(time.Nanosecond)
	}
	log.Tracef("Calling C function \"free_eventlog_result()\"")
	C.free_eventlog_result(result)

	item.LastTs = logTs

	if ret == C.FAIL {
		log.Tracef("Calling C function \"metric_set_unsupported()\"")
		C.metric_set_unsupported(C.ZBX_ACTIVE_METRIC_LP(data))

		var err error
		if cerrmsg != nil {
			err = errors.New(C.GoString(cerrmsg))
			log.Tracef("Calling C function \"free()\"")
			C.free(unsafe.Pointer(cerrmsg))
		} else {
			err = errors.New("Unknown error.")
		}
		result := &EventLogResult{
			Ts:    time.Now(),
			Error: err,
		}
		item.Results = append(item.Results, result)
	} else {
		log.Tracef("Calling C function \"metric_set_supported()\"")
		ret := C.metric_set_supported(C.ZBX_ACTIVE_METRIC_LP(data), clastLogsizeSent, 0, clastLogsizeLast, 0)

		if ret == Succeed {
			log.Tracef("Calling C function \"metric_get_meta()\"")
			C.metric_get_meta(C.ZBX_ACTIVE_METRIC_LP(data), &clastLogsizeLast, &cmtime)
			result := EventLogResult{
				Ts:          time.Now(),
				LastLogsize: uint64(clastLogsizeLast),
			}
			item.Results = append(item.Results, &result)
		}
	}
}

func SetEventlogMaxLinesPerSecond(num int) {
	C.CONFIG_EVENTLOG_MAX_LINES_PER_SECOND = C.int(num)
}