/*
** Job Arranger for ZABBIX
** Copyright (C) 2025 Daiwa Institute of Research Ltd. All Rights Reserved.
**
** 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 main

/*
typedef struct {
	char *parameter;
	void *variable;
	int	data_type;
	size_t max_size;
} cfg_line;

// Define corresponding C enum for logger
typedef enum {
    LOG_LEVEL_INFO  = 0,
    LOG_LEVEL_CRIT  = 1,
    LOG_LEVEL_ERR   = 2,
    LOG_LEVEL_WARN  = 3,
    LOG_LEVEL_DEBUG = 4
} CLogLevel;

typedef enum {
    LOG_TYPE_FILE    = 0,
    LOG_TYPE_SYSTEM  = 1,
    LOG_TYPE_CONSOLE = 2
} CLogType;
*/
import "C"

import (
	"fmt"
	"os"
	"reflect"
	"unsafe"

	"jobarranger2/src/libs/golibs/common"
	"jobarranger2/src/libs/golibs/config_reader/agent"
	"jobarranger2/src/libs/golibs/config_reader/conf"
	"jobarranger2/src/libs/golibs/config_reader/server"
	"jobarranger2/src/libs/golibs/logger/logger"
)

// ==================== Config Reader Functions ====================

//export LoadConfigForC
func LoadConfigForC(configPath *C.char, cfg *C.cfg_line, cfgCount C.int, target C.int) C.int {
	goConfigPath := C.GoString(configPath)

	var goOpts any

	if target == 0 {
		goOpts = &server.ServerOptions{}
	} else if target == 1 {
		goOpts = &agent.AgentOptions{}
	} else {
		fmt.Fprint(os.Stderr, "Invalid target [0:server, 1:agent]\n")
		return -1
	}

	err := conf.Load(goConfigPath, goOpts)
	if err != nil {
		// Error message
		fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
		return -1
	}

	// sanity check for bound
	if cfgCount <= 0 || cfgCount > 1e6 {
		return -1
	}

	cfgSlice := (*[1 << 30]C.cfg_line)(unsafe.Pointer(cfg))[:cfgCount:cfgCount]
	// Get value of go options
	val := reflect.ValueOf(goOpts).Elem()

	for _, item := range cfgSlice {
		paramName := C.GoString(item.parameter)

		// Check if the param passed from C is valid
		field := val.FieldByName(paramName)
		if !field.IsValid() {
			fmt.Fprintf(os.Stderr, "field is not valid: %s\n", paramName)
			return -1
		}

		// Assign value to pointers based on the data type from C
		switch item.data_type {
		// Int type
		case 0:
			if field.Kind() == reflect.Int {
				*(*C.int)(item.variable) = C.int(field.Int())
			} else {
				fmt.Fprintf(os.Stderr, "type mismatched for '%s'\n", paramName)
				return -1
			}
		// String type
		case 1:
			if field.Kind() == reflect.String {
				str := field.String()
				if str != "" {
					maxSize := item.max_size
					if maxSize > 0 {
						// Copy string directly into C's buffer
						strBytes := []byte(str)
						maxLen := int(maxSize) - 1

						// Check for overflow
						if len(strBytes) > maxLen {
							strBytes = strBytes[:maxLen]
						}

						// Write to C memory
						dst := unsafe.Slice((*byte)(item.variable), maxSize)
						copy(dst, strBytes)

						// Null-terminate
						dst[len(strBytes)] = 0

					}
				}
			} else {
				fmt.Fprintf(os.Stderr, "type mismatched for '%s'\n", paramName)
				return -1
			}
		default:
			fmt.Fprintf(os.Stderr, "unsupported data type for parameter '%s'\n", paramName)
			return -1
		}

	}

	return 0
}

// ==================== Logger Functions ====================

// Export the enum values as C-compatible functions
//
//export GetLogLevelInfo
func GetLogLevelInfo() C.CLogLevel {
	return C.CLogLevel(logger.LogLevelInfo)
}

//export GetLogLevelCrit
func GetLogLevelCrit() C.CLogLevel {
	return C.CLogLevel(logger.LogLevelCrit)
}

//export GetLogLevelErr
func GetLogLevelErr() C.CLogLevel {
	return C.CLogLevel(logger.LogLevelErr)
}

//export GetLogLevelWarn
func GetLogLevelWarn() C.CLogLevel {
	return C.CLogLevel(logger.LogLevelWarn)
}

//export GetLogLevelDebug
func GetLogLevelDebug() C.CLogLevel {
	return C.CLogLevel(logger.LogLevelDebug)
}

//export InitLogger
func InitLogger(logFilePath *C.char, messageFilePath *C.char, logTypeStr *C.char, maxLogFileSize C.int, logLevelInt C.int, targetType C.int, cErrMsg *C.char, size C.int) C.int {
	err := logger.InitLogger(C.GoString(logFilePath), C.GoString(messageFilePath), C.GoString(logTypeStr), int(maxLogFileSize), int(logLevelInt), int(targetType))

	if err != nil {
		setStrFromGoToC(err.Error(), cErrMsg, size)
		return -1
	}

	return 0
}

// It finds the message by id and store the value in messageStr
// FindMessageByID returns:
// -2 = ignore the log due to log level
// -1 = error
//
//	0 = no notification
//	1 = needs notification
//
//export FindMessageByID
func FindMessageByID(messageID *C.char, cLogLevel *C.CLogLevel, msgStr *C.char, msgStrSize C.int, cErrMsg *C.char, size C.int) C.int {
	msg, err := logger.FindMessage(C.GoString(messageID))
	if err != nil {
		setStrFromGoToC(err.Error(), cErrMsg, size)
		return -1
	}

	if logger.ShouldIgnoreLog(msg.LogLevel) {
		return -2
	}

	*cLogLevel = C.CLogLevel(msg.LogLevel)

	setStrFromGoToC(msg.Body, msgStr, msgStrSize)

	return C.int(msg.NoticeFlag)
}

//export WriteLog
func WriteLog(cLogLevel C.CLogLevel, message *C.char, cErrMsg *C.char, size C.int) int {
	err := logger.WriteLogHelper(logger.LogLevel(cLogLevel), C.GoString(message))
	if err != nil {
		setStrFromGoToC(err.Error(), cErrMsg, size)
		return -1
	}

	return 0
}

//export CreateNotiEvent
func CreateNotiEvent(message *C.char, messageID *C.char, cLogLevel C.CLogLevel, cErrMsg *C.char, size C.int) int {
	if err := logger.CreateNotiEvent(common.ZbxSendInfo{
		LogMessage:   C.GoString(message),
		LogMessageID: C.GoString(messageID),
		LogLevel:     int(cLogLevel),
	}); err != nil {
		setStrFromGoToC(err.Error(), cErrMsg, size)
		return -1
	}

	return 0
}

// Set error message to the C char array
func setStrFromGoToC(errMsg string, cErrMsg *C.char, size C.int) {
	// Check max length to be greater than zero
	maxLength := int(size) - 1
	if maxLength <= 0 {
		return
	}

	// Truncate the message if overflow
	msgBytes := []byte(errMsg)

	if len(msgBytes) >= maxLength {
		msgBytes = msgBytes[:maxLength]
	}

	// Create a slice that references the C memory
	destSlice := unsafe.Slice((*byte)(unsafe.Pointer(cErrMsg)), int(size))

	// Copy the value
	copy(destSlice, msgBytes)

	// Ensure null-termination
	destSlice[len(msgBytes)] = 0
}

func main() {}
