/*
** 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

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"strconv"
	"sync"
	"syscall"
	"time"
	"unsafe"

	zabbixloader "jobarranger2/src/jobarg_server/managers/zabbix_link_manager/zabbix_loader"
	zbxlink "jobarranger2/src/jobarg_server/managers/zabbix_link_manager/zbx_link"
	"jobarranger2/src/libs/golibs/common"
	"jobarranger2/src/libs/golibs/config_reader/server"
	"jobarranger2/src/libs/golibs/database"
	"jobarranger2/src/libs/golibs/logger/logger"
	"jobarranger2/src/libs/golibs/utils"
	wm "jobarranger2/src/libs/golibs/worker_manager"
)

const (
	SUCCEED               = 0
	FAIL                  = -1
	CONFIG_ZABBIX_VERSION = 6

	/* zabbix flag discovery */
	ZBXLINK_FLAG_DISCOVERY_NORMAL  = 0
	ZBXLINK_FLAG_DISCOVERY_CREATED = 4

	/* zabbix host group flag */
	ZBXLINK_HOST_GROUP_FLAG_OFF = 0
	ZBXLINK_HOST_GROUP_FLAG_ON  = 1
	cacheInterval               = 3 * time.Second

	/* zabbix link */
	ZBXLINK_ZBX_LAST_STATUS = "ZBX_LAST_STATUS"
	ZBXLINK_ZBX_LATEST_DATA = "ZBX_LATEST_DATA"
	ZBXLINK_ZBX_DATA_TYPE   = "ZBX_DATA_TYPE"
	SIGRTMAX                = 64
)

type ItemData struct {
	valueType int
	value     string
}

type HostInterfaceInfo struct {
	HostID uint64
	UseIP  int
	Status int
	DNS    string
	IP     string
	Host   string
	Main   int
	Type   int
}

type MacroInfo struct {
	MacroName string
	Value     string
	HostID    uint64
}

var (
	hostCache       []HostInterfaceInfo
	hostCacheMutex  sync.RWMutex
	macroCache      []MacroInfo
	macroCacheMutex sync.RWMutex
	forceReloadChan = make(chan struct{}, 1) // buffered so multiple triggers won't block
	hostCacheReady  = make(chan struct{})
	macroCacheReady = make(chan struct{})
	notifyOnce      sync.Once
	testFlagOn      = errors.New("test run mode")
)
var zbxUpdater zbxlink.ZabbixUpdater

var (
	CacheHostOnMemoryID  string = "cacheHostOnMemory"
	CacheMacroOnMemoryID string = "cacheMacroOnMemory"
)

func ProcessEventData(data common.Data) {
	funcName := "ProcessEventData"
	var eventData common.EventData

	err := utils.UnmarshalEventData(data.EventData, &eventData)
	if err != nil {
		logger.JaLog("JAZABBIXLINK200012", logger.Logging{}, funcName, "event data", err.Error())
	}

	switch eventData.Event.Name {
	case common.EventIconReady:
		zabbixLink(eventData, data.DBConn, data.NetConn)
	case common.EventZabbixHostIp:
		collectHostResults(eventData, data.NetConn)
	}
}

func StartDaemonWorkers(data common.Data) {
	wm.StartWorker(func() { cacheHostOnMemory(data.DB) }, CacheHostOnMemoryID, 300, 0, string(common.ZabbixLinkManagerProcess))
	wm.StartWorker(func() { cacheMacroOnMemory(data.DB) }, CacheMacroOnMemoryID, 300, 0, string(common.ZabbixLinkManagerProcess))
	go func() {
		<-hostCacheReady
		<-macroCacheReady

		notifyOnce.Do(func() {
			logger.JaLog("JAZABBIXLINK400032", logger.Logging{})
			ppid := os.Getppid()
			syscall.Kill(ppid, syscall.Signal(SIGRTMAX))
		})
	}()
}

func collectHostResults(eventData common.EventData, netConn *common.NetConnection) {
	funcName := "collectHostResults"
	var hostNameData common.HostData
	var ok bool

	logger.JaLog("JAZABBIXLINK400023", logger.Logging{}, funcName)

	hostNameData, ok = eventData.NextProcess.Data.(common.HostData)
	if !ok {
		logger.JaLog("JAZABBIXLINK200015", logger.Logging{}, funcName, "host data")
		return
	}

	var results []common.HostResult
	for _, hostMacro := range hostNameData.Hosts {
		result := hostResultFor(hostMacro.Hostname, hostMacro.Macroname)
		results = append(results, result)
	}

	resultMap := map[string]any{
		"host_results": results,
	}
	err := netConn.Send(resultMap)
	if err != nil {
		logger.JaLog("JAZABBIXLINK200019", logger.Logging{}, funcName)
		return
	}
	logger.JaLog("JAZABBIXLINK400024", logger.Logging{}, funcName)
}

