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

import (
	"encoding/json"
	"fmt"
	"os"
	"strconv"

	"jobarranger2/src/libs/golibs/common"
	"jobarranger2/src/libs/golibs/event"
	"jobarranger2/src/libs/golibs/logger/logger"
)

func getJobResult(ret string) (int, error) {
	clientExitCode, err := strconv.Atoi(ret)
	if err != nil {
		return 1, fmt.Errorf("could not get jobResult. resultMap[.ret]: %s, error:%s", ret, err.Error())
	}

	switch clientExitCode {
	case common.JA_JOBRESULT_SUCCEED:
		return common.JA_JOBRESULT_SUCCEED, nil
	case common.JA_JOBEXEC_FAIL:
		return common.JA_JOBEXEC_FAIL, nil
	case common.JA_JOBRESULT_PRE_EXEC_FAIL:
		return common.JA_JOBRESULT_PRE_EXEC_FAIL, nil
	case common.JA_JOBEXEC_IGNORE:
		return common.JA_JOBEXEC_IGNORE, nil
	default:
		return common.JA_JOBRESULT_FAIL, nil
	}
}

func agentlessIconEnd(processData common.IconExecutionProcessData, resultMap map[string][]byte) error {
	var err error
	var iconData common.IconLessData

	dataMap := make(map[string]interface{})

	// icon data
	err = json.Unmarshal(resultMap[".data"], &dataMap)
	if err != nil {
		return fmt.Errorf("could not get dataMap. resultMap[.data]: %s, error:%s", string(resultMap[".data"]), err.Error())
	}
	processData.RunJobData.Data = dataMap

	err = json.Unmarshal(resultMap[".data"], &iconData)
	if err != nil {
		return fmt.Errorf("could not get iconData. resultMap[.data]: %s, error:%s", string(resultMap[".data"]), err.Error())
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventIconResultUpdate,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.IconResultManagerProcess,
			Data: common.IconExecutionProcessData{
				JobResult: common.JobResultData{
					JobRunRequestData: common.JobRunRequestData{
						JobID: strconv.FormatUint(processData.RunJobData.InnerJobID, 10),
					},
					ReturnCode: iconData.JobExitCode,
					StdOut:     processData.JobResult.StdOut,
					StdErr:     processData.JobResult.StdErr,
					Result:     common.JA_JOBRESULT_SUCCEED,
					Message:    processData.JobResult.Message,
				},
				RunJobData:         processData.RunJobData,
				RunJobVariableData: processData.RunJobVariableData,
				RunJobnetData:      processData.RunJobnetData,
			},
		},
	}

	return event.CreateNextEvent(
		eventData, processData.RunJobData.InnerJobnetID,
		processData.RunJobnetData.JobnetID, processData.RunJobData.InnerJobID,
	)
}

func extIconEnd(processData common.IconExecutionProcessData, resultMap map[string][]byte) error {
	var err error
	var iconData common.IconExtData

	dataMap := make(map[string]interface{})

	// icon data
	err = json.Unmarshal(resultMap[".data"], &dataMap)
	if err != nil {
		return fmt.Errorf("could not get dataMap. resultMap[.data]: %s, error:%s", string(resultMap[".data"]), err.Error())
	}
	processData.RunJobData.Data = dataMap

	err = json.Unmarshal(resultMap[".data"], &iconData)
	if err != nil {
		return fmt.Errorf("could not get iconData. resultMap[.data]: %s, error:%s", string(resultMap[".data"]), err.Error())
	}

	// exec_manager doesn't need to make icon end event for timer icons
	if iconData.CommandId == common.JA_CMD_TIME || iconData.CommandId == common.JA_CMD_SLEEP {
		return nil
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventIconResultUpdate,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.IconResultManagerProcess,
			Data: common.IconExecutionProcessData{
				JobResult: common.JobResultData{
					JobRunRequestData: common.JobRunRequestData{
						JobID: strconv.FormatUint(processData.RunJobData.InnerJobID, 10),
					},
					ReturnCode: iconData.JobExitCode,
					StdOut:     processData.JobResult.StdOut,
					StdErr:     processData.JobResult.StdErr,
					Result:     common.JA_JOBRESULT_SUCCEED,
					Message:    processData.JobResult.Message,
				},
				RunJobData:         processData.RunJobData,
				RunJobVariableData: processData.RunJobVariableData,
				RunJobnetData:      processData.RunJobnetData,
			},
		},
	}

	return event.CreateNextEvent(
		eventData, processData.RunJobData.InnerJobnetID,
		processData.RunJobnetData.JobnetID, processData.RunJobData.InnerJobID,
	)
}

func commonIconEnd(processData common.IconExecutionProcessData, resultMap map[string][]byte) error {
	var err error

	dataMap := make(map[string]interface{})

	// icon data
	err = json.Unmarshal(resultMap[".data"], &dataMap)
	if err != nil {
		return fmt.Errorf("could not get icondata. resultMap[.data]: %s, error:%s", string(resultMap[".data"]), err.Error())
	}

	processData.RunJobData.Data = dataMap

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventIconResultUpdate,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.IconResultManagerProcess,
			Data: common.IconExecutionProcessData{
				JobResult: common.JobResultData{
					JobRunRequestData: common.JobRunRequestData{
						JobID: strconv.FormatUint(processData.RunJobData.InnerJobID, 10),
					},
					StdOut:  processData.JobResult.StdOut,
					StdErr:  processData.JobResult.StdErr,
					Result:  common.JA_JOBRESULT_SUCCEED,
					Message: processData.JobResult.Message,
				},
				RunJobData:         processData.RunJobData,
				RunJobVariableData: processData.RunJobVariableData,
				RunJobnetData:      processData.RunJobnetData,
			},
		},
	}

	return event.CreateNextEvent(
		eventData, processData.RunJobData.InnerJobnetID,
		processData.RunJobnetData.JobnetID, processData.RunJobData.InnerJobID,
	)
}

func fcopyIconEnd(processData common.IconExecutionProcessData, resultMap map[string][]byte) error {
	var err error

	// icon data
	var iconData common.IconFCopyData
	err = json.Unmarshal(resultMap[".data"], &iconData)
	if err != nil {
		return fmt.Errorf("could not get IconFCopyData. resultMap[.data]: %s, error:%s", string(resultMap[".data"]), err.Error())
	}

	// If the agent is not jaz1, this event will be ignored
	if !iconData.IsJaz1 {
		return nil
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventIconResultUpdate,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.IconResultManagerProcess,
			Data: common.IconExecutionProcessData{
				JobResult: common.JobResultData{
					JobRunRequestData: common.JobRunRequestData{
						JobID: strconv.FormatUint(processData.RunJobData.InnerJobID, 10),
						Type:  common.AgentJobTypePutFile,
					},
					StdOut:  processData.JobResult.StdOut,
					StdErr:  processData.JobResult.StdErr,
					Result:  common.JA_JOBRESULT_SUCCEED,
					Message: processData.JobResult.Message,
				},
				RunJobData:         processData.RunJobData,
				RunJobVariableData: processData.RunJobVariableData,
				RunJobnetData:      processData.RunJobnetData,
			},
		},
	}

	return event.CreateNextEvent(
		eventData, processData.RunJobData.InnerJobnetID,
		processData.RunJobnetData.JobnetID, processData.RunJobData.InnerJobID,
	)
}

func wIconEnd(processData common.IconExecutionProcessData, runData common.IconRunData) error {
	var err error

	// icon data
	var iconData common.IconWData
	err = json.Unmarshal(runData.ClientData[".data"], &iconData)
	if err != nil {
		return fmt.Errorf("could not get IconWData. runData.ClientData[.data]: %s, error:%s", string(runData.ClientData[".data"]), err.Error())
	}

	// If end_cout hits the boot_count, this event will be ignored
	if !iconData.EndCountPlusFlag {
		// ignore event
		return nil
	}

	processData.RunJobData.Data = map[string]interface{}{
		"end_count_plus_flag": true,
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventIconResultUpdate,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.IconResultManagerProcess,
			Data: common.IconExecutionProcessData{
				JobResult: common.JobResultData{
					JobRunRequestData: common.JobRunRequestData{
						JobID: strconv.FormatUint(processData.RunJobData.InnerJobID, 10),
					},
					StdOut:  processData.JobResult.StdOut,
					StdErr:  processData.JobResult.StdErr,
					Result:  common.JA_JOBRESULT_SUCCEED,
					Message: processData.JobResult.Message,
				},
				RunJobData:         processData.RunJobData,
				RunJobVariableData: processData.RunJobVariableData,
				RunJobnetData:      processData.RunJobnetData,
			},
		},
	}

	return event.CreateNextEvent(
		eventData, processData.RunJobData.InnerJobnetID,
		processData.RunJobnetData.JobnetID, processData.RunJobData.InnerJobID,
	)
}