func hostResultFor(hostName string, macroName string) common.HostResult {
	funcName := "hostResultFor"
	logger.JaLog("JAZABBIXLINK400021", logger.Logging{}, funcName)

	hostCacheMutex.RLock()
	defer hostCacheMutex.RUnlock()

	for _, h := range hostCache {
		if h.Host != hostName || h.Main != 1 || h.Type != 1 {
			continue
		}

		// Initial host result
		result := common.HostResult{
			Hostname: hostName,
			Status:   h.Status,
		}

		if h.UseIP == 0 {
			result.Dns = h.DNS
		} else {
			result.IP = h.IP
		}

		// Default port assignment
		switch macroName {
		case common.JaAgentPort:
			result.Port = server.Options.JaAgentListenPort
		case common.JaSSHPort:
			result.Port = common.SSHPort
		}

		// Macro lookup
		for _, m := range macroCache {
			if m.HostID == h.HostID && m.MacroName == macroName {
				if port, err := strconv.Atoi(m.Value); err == nil {
					result.Port = port
				} else {
					logger.JaLog("JAZABBIXLINK200015", logger.Logging{}, funcName, "port [from string to int]")
				}
				break
			}
		}

		logger.JaLog("JAZABBIXLINK400022", logger.Logging{}, funcName)
		return result
	}

	return common.HostResult{
		Hostname: hostName,
		Error:    "Requested host not found or not active.",
	}
}

// estimate memory usage of HostInterfaceInfo
func estimateHostSize(h HostInterfaceInfo) uintptr {
	size := unsafe.Sizeof(h)
	size += uintptr(len(h.DNS))
	size += uintptr(len(h.IP))
	size += uintptr(len(h.Host))
	return size
}

// estimate memory usage of MacroInfo
func estimateMacroSize(m MacroInfo) uintptr {
	size := unsafe.Sizeof(m)
	size += uintptr(len(m.MacroName))
	size += uintptr(len(m.Value))
	return size
}

// common function to log cache update
func logCacheUpdate[T any](name string, cache []T, estimator func(T) uintptr) {
	var totalSize uintptr
	for _, item := range cache {
		totalSize += estimator(item)
	}

	approxKB := float64(totalSize) / 1024
	cacheUpdated := fmt.Sprintf("%s cache updated with %d entries, approx memory: %.2f KB",
		name, len(cache), approxKB)
	logger.JaLog("JAZABBIXLINK400020", logger.Logging{}, cacheUpdated)
}

func cacheHostOnMemory(db database.Database) {
	funcName := "cacheHostOnMemory"
	logger.JaLog("JAZABBIXLINK400019", logger.Logging{}, funcName)

	hostTicker := time.NewTicker(cacheInterval)

	defer hostTicker.Stop()
	dbConn, err := db.GetConn()
	if err != nil {
		logger.JaLog("JAZABBIXLINK200023", logger.Logging{}, funcName, err.Error())
		return
	}

	firstLoaded := false

	for {
		select {
		case <-wm.Wm.Ctx.Done():
			logger.JaLog("JAZABBIXLINK400031", logger.Logging{}, funcName, CacheHostOnMemoryID)
			return

		case <-time.After(1 * time.Second):
			wm.Wm.MonitorChan <- CacheHostOnMemoryID

		case <-hostTicker.C:
			loadHostCache(dbConn)
			if !firstLoaded {
				firstLoaded = true
				close(hostCacheReady) // signal first load
			}

		case <-forceReloadChan:
			logger.JaLog("JAZABBIXLINK400030", logger.Logging{}, funcName)
			loadHostCache(dbConn)
			if !firstLoaded {
				firstLoaded = true
				close(hostCacheReady) // signal first load
			}
		}
	}
}

func loadHostCache(dbConn database.DBConnection) {
	funcName := "reloadHostCache"
	logger.JaLog("JAZABBIXLINK400028", logger.Logging{}, funcName)

	result, err := dbConn.Select(
		"SELECT i.hostid, i.useip, i.dns, i.ip, h.status, h.host, i.main, i.type " +
			"FROM hosts h, interface i WHERE h.hostid = i.hostid",
	)
	if err != nil {
		logger.JaLog("JAZABBIXLINK200013", logger.Logging{}, funcName, "host information", err.Error())
		return
	}

	defer result.Free()
	var cache []HostInterfaceInfo
	for result.HasNextRow() {
		row, err := result.Fetch()
		if err != nil {
			logger.JaLog("JAZABBIXLINK200014", logger.Logging{}, funcName, "host information", err.Error())
			continue
		}

		hostID, _ := strconv.ParseUint(row["hostid"], 10, 64)
		useIP, _ := strconv.Atoi(row["useip"])
		status, _ := strconv.Atoi(row["status"])
		mainValue, _ := strconv.Atoi(row["main"])
		interfaceType, _ := strconv.Atoi(row["type"])

		info := HostInterfaceInfo{
			HostID: hostID,
			UseIP:  useIP,
			Status: status,
			DNS:    row["dns"],
			IP:     row["ip"],
			Host:   row["host"],
			Main:   mainValue,
			Type:   interfaceType,
		}
		cache = append(cache, info)
	}

	// Estimate memory size before locking
	var totalSize uintptr
	for _, h := range cache {
		totalSize += unsafe.Sizeof(h)
		totalSize += uintptr(len(h.DNS))
		totalSize += uintptr(len(h.IP))
		totalSize += uintptr(len(h.Host))
	}

	// Update cache atomically
	hostCacheMutex.Lock()
	hostCache = cache
	hostCacheMutex.Unlock()

	logCacheUpdate("Host", hostCache, estimateHostSize)
}

func cacheMacroOnMemory(db database.Database) {
	funcName := "cacheMacroOnMemory"
	logger.JaLog("JAZABBIXLINK400027", logger.Logging{}, funcName)

	macroTicker := time.NewTicker(cacheInterval)

	defer macroTicker.Stop()
	// Get db connection
	dbConn, err := db.GetConn()
	if err != nil {
		logger.JaLog("JAZABBIXLINK200023", logger.Logging{}, funcName, err.Error())
		return
	}

	firstLoaded := false

	for {
		select {
		case <-wm.Wm.Ctx.Done():
			logger.JaLog("JAZABBIXLINK400031", logger.Logging{}, funcName, CacheMacroOnMemoryID)
			return
		case <-time.After(1 * time.Second):
			wm.Wm.MonitorChan <- CacheMacroOnMemoryID
		}

		macroResult, err := dbConn.Select(
			"SELECT hostid, macro, value FROM hostmacro",
		)
		if err != nil {
			logger.JaLog("JAZABBIXLINK200013", logger.Logging{}, funcName, "macro information", err.Error())
			continue
		}

		var macro []MacroInfo
		for macroResult.HasNextRow() {
			row, err := macroResult.Fetch()
			if err != nil {
				logger.JaLog("JAZABBIXLINK200014", logger.Logging{}, funcName, "macro information", err.Error())
				continue
			}
			hostID, _ := strconv.ParseUint(row["hostid"], 10, 64)
			macroInfo := MacroInfo{
				HostID:    hostID,
				MacroName: row["macro"],
				Value:     row["value"],
			}
			macro = append(macro, macroInfo)
		}
		macroResult.Free()

		macroCacheMutex.Lock()
		macroCache = macro
		macroCacheMutex.Unlock()

		logCacheUpdate("Macro", macroCache, estimateMacroSize)

		if !firstLoaded {
			firstLoaded = true
			close(macroCacheReady) // signal first load
		}

		<-macroTicker.C
	}
}

func zabbixLink(eventData common.EventData, dbConn database.DBConnection, netConn *common.NetConnection) {
	funcName := "zabbixLink"
	var iconExecData common.IconExecutionProcessData
	var zabbixIconData common.IconLinkData
	var err error

	logger.JaLog("JAZABBIXLINK400025", logger.Logging{}, funcName)

	if execData, ok := eventData.NextProcess.Data.(common.IconExecutionProcessData); ok {
		iconExecData = execData
	} else {
		logger.JaLog("JAZABBIXLINK200015", logger.Logging{}, funcName, "icon exec data")
		return
	}

	zabbixIconDataBytes, err := json.Marshal(iconExecData.RunJobData.Data)
	if err != nil {
		logger.JaLog("JAZABBIXLINK200017", logger.Logging{}, funcName, "icon link data", iconExecData.RunJobData.InnerJobID, err.Error())
	}

	err = json.Unmarshal(zabbixIconDataBytes, &zabbixIconData)
	if err != nil {
		logger.JaLog("JAZABBIXLINK200012", logger.Logging{}, funcName, "icon link data", err.Error())
	}

	zbxUpdater, err = zabbixloader.GetZabbixUpdater(CONFIG_ZABBIX_VERSION, dbConn)
	if err != nil {
		logger.JaLog("JAZABBIXLINK200016", logger.Logging{}, funcName, err.Error())
		os.Exit(1)
	}

	err = jaRunIconZabbixlink(dbConn, netConn, zabbixIconData)
	if errors.Is(err, testFlagOn) {
		prepErr := prepareEventData(netConn, common.ZabbixIconResult{
			Output: common.IconLinkData{},
			Error:  "",
		})
		if prepErr != nil {
			logger.JaLog("JAZABBIXLINK200018", logger.Logging{}, funcName, "error info", zabbixIconData.InnerJobId, prepErr.Error())
			return
		}
		return
	}
	if err != nil {
		prepErr := prepareEventData(netConn, common.ZabbixIconResult{
			Output: common.IconLinkData{},
			Error:  err.Error(),
		})
		if prepErr != nil {
			logger.JaLog("JAZABBIXLINK200018", logger.Logging{}, funcName, "error info", zabbixIconData.InnerJobId, prepErr.Error())
			return
		}
		return
	}

}