func ifEndIconEnd(processData common.IconExecutionProcessData, runData common.IconRunData) error {
	var err error

	// icon data
	var iconData common.IconIfEndData
	err = json.Unmarshal(runData.ClientData[".data"], &iconData)
	if err != nil {
		return fmt.Errorf("could not get IconWData. runData.ClientData[.data]: %s, error:%s", string(runData.ClientData[".data"]), err.Error())
	}

	// If end_cout hits the boot_count, this event will be ignored
	if !iconData.EndCountPlusFlag {
		// ignore event
		return nil
	}

	processData.RunJobData.Data = map[string]interface{}{
		"end_count_plus_flag": true,
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventIconResultUpdate,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.IconResultManagerProcess,
			Data: common.IconExecutionProcessData{
				JobResult: common.JobResultData{
					JobRunRequestData: common.JobRunRequestData{
						JobID: strconv.FormatUint(processData.RunJobData.InnerJobID, 10),
					},
					StdOut:  processData.JobResult.StdOut,
					StdErr:  processData.JobResult.StdErr,
					Result:  common.JA_JOBRESULT_SUCCEED,
					Message: processData.JobResult.Message,
				},
				RunJobData:         processData.RunJobData,
				RunJobVariableData: processData.RunJobVariableData,
				RunJobnetData:      processData.RunJobnetData,
			},
		},
	}

	return event.CreateNextEvent(
		eventData, processData.RunJobData.InnerJobnetID,
		processData.RunJobnetData.JobnetID, processData.RunJobData.InnerJobID,
	)
}