func jaRunIconZabbixlink(dbConn database.DBConnection, netConn *common.NetConnection, zabbixIconData common.IconLinkData) error {
	funcName := "jaRunIconZabbixLink"
	var (
		hostGroup      = "host group"
		host           = "host"
		item           = "item"
		itemDataString = "item data"
		trigger        = "trigger"
		dbErr          error
	)
	logData := logger.Logging{
		InnerJobID: zabbixIconData.InnerJobId,
	}

	defer func() {
		if dbErr != nil {
			err := dbConn.Rollback()
			if err != nil {
				logger.JaLog("JAZABBIXLINK200022", logData, funcName, hostGroup, zabbixIconData.InnerJobId, err.Error())
				return
			}
			logger.JaLog("JAZABBIXLINK400026", logData, funcName, hostGroup, zabbixIconData.InnerJobId)
		}
	}()

	logger.JaLog("JAZABBIXLINK400001", logData, funcName, zabbixIconData.InnerJobId)
	/* Cooperation target determining */
	switch zabbixIconData.LinkTarget {
	case 0: /* Host group */
		/* Work together decision */
		switch zabbixIconData.LinkOperation {
		case 0, 1: /* 0 - Activation */
			/* 1 - Invalidation */
			if zabbixIconData.TestFlag == 1 {
				return testFlagOn
			} else {
				err := checkAccessPermission(dbConn, zabbixIconData)
				if err != nil {
					logger.JaLog("JAZABBIXLINK200001", logData, hostGroup, zabbixIconData.InnerJobId, err.Error())
					return fmt.Errorf("permission denied: [%s] inner_job_id [%d] %w", hostGroup, zabbixIconData.InnerJobId, err)
				}

				err = dbConn.Begin()
				if err != nil {
					logger.JaLog("JAZABBIXLINK200021", logData, funcName, hostGroup, zabbixIconData.InnerJobId, err.Error())
					return fmt.Errorf("fail to start transaction: [%s] inner_job_id [%d] %w", hostGroup, zabbixIconData.InnerJobId, err)
				}

				dbErr = setHostGroupStatus(zabbixIconData.GroupId, zabbixIconData.LinkOperation)
				if dbErr != nil {
					logger.JaLog("JAZABBIXLINK200002", logData, funcName, zabbixIconData.InnerJobId, dbErr.Error())
					return fmt.Errorf("fail to set host group status: inner_job_id [%d] %w", zabbixIconData.InnerJobId, err)
				}

				dbErr = dbConn.Commit()
				if dbErr != nil {
					logger.JaLog("JAZABBIXLINK200020", logData, funcName, hostGroup, zabbixIconData.InnerJobId, dbErr.Error())
					return fmt.Errorf("fail to commit: [%s] inner_job_id [%d] %w", hostGroup, zabbixIconData.InnerJobId, err)
				}

				logger.JaLog("JAZABBIXLINK400018", logData, funcName, hostGroup, zabbixIconData.InnerJobId)

				// Trigger force reload
				select {
				case forceReloadChan <- struct{}{}:
					logger.JaLog("JAZABBIXLINK400029", logData, funcName)
				default:
					// if one already pending, skip
				}

				err = prepareEventData(netConn, common.ZabbixIconResult{
					Output: zabbixIconData,
					Error:  "",
				})
				if err != nil {
					logger.JaLog("JAZABBIXLINK200018", logData, funcName, hostGroup, zabbixIconData.InnerJobId, err.Error())
					return fmt.Errorf("fail to send event data via UDS socket: [%s] inner_job_id [%d] %w", hostGroup, zabbixIconData.InnerJobId, err)
				}

				logger.JaLog("JAZABBIXLINK400017", logData, funcName, hostGroup, zabbixIconData.InnerJobId)

				logger.JaLog("JAZABBIXLINK400009", logData, funcName, zabbixIconData.InnerJobId)
			}
		default:
			logger.JaLog("JAZABBIXLINK200010", logData, funcName, hostGroup, zabbixIconData.InnerJobId, zabbixIconData.LinkOperation)
		}

	case 1: /* Host */
		/* Work together decision */
		switch zabbixIconData.LinkOperation {
		case 0, 1: /* 0 - Activation */
			/* 1- Invalidation */
			if zabbixIconData.TestFlag == 1 {
				return testFlagOn
			} else {
				err := checkAccessPermission(dbConn, zabbixIconData)
				if err != nil {
					logger.JaLog("JAZABBIXLINK200001", logData, host, zabbixIconData.InnerJobId, err.Error())
					return fmt.Errorf("permission denied: [%s] inner_job_id [%d] %w", host, zabbixIconData.InnerJobId, err)
				}

				err = dbConn.Begin()
				if err != nil {
					logger.JaLog("JAZABBIXLINK200021", logData, funcName, host, zabbixIconData.InnerJobId, err.Error())
					return fmt.Errorf("fail to start transaction: [%s] inner_job_id [%d] %w", host, zabbixIconData.InnerJobId, err)
				}

				dbErr = setHostStatus(zabbixIconData.HostId, zabbixIconData.LinkOperation)
				if dbErr != nil {
					logger.JaLog("JAZABBIXLINK200003", logData, funcName, zabbixIconData.InnerJobId, dbErr.Error())
					return fmt.Errorf("fail to set host status: inner_job_id [%d] %w", zabbixIconData.InnerJobId, err)
				}

				dbErr = dbConn.Commit()
				if dbErr != nil {
					logger.JaLog("JAZABBIXLINK200020", logData, funcName, host, zabbixIconData.InnerJobId, dbErr.Error())
					return fmt.Errorf("fail to commit: [%s] inner_job_id [%d] %w", host, zabbixIconData.InnerJobId, err)
				}

				logger.JaLog("JAZABBIXLINK400018", logData, funcName, host, zabbixIconData.InnerJobId)

				// Trigger force reload
				select {
				case forceReloadChan <- struct{}{}:
					logger.JaLog("JAZABBIXLINK400029", logData, funcName)
				default:
					// if one already pending, skip
				}
				err = prepareEventData(netConn, common.ZabbixIconResult{
					Output: zabbixIconData,
					Error:  "",
				})
				if err != nil {
					logger.JaLog("JAZABBIXLINK200018", logData, funcName, host, zabbixIconData.InnerJobId, err.Error())
					return fmt.Errorf("fail to send event data via UDS socket: [%s] inner_job_id [%d] %w", host, zabbixIconData.InnerJobId, err)
				}

				logger.JaLog("JAZABBIXLINK400017", logData, funcName, host, zabbixIconData.InnerJobId)

				logger.JaLog("JAZABBIXLINK400010", logData, funcName, zabbixIconData.InnerJobId)
			}
		case 2: /* State acquisition */
			hostStatus, err := getHostStatus(zabbixIconData.HostId)
			if err != nil {
				logger.JaLog("JAZABBIXLINK200004", logData, funcName, zabbixIconData.InnerJobId, err.Error())
				return fmt.Errorf("fail to get host status: inner_job_id [%d] %w", zabbixIconData.InnerJobId, err)
			}

			logger.JaLog("JAZABBIXLINK400013", logData, funcName, zabbixIconData.InnerJobId)

			zabbixIconData.ZBXLastStatus = hostStatus

			err = prepareEventData(netConn, common.ZabbixIconResult{
				Output: zabbixIconData,
				Error:  "",
			})
			if err != nil {
				logger.JaLog("JAZABBIXLINK200018", logData, funcName, host, zabbixIconData.InnerJobId, err.Error())
				return fmt.Errorf("fail to send event data via UDS socket: [%s] inner_job_id [%d] %w", host, zabbixIconData.InnerJobId, err)
			}

			logger.JaLog("JAZABBIXLINK400017", logData, funcName, host, zabbixIconData.InnerJobId)
		default:
			logger.JaLog("JAZABBIXLINK200010", logData, funcName, host, zabbixIconData.InnerJobId, zabbixIconData.LinkOperation)
		}

	case 2: /* Item */
		/* Work together decision */
		switch zabbixIconData.LinkOperation {
		case 0, 1: /* 0 - Activation */
			/* 1 - Invalidation */
			if zabbixIconData.TestFlag == 1 {
				return testFlagOn
			} else {
				err := checkAccessPermission(dbConn, zabbixIconData)
				if err != nil {
					logger.JaLog("JAZABBIXLINK200001", logData, item, zabbixIconData.InnerJobId, err.Error())
					return fmt.Errorf("permission denied: [%s] inner_job_id [%d] %w", item, zabbixIconData.InnerJobId, err)
				}

				err = dbConn.Begin()
				if err != nil {
					logger.JaLog("JAZABBIXLINK200021", logData, funcName, item, zabbixIconData.InnerJobId, err.Error())
					return fmt.Errorf("fail to start transaction: [%s] inner_job_id [%d] %w", item, zabbixIconData.InnerJobId, err)
				}

				dbErr = setItemStatus(zabbixIconData.ItemId, zabbixIconData.LinkOperation)
				if dbErr != nil {
					logger.JaLog("JAZABBIXLINK200005", logData, funcName, zabbixIconData.InnerJobId, dbErr.Error())
					return fmt.Errorf("fail to set item status: inner_job_id [%d] %w", zabbixIconData.InnerJobId, err)
				}

				dbErr = dbConn.Commit()
				if dbErr != nil {
					logger.JaLog("JAZABBIXLINK200020", logData, funcName, item, zabbixIconData.InnerJobId, dbErr.Error())
					return fmt.Errorf("fail to commit: [%s] inner_job_id [%d] %w", item, zabbixIconData.InnerJobId, err)
				}

				logger.JaLog("JAZABBIXLINK400018", logData, funcName, item, zabbixIconData.InnerJobId)

				err = prepareEventData(netConn, common.ZabbixIconResult{
					Output: zabbixIconData,
					Error:  "",
				})
				if err != nil {
					logger.JaLog("JAZABBIXLINK200018", logData, funcName, item, zabbixIconData.InnerJobId, err.Error())
					return fmt.Errorf("fail to send event data via UDS socket: [%s] inner_job_id [%d] %w", item, zabbixIconData.InnerJobId, err)
				}

				logger.JaLog("JAZABBIXLINK400017", logData, funcName, item, zabbixIconData.InnerJobId)

				logger.JaLog("JAZABBIXLINK400011", logData, funcName, zabbixIconData.InnerJobId)
			}
		case 2: /* State acquisition */
			itemStatus, err := getItemStatus(zabbixIconData.ItemId)
			if err != nil {
				logger.JaLog("JAZABBIXLINK200006", logData, funcName, zabbixIconData.InnerJobId, err.Error())
				return fmt.Errorf("fail to get item status: inner_job_id [%d] %w", zabbixIconData.InnerJobId, err)
			}

			logger.JaLog("JAZABBIXLINK400014", logData, funcName, zabbixIconData.InnerJobId)

			zabbixIconData.ZBXLastStatus = itemStatus

			err = prepareEventData(netConn, common.ZabbixIconResult{
				Output: zabbixIconData,
				Error:  "",
			})
			if err != nil {
				logger.JaLog("JAZABBIXLINK200018", logData, funcName, item, zabbixIconData.InnerJobId, err.Error())
				return fmt.Errorf("fail to send event data via UDS socket: [%s] inner_job_id [%d] %w", item, zabbixIconData.InnerJobId, err)
			}

			logger.JaLog("JAZABBIXLINK400017", logData, funcName, item, zabbixIconData.InnerJobId)
		case 3: /* Data acquisition */
			itemData, err := getItemData(zabbixIconData.ItemId)
			if err != nil {
				logger.JaLog("JAZABBIXLINK200007", logData, funcName, zabbixIconData.InnerJobId, err.Error())
				return fmt.Errorf("fail to get item data: inner_job_id [%d] %w", zabbixIconData.InnerJobId, err)
			}

			logger.JaLog("JAZABBIXLINK400015", logData, funcName, zabbixIconData.InnerJobId)

			zabbixIconData.ZBXDataType = itemData.valueType
			zabbixIconData.ZBXLatestData = itemData.value

			err = prepareEventData(netConn, common.ZabbixIconResult{
				Output: zabbixIconData,
				Error:  "",
			})
			if err != nil {
				logger.JaLog("JAZABBIXLINK200018", logData, funcName, itemDataString, zabbixIconData.InnerJobId, err.Error())
				return fmt.Errorf("fail to send event data via UDS socket: [%s] inner_job_id [%d] %w", itemDataString, zabbixIconData.InnerJobId, err)
			}

			logger.JaLog("JAZABBIXLINK400017", logData, funcName, itemDataString, zabbixIconData.InnerJobId)
		default:
			logger.JaLog("JAZABBIXLINK200010", logData, funcName, item, zabbixIconData.InnerJobId, zabbixIconData.LinkOperation)
		}

	case 3: /* Trigger */
		/* Work together decision */
		switch zabbixIconData.LinkOperation {
		case 0, 1: /* 0 - Activation */
			/* 1 - Invalidation */
			if zabbixIconData.TestFlag == 1 {
				return testFlagOn
			} else {
				err := checkAccessPermission(dbConn, zabbixIconData)
				if err != nil {
					logger.JaLog("JAZABBIXLINK200001", logData, trigger, zabbixIconData.InnerJobId, err.Error())
					return fmt.Errorf("permission denied: [%s] inner_job_id [%d] %w", trigger, zabbixIconData.InnerJobId, err)
				}

				err = dbConn.Begin()
				if err != nil {
					logger.JaLog("JAZABBIXLINK200021", logData, funcName, trigger, zabbixIconData.InnerJobId, err.Error())
					return fmt.Errorf("fail to start transaction: [%s] inner_job_id [%d] %w", trigger, zabbixIconData.InnerJobId, err)
				}

				dbErr = setTriggerStatus(zabbixIconData.TriggerId, zabbixIconData.LinkOperation)
				if dbErr != nil {
					logger.JaLog("JAZABBIXLINK200008", logData, funcName, zabbixIconData.InnerJobId, dbErr.Error())
					return fmt.Errorf("fail to set trigger status: inner_job_id [%d] %w", zabbixIconData.InnerJobId, err)
				}

				dbErr = dbConn.Commit()
				if dbErr != nil {
					logger.JaLog("JAZABBIXLINK200020", logData, funcName, trigger, zabbixIconData.InnerJobId, dbErr.Error())
					return fmt.Errorf("fail to commit: [%s] inner_job_id [%d] %w", trigger, zabbixIconData.InnerJobId, err)
				}

				logger.JaLog("JAZABBIXLINK400018", logData, funcName, trigger, zabbixIconData.InnerJobId)

				err = prepareEventData(netConn, common.ZabbixIconResult{
					Output: zabbixIconData,
					Error:  "",
				})
				if err != nil {
					logger.JaLog("JAZABBIXLINK200018", logData, funcName, trigger, zabbixIconData.InnerJobId, err.Error())
					return fmt.Errorf("fail to send event data via UDS socket: [%s] inner_job_id [%d] %w", trigger, zabbixIconData.InnerJobId, err)
				}

				logger.JaLog("JAZABBIXLINK400017", logData, funcName, trigger, zabbixIconData.InnerJobId)

				logger.JaLog("JAZABBIXLINK400012", logData, funcName, zabbixIconData.InnerJobId)
			}
		case 2: /* State acquisition */
			triggerStatus, err := getTriggerStatus(zabbixIconData.TriggerId)
			if err != nil {
				logger.JaLog("JAZABBIXLINK200009", logData, funcName, zabbixIconData.InnerJobId, err.Error())
				return fmt.Errorf("fail to get trigger status: inner_job_id [%d] %w", zabbixIconData.InnerJobId, err)
			}

			zabbixIconData.ZBXLastStatus = triggerStatus

			logger.JaLog("JAZABBIXLINK400016", logData, funcName, zabbixIconData.InnerJobId)

			err = prepareEventData(netConn, common.ZabbixIconResult{
				Output: zabbixIconData,
				Error:  "",
			})

			if err != nil {
				logger.JaLog("JAZABBIXLINK200018", logData, funcName, trigger, zabbixIconData.InnerJobId, err.Error())
				return fmt.Errorf("fail to send event data via UDS socket: [%s] inner_job_id [%d] %w", trigger, zabbixIconData.InnerJobId, err)
			}

			logger.JaLog("JAZABBIXLINK400017", logData, funcName, trigger, zabbixIconData.InnerJobId)
		default:
			logger.JaLog("JAZABBIXLINK200010", logData, funcName, trigger, zabbixIconData.InnerJobId, zabbixIconData.LinkOperation)
		}
	default:
		logger.JaLog("JAZABBIXLINK200011", logData, funcName, zabbixIconData.InnerJobId, zabbixIconData.LinkTarget)
	}
	return nil
}

func prepareEventData(netConn *common.NetConnection, result common.ZabbixIconResult) error {
	err := netConn.Send(result)
	if err != nil {
		return err
	}
	return nil
}

func checkAccessPermission(db database.DBConnection, zabbixIconData common.IconLinkData) error {
	funcName := "checkAccessPermission"
	var hostGroupName string

	logger.JaLog("JAZABBIXLINK400002", logger.Logging{}, funcName, zabbixIconData.GroupId)

	userResult, err := db.Select(
		"select r.type from users u,role r where u.username = '%s' and r.roleid = u.roleid",
		zabbixIconData.ExecUserName,
	)
	if err != nil {
		return fmt.Errorf("fail to select [role type]: %w", err)
	}

	defer userResult.Free()
	if userResult.HasNextRow() {
		row, err := userResult.Fetch()
		if err != nil {
			return fmt.Errorf("fail to fetch rows [role type]: %w", err)
		}

		roleType, err := strconv.Atoi(row["type"])
		if err != nil {
			return fmt.Errorf("fail to convert data type [role type] string to integer: %w", err)
		}

		if roleType == 3 {
			return nil
		}
	} else {
		return errors.New("user not found in zabbix")
	}

	res := 0

	userGroupResult, err := db.Select(
		"select c.permission from users a, users_groups b, rights c where c.id = %s and b.usrgrpid = c.groupid and a.userid = b.userid and a.username = %s",
		zabbixIconData.GroupId, zabbixIconData.ExecUserName,
	)
	if err != nil {
		return fmt.Errorf("fail to select [permission]: %w", err)
	}

	defer userGroupResult.Free()
loop:
	for userGroupResult.HasNextRow() {
		row, err := userGroupResult.Fetch()
		if err != nil {
			return fmt.Errorf("fail to fetch rows [permission]: %w", err)
		}
		permission, err := strconv.Atoi(row["permission"])
		if err != nil {
			return fmt.Errorf("fail to convert data type [permission] string to integer: %w", err)
		}

		switch permission {
		case 0: /* deny ? */
			res = 0
			break loop
		case 3: /* read-write ? */
			res = 1
		}
	}

	/* inaccessible ? */
	if res == 0 {
		hstgrpResult, err := db.Select(
			"select name from hstgrp where groupid = %s",
			zabbixIconData.GroupId,
		)
		if err != nil {
			return fmt.Errorf("fail to select [host group name]: %w", err)
		}

		defer hstgrpResult.Free()
		if hstgrpResult.HasNextRow() {
			row, err := hstgrpResult.Fetch()
			if err != nil {
				return fmt.Errorf("fail to fetch rows [host group name]: %w", err)
			}
			hostGroupName = row["name"]
		}

		return fmt.Errorf("do not have write permission for the host group [host group name - %s]: %w", hostGroupName, err)
	}
	return nil
}