func EventIconEnd(eventData common.EventData) error {
	loadCommonVars()
	var err error
	log := logger.Logging{}
	jobResult := common.JA_JOBRESULT_FAIL

	runData, ok := eventData.NextProcess.Data.(common.IconRunData)
	if !ok {
		err := fmt.Errorf("expected data type: common.IconRunData), data: %v", eventData.NextProcess.Data)
		log.JaLog("JAEXEC100001", eventData.Event.Name, err.Error(), eventData.NextProcess.Data)
		os.Exit(1)
	}

	processData := runData.ExecProcessData

	// set up log data
	log.InnerJobnetID = processData.RunJobData.InnerJobnetID
	log.InnerJobID = processData.RunJobData.InnerJobID
	log.JobID = processData.RunJobData.JobID
	log.JobType = processData.RunJobData.IconType
	log.JobnetID = processData.RunJobnetData.JobnetID

	// clean up incoming event data on finish
	defer cleanupEventData(eventData, log)

	// clean up finished job's transaction files
	defer func() {
		err = cleanupTransactionFiles(jobResult, runData.TransactionFileId)
		if err != nil {
			log.JaLog("JAEXECEND200007", log.InnerJobnetID, log.InnerJobID,
				log.JobnetID, log.JobID, processData.RunJobData.IconType, err.Error())
		}
	}()

	log.JaLog("JAEXECEND400001", log.InnerJobnetID, log.InnerJobID,
		log.JobnetID, log.JobID)

	// set stdout and stderr to processData
	processData.JobResult.StdOut = string(runData.ClientData[common.ClientStdoutExt])
	processData.JobResult.StdErr = string(runData.ClientData[common.ClientStderrExt])

	// get client exit code
	jobResult, err = getJobResult(string(runData.ClientData[common.ClientReturnExt]))
	if err != nil {
		logMessage, _ := log.JaLog("JAEXECEND200003", log.InnerJobnetID, log.InnerJobID,
			log.JobnetID, log.JobID, processData.RunJobData.IconType, err.Error(),
		)

		// no need to set runerr for abort and check processes
		if processData.RunJobData.MethodFlag == common.MethodAbort ||
			processData.RunJobData.IconType == common.IconTypeCheckJob {
			return nil
		}

		return setRunErr(logMessage, processData)
	}

	// check if the client with ignore
	if jobResult == common.JA_JOBEXEC_IGNORE {
		// clients will exit with ignore when it detects duplicated execution

		return nil
	}

	// check if the client with abort task
	if processData.RunJobData.MethodFlag == common.MethodAbort {
		// print log on failure
		if jobResult != common.JA_JOBRESULT_SUCCEED {
			log.JaLog("JAEXECEND200005", log.InnerJobnetID, log.InnerJobID,
				log.JobnetID, log.JobID, processData.RunJobData.IconType, processData.JobResult.StdErr,
			)
		}

		return nil
	}

	// check if the check job client
	if processData.RunJobData.IconType == common.IconTypeCheckJob {
		// print log on failure
		if jobResult != common.JA_JOBRESULT_SUCCEED {
			log.JaLog("JAEXECEND200006", log.InnerJobnetID, log.InnerJobID,
				log.JobnetID, log.JobID, processData.RunJobData.IconType, processData.JobResult.StdErr,
			)
		}

		return nil
	}

	// check client exit code
	if jobResult == common.JA_JOBEXEC_FAIL {
		logMessage, _ := log.JaLog("JAEXECEND200004", log.InnerJobnetID, log.InnerJobID,
			log.JobnetID, log.JobID, processData.RunJobData.IconType, jobResult, processData.JobResult.StdErr,
		)

		return setExecFail(logMessage, processData)
	}
	if jobResult != common.JA_JOBRESULT_SUCCEED {
		logMessage, _ := log.JaLog("JAEXECEND200004", log.InnerJobnetID, log.InnerJobID,
			log.JobnetID, log.JobID, processData.RunJobData.IconType, jobResult, processData.JobResult.StdErr,
		)

		switch processData.RunJobData.IconType {
		case common.IconTypeLess:
			iconData, err := getIconData[common.IconLessData](runData.ClientData[".data"])
			if err != nil {
				log.JaLog("JAEXECEND300001", log.InnerJobnetID, log.InnerJobID,
					log.JobnetID, log.JobID, processData.RunJobData.IconType, err.Error())
				break
			}

			processData.JobResult.ReturnCode = iconData.JobExitCode
		case common.IconTypeExtJob:
			iconData, err := getIconData[common.IconExtData](runData.ClientData[".data"])
			if err != nil {
				log.JaLog("JAEXECEND300001", log.InnerJobnetID, log.InnerJobID,
					log.JobnetID, log.JobID, processData.RunJobData.IconType, err.Error())
				break
			}

			processData.JobResult.ReturnCode = iconData.JobExitCode
		case common.IconTypeFCopy:
			processData.JobResult.JobRunRequestData.Type = common.AgentJobTypePutFile
		default:
			// other local icons and agent related icons don't need job exit code to set run error
		}

		if jobResult == common.JA_JOBRESULT_PRE_EXEC_FAIL {
			processData.JobResult.PreExecFailFlag = true
		}
		return setRunErr(logMessage, processData)
	}

	switch processData.RunJobData.IconType {
	case common.IconTypeExtJob:
		err = extIconEnd(processData, runData.ClientData)

	case common.IconTypeIf:
		err = commonIconEnd(processData, runData.ClientData)

	case common.IconTypeCalc:
		err = commonIconEnd(processData, runData.ClientData)

	case common.IconTypeLess:
		err = agentlessIconEnd(processData, runData.ClientData)

	case common.IconTypeFCopy:
		err = fcopyIconEnd(processData, runData.ClientData)

	case common.IconTypeInfo:
		err = commonIconEnd(processData, runData.ClientData)

	case common.IconTypeLink:
		err = commonIconEnd(processData, runData.ClientData)

	case common.IconTypeValue:
		err = commonIconEnd(processData, runData.ClientData)

	case common.IconTypeW:
		err = wIconEnd(processData, runData)

	case common.IconTypeIfEnd:
		err = ifEndIconEnd(processData, runData)

	case common.IconTypeStart,
		common.IconTypeEnd,
		common.IconTypeM,
		common.IconTypeL,
		common.IconTypeJobnet,
		common.IconTypeJob,
		common.IconTypeRel,
		common.IconTypeFWait,
		common.IconTypeReboot,
		common.IconTypeTask:

		// These icons dont need to make new event.
		err = nil
	default:
		err = fmt.Errorf("unkown icon: %d", processData.RunJobData.IconType)
	}

	if err != nil {
		logMessage, _ := log.JaLog("JAEXECEND200002", log.InnerJobnetID, log.InnerJobID,
			log.JobnetID, log.JobID, processData.RunJobData.IconType, processData.JobResult.StdErr, err.Error(),
		)

		return setRunErr(logMessage, processData)
	}

	return nil
}