func setHostGroupStatus(groupId string, status int) error {
	funcName := "setHostGroupStatus"
	logger.JaLog("JAZABBIXLINK400002", logger.Logging{}, funcName, groupId)

	result, err := zbxUpdater.GetHostId(groupId)
	if err != nil {
		return fmt.Errorf("fail to select [host id]: host_group_id [%s] %w", groupId, err)
	}

	defer result.Free()
	for result.HasNextRow() {
		row, err := result.Fetch()
		if err != nil {
			return fmt.Errorf("fail to fetch rows [host id]: host_group_id [%s] %w", groupId, err)
		}

		hostId := row["hostid"]
		err = setHostStatus(hostId, status)
		if err != nil {
			return err
		}
	}
	return nil
}

func setHostStatus(hostId string, status int) error {
	funcName := "setHostStatus"
	logger.JaLog("JAZABBIXLINK400003", logger.Logging{}, funcName, hostId, status)

	/* host state acquisition */
	hostStatus, err := getHostStatus(hostId)
	if err != nil {
		return err
	}
	/* host state acquisition success */
	/* host state update necessary */
	if hostStatus == status {
		return nil
	}

	if err := zbxUpdater.SetHostStatus(hostId, status); err != nil {
		return fmt.Errorf("fail to update [host status]: host_id [%s] %w", hostId, err)
	}

	return nil
}

func getHostStatus(hostId string) (int, error) {
	funcName := "getHostStatus"
	var status = 0

	logger.JaLog("JAZABBIXLINK400004", logger.Logging{}, funcName, hostId)

	result, err := zbxUpdater.GetHostStatus(hostId)
	if err != nil {
		return FAIL, fmt.Errorf("fail to select [host status]: host_id [%s] %w", hostId, err)
	}

	defer result.Free()
	if result.HasNextRow() {
		row, err := result.Fetch()
		if err != nil {
			return FAIL, fmt.Errorf("fail to fetch rows [host status]: host_id [%s] %w", hostId, err)
		}
		status, err = strconv.Atoi(row["status"])
		if err != nil {
			return FAIL, fmt.Errorf("fail to convert data type [status] string to integer: host_id [%s] %w", hostId, err)
		}
	} else {
		return FAIL, fmt.Errorf("no next row found for host status: host_id [%s]", hostId)
	}

	return status, nil
}

func setItemStatus(itemId string, status int) error {
	funcName := "setItemStatus"
	logger.JaLog("JAZABBIXLINK400005", logger.Logging{}, funcName, itemId, status)
	/* host state acquisition */
	itemStatus, err := getItemStatus(itemId)
	if err != nil {
		return err
	}
	/* host state acquisition success */
	/* host state update necessary */
	if itemStatus == status {
		return nil
	}
	err = zbxUpdater.SetItemStatus(itemId, status)
	if err != nil {
		return fmt.Errorf("fail to update [item status]: item_id [%s] %w", itemId, err)
	}

	return nil
}

func getItemStatus(itemId string) (int, error) {
	funcName := "getItemStatus"
	logger.JaLog("JAZABBIXLINK400006", logger.Logging{}, funcName, itemId)

	var (
		ret    = 0
		status = 0
		state  = 0
	)

	result, err := zbxUpdater.GetItemStatus(itemId)
	if err != nil {
		return FAIL, fmt.Errorf("fail to select [item status]: item_id [%s] %w", itemId, err)
	}

	defer result.Free()
	if result.HasNextRow() {
		row, err := result.Fetch()
		if err != nil {
			return FAIL, fmt.Errorf("fail to fetch rows [item status]: item_id [%s] %w", itemId, err)
		}
		status, err = strconv.Atoi(row["status"])
		if err != nil {
			return FAIL, fmt.Errorf("fail to convert data type [status] string to integer: item_id [%s] %w", itemId, err)
		}

		state, err = strconv.Atoi(row["state"])
		if err != nil {
			return FAIL, fmt.Errorf("fail to convert data type [state] string to integer: item_id [%s] %w", itemId, err)
		}

		if state != 0 {
			ret = 3
		} else {
			ret = status
		}
	} else {
		return FAIL, fmt.Errorf("no next row found for item status: item_id [%s]", itemId)
	}
	return ret, nil
}

func getItemData(getItemId string) (ItemData, error) {
	funcName := "getItemData"
	var valueType int
	var itemId string
	var value string

	logger.JaLog("JAZABBIXLINK400006", logger.Logging{}, funcName, getItemId)

	itemResult, err := zbxUpdater.GetItemData(getItemId)
	if err != nil {
		return ItemData{}, fmt.Errorf("fail to select [value type]: item_id [%s] %w", getItemId, err)
	}

	defer itemResult.Free()
	if itemResult.HasNextRow() {
		row, err := itemResult.Fetch()
		if err != nil {
			return ItemData{}, fmt.Errorf("fail to fetch rows [value type]: item_id [%s] %w", getItemId, err)
		}

		itemId = row["itemid"]
		valueType, err = strconv.Atoi(row["value_type"])
		if err != nil {
			return ItemData{}, fmt.Errorf("fail to convert data type [value type] string to integer: item_id [%s] %w", itemId, err)
		}
	} else {
		return ItemData{}, fmt.Errorf("no next row found for value type: item_id [%s]", itemId)
	}

	historyResult, err := zbxUpdater.GetHistoryData(itemId, valueType)
	if err != nil {
		return ItemData{}, fmt.Errorf("fail to select [value] from history data: item_id [%s] %w", itemId, err)
	}

	defer historyResult.Free()
	if historyResult.HasNextRow() {
		row, err := historyResult.Fetch()
		if err != nil {
			return ItemData{}, fmt.Errorf("fail to fetch rows [value] from history data: item_id [%s] %w", itemId, err)
		}
		value = row["value"]

	} else {
		return ItemData{}, fmt.Errorf("no next row found for value from history data: item_id %s", itemId)
	}
	return ItemData{
		valueType: valueType,
		value:     value,
	}, nil
}

func setTriggerStatus(triggerId string, status int) error {
	funcName := "setTriggerStatus"
	logger.JaLog("JAZABBIXLINK400007", logger.Logging{}, funcName, triggerId, status)

	/* trigger state acquisition */
	triggerStatus, err := getTriggerStatus(triggerId)
	if err != nil {
		return err
	}
	/* trigger state acquisition success */
	/* There trigger state change */
	if triggerStatus == status {
		return nil
	}

	err = zbxUpdater.SetTriggerStatus(triggerId, status)
	if err != nil {
		return fmt.Errorf("fail to update [trigger status]: trigger_id [%s] %w", triggerId, err)
	}

	return nil
}

func getTriggerStatus(triggerId string) (int, error) {
	funcName := "getTriggerStatus"
	logger.JaLog("JAZABBIXLINK400008", logger.Logging{}, funcName, triggerId)

	var (
		ret    = 0
		status = 0
		state  = 0
	)

	result, err := zbxUpdater.GetTriggerStatus(triggerId)
	if err != nil {
		return FAIL, fmt.Errorf("fail to select [trigger status]: trigger_id [%s] %w", triggerId, err)
	}

	defer result.Free()
	if result.HasNextRow() {
		row, err := result.Fetch()
		if err != nil {
			return FAIL, fmt.Errorf("fail to fetch rows [trigger status]: trigger_id [%s] %w", triggerId, err)
		}

		/* status setting */
		status, err = strconv.Atoi(row["status"])
		if err != nil {
			return FAIL, fmt.Errorf("fail to convert data type [status] string to integer: trigger_id [%s] %w", triggerId, err)
		}
		state, err = strconv.Atoi(row["state"])
		if err != nil {
			return FAIL, fmt.Errorf("fail to convert data type [state] string to integer: trigger_id [%s] %w", triggerId, err)
		}

		if state != 0 {
			ret = 3
		} else {
			ret = status
		}
	} else {
		return FAIL, fmt.Errorf("no next row found for trigger status: trigger_id %s", triggerId)
	}
	return ret, nil
}
