/*
** 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"
	"fmt"
	"strconv"
	"time"

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

func StartDaemonWorkers(data common.Data) {
}

func ProcessEventData(data common.Data) {
	const funcName = "ProcessEventData"
	var (
		eventData       common.EventData
		nextdata        common.EventData
		flowProcessData common.FlowProcessData
		filePath        string
	)

	getFilePath := func(eventData *common.EventData) string {
		if eventData == nil || len(eventData.Transfer.Files) == 0 {
			return ""
		}
		switch eventData.Event.Name {
		case common.EventOneWayIconRun, common.EventIconSkip,
			common.EventFlowRunErr:
			return eventData.Transfer.Files[0].Destination
		case common.EventIconResultUpdate:
			return eventData.Transfer.Files[0].Source
		case common.EventIconTimeout:
			// fallback if needed
			return eventData.Transfer.Files[0].Source
		default:
			return ""
		}
	}

	if err := utils.UnmarshalEventData(data.EventData, &eventData); err != nil {
		logger.JaLog("JAICONRESULTNORMAL200004", logger.Logging{}, funcName, data.EventData, err.Error())
		_ = utils.MoveToSubFolder(filePath, "error")
		return
	}

	filePath = getFilePath(&eventData)

	var processData common.IconExecutionProcessData
	if err := utils.Convert(eventData.NextProcess.Data, &processData); err != nil {
		logger.JaLog("JAICONRESULTNORMAL200005", logger.Logging{}, funcName, eventData.NextProcess.Data, err.Error())
		_ = utils.MoveToSubFolder(filePath, "error")
		return
	}

	//check jazversion and if 1, extract DB data
	jazVersion := processData.JobResult.JazVersion
	if jazVersion == common.JazVer1 {
		innerJobID := processData.JobResult.JobRunRequestData.JobID
		innerJobIDInt, err := strconv.ParseUint(innerJobID, 10, 64)
		if err != nil {
			logger.JaLog("JAICONRESULTNORMAL200009", logger.Logging{}, funcName, innerJobID, err.Error())
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}

		iconType, status, dataJSON, innerJobnetID, jobnetID, err := getAgentData(data.DBConn, innerJobIDInt)
		if err != nil {
			logger.JaLog("JAICONRESULTNORMAL200015", logger.Logging{}, funcName, innerJobIDInt, err.Error())
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}

		processData.RunJobData.IconType = common.IconType(iconType)
		processData.JobResult.JobStatus = status
		if processData.RunJobData.Data == nil {
			processData.RunJobData.Data = make(map[string]interface{})
		}
		processData.RunJobData.Data = dataJSON
		processData.RunJobData.InnerJobnetID = innerJobnetID
		processData.RunJobData.InnerJobID = innerJobIDInt
		processData.RunJobnetData.JobnetID = jobnetID

		varData, err := getVariableData(data.DBConn, innerJobIDInt)
		if err == nil {
			processData.RunJobVariableData = *varData
		} else {
			logger.JaLog("JAICONRESULTNORMAL200016", logger.Logging{}, funcName, innerJobIDInt, err.Error())
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}
		// Assign updated processData back to eventData
		eventData.NextProcess.Data = processData

		// Marshal updated eventData back to bytes
		updatedEventDataBytes, err := json.Marshal(eventData)
		if err != nil {
			logger.JaLog("JAICONRESULTNORMAL200003", logger.Logging{}, funcName, eventData, err.Error())
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}

		// Update Data.EventData with new JSON bytes
		data.EventData = updatedEventDataBytes
	}

	logData := &logger.Logging{}
	logData, err := getLogData(&processData)
	if err != nil {
		logger.JaLog("JAICONRESULTNORMAL200013", logger.Logging{}, funcName, err.Error())
		_ = utils.MoveToSubFolder(filePath, "error")
		return
	}

	switch eventData.Event.Name {
	case common.EventIconResultUpdate, common.EventOneWayIconRun, common.EventFlowRunErr:

		innerJobIDStr := processData.JobResult.JobRunRequestData.JobID
		innerJobID, err := strconv.ParseUint(innerJobIDStr, 10, 64)
		if err != nil {
			logger.JaLog("JAICONRESULTNORMAL200009", *logData, funcName, innerJobIDStr, err.Error())
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}
		iconType := processData.RunJobData.IconType

		if ret := ProcessIconResult(innerJobID, filePath, eventData.Event.Name, &processData, &nextdata); ret != nil {
			logger.JaLog("JAICONRESULTNORMAL200010", *logData, funcName, ret.Error())
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}

		if processData.RunJobData.IconType == common.IconTypeFCopy && nextdata.NextProcess.Data == nil {
			ClearEventData(&nextdata, &processData)
			return
		}

		innerJobnetID := processData.RunJobData.InnerJobnetID
		jobnetID := processData.RunJobnetData.JobnetID

		if nextdata.NextProcess.Data == nil {
			flowProcessData = common.FlowProcessData{
				InnerJobnetId: innerJobnetID,
				JobnetId:      jobnetID,
				InnerJobId:    innerJobID,
				IconType:      iconType,
				Data:          processData,
			}
			if processData.RunJobData.IconType == common.IconTypeW || processData.RunJobData.IconType == common.IconTypeIfEnd {
				nextdata.Event.Name = common.EventEndCountPlus
			}
		} else if fpd, ok := nextdata.NextProcess.Data.(common.FlowProcessData); ok {
			flowProcessData = common.FlowProcessData{
				InnerJobnetId: innerJobnetID,
				JobnetId:      jobnetID,
				InnerJobId:    innerJobID,
				IconType:      iconType,
				FlowType:      fpd.FlowType,
				Status:        fpd.Status,
				Data:          processData,
			}
		} else {
			logger.JaLog("JAICONRESULTNORMAL200018", *logData, funcName)
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}

		nextdata.Event.UniqueKey = common.GetUniqueKey(common.IconResultManagerProcess)
		nextdata.NextProcess.Name = common.DBSyncerManagerProcess
		nextdata.NextProcess.Data = flowProcessData

		if err := sendEventToDBSyncer(innerJobID, innerJobnetID, jobnetID, nextdata, &processData); err != nil {
			logger.JaLog("JAICONRESULTNORMAL200012", *logData, funcName, err.Error())
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}

		if processData.RunJobData.IconType != common.IconTypeFCopy {
			if err := utils.MoveToSubFolder(filePath, "end"); err != nil {
				logger.JaLog("JAICONRESULTNORMAL200014", *logData, funcName, filePath, "in", "end", innerJobID)
				_ = utils.MoveToSubFolder(filePath, "error")
				return
			}
		}

	case common.EventIconSkip:

		innerJobIDStr := processData.JobResult.JobRunRequestData.JobID
		innerJobID, err := strconv.ParseUint(innerJobIDStr, 10, 64)
		if err != nil {
			logger.JaLog("JAICONRESULTNORMAL200009", *logData, funcName, innerJobIDStr, err.Error())
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}
		innerJobnetID := processData.RunJobData.InnerJobnetID
		jobnetID := processData.RunJobnetData.JobnetID
		iconType := processData.RunJobData.IconType
		// filePath = eventData.Transfer.Files[0].Source

		if err := ProcessIconSkip(innerJobID, eventData.Event.Name, &processData, &nextdata); err != nil {
			logger.JaLog("JAICONRESULTSKIP200002", *logData, funcName, err.Error())
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}

		flowProcessData = nextdata.NextProcess.Data.(common.FlowProcessData)
		flowProcessData = common.FlowProcessData{
			InnerJobnetId: innerJobnetID,
			JobnetId:      jobnetID,
			InnerJobId:    innerJobID,
			IconType:      iconType,
			Status:        flowProcessData.Status,
			Data:          processData,
		}

		nextdata.Event.UniqueKey = common.GetUniqueKey(common.IconResultManagerProcess)
		nextdata.NextProcess.Name = common.DBSyncerManagerProcess
		nextdata.NextProcess.Data = flowProcessData

		if err := sendEventToDBSyncer(innerJobID, innerJobnetID, jobnetID, nextdata, &processData); err != nil {
			logger.JaLog("JAICONRESULTNORMAL200012", *logData, funcName, err.Error())
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}

		if err := utils.MoveToSubFolder(filePath, "end"); err != nil {
			logger.JaLog("JAICONRESULTNORMAL200014", *logData, funcName, filePath, "in", "end", innerJobID)
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}

	case common.EventIconTimeout:

		innerJobIDStr := processData.JobResult.JobRunRequestData.JobID
		innerJobID, err := strconv.ParseUint(innerJobIDStr, 10, 64)
		if err != nil {
			logger.JaLog("JAICONRESULTNORMAL200009", *logData, funcName, innerJobIDStr, err.Error())
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}
		innerJobnetID := processData.RunJobData.InnerJobnetID
		jobnetID := processData.RunJobnetData.JobnetID

		if err := ProcessIconTimeout(innerJobID, eventData.Event.Name, &processData, &nextdata); err != nil {
			logger.JaLog("JAICONRESULTTIMEOUT200002", *logData, funcName, err.Error())
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}

		sqlCond := builder.NewSQLCondition(
			fmt.Sprintf("select status from %s where inner_job_id = %d", common.Ja2RunJobTable, innerJobID),
		).
			AddCondition(
				builder.NewCondition(common.ActionIgnore).
					Field("status", common.OpEq, common.StatusEnd).
					Build(),
			).
			AddCondition(
				builder.NewCondition(common.ActionIgnore).
					Field("status", common.OpEq, common.StatusRunErr).
					Build(),
			).
			AddCondition(
				builder.NewCondition(common.ActionExec).
					Field("status", common.OpNe, common.StatusEnd).
					Field("status", common.OpNe, common.StatusRunErr).
					Build(),
			).
			DefaultAction(common.ActionError).
			Build()

		nextdata.SQLConditions = []common.SQLCondition{sqlCond}

		if nextdata.Event.Name == common.EventIconTimeoutStop {
			flowProcessData = common.FlowProcessData{
				InnerJobId:    innerJobID,
				InnerJobnetId: innerJobnetID,
				JobnetId:      jobnetID,
				Data:          processData,
			}
			nextdata.NextProcess.Data = flowProcessData
		} else {
			jobnetRunData := common.JobnetRunData{
				InnerJobId:    innerJobID,
				InnerJobnetId: innerJobnetID,
				JobnetID:      jobnetID,
			}
			nextdata.NextProcess.Data = jobnetRunData
		}

		nextdata.Event.UniqueKey = common.GetUniqueKey(common.IconResultManagerProcess)
		nextdata.NextProcess.Name = common.DBSyncerManagerProcess

		if err := sendEventToDBSyncer(innerJobID, innerJobnetID, jobnetID, nextdata, &processData); err != nil {
			logger.JaLog("JAICONRESULTNORMAL200012", *logData, funcName, err)
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}

		if err := utils.MoveToSubFolder(filePath, "end"); err != nil {
			logger.JaLog("JAICONRESULTNORMAL200014", *logData, funcName, filePath, "in", "end", innerJobID)
			_ = utils.MoveToSubFolder(filePath, "error")
			return
		}

	default:
		logger.JaLog("JAICONRESULTNORMAL300004", *logData, funcName, eventData.Event.Name)
		_ = utils.MoveToSubFolder(filePath, "error")
		return
	}

	ClearEventData(&nextdata, &processData)
}

func ProcessIconResult(innerJobID uint64, filePath string, eventName common.EventName, processData *common.IconExecutionProcessData, nextdata *common.EventData) error {
	const funcName = "ProcessIconResult"
	logData := &logger.Logging{}
	logData, err := getLogData(processData)
	if err != nil {
		logger.JaLog("JAICONRESULTNORMAL200013", logger.Logging{}, funcName, err.Error())
		return err
	}

	jobType := processData.RunJobData.IconType
	fileType := processData.JobResult.JobRunRequestData.Type

	logger.JaLog("JAICONRESULTNORMAL400005", *logData, funcName, innerJobID, jobType)

	FillAfterVariable(innerJobID, processData, true)

	var retErr error
	eventFlag := true

	switch jobType {
	case common.JobTypeStart:
		retErr = ProcessStartIconResult(innerJobID, processData, nextdata)
	case common.JobTypeEnd:
		retErr = ProcessEndIconResult(innerJobID, processData, nextdata)
	case common.JobTypeIf:
		retErr = ProcessIfIconResult(innerJobID, processData, nextdata)
	case common.JobTypeValue:
		retErr = ProcessValueIconResult(innerJobID, processData, nextdata)
	case common.JobTypeJob, common.JobTypeFwait, common.JobTypeReboot:
		retErr = ProcessAgentJobResult(innerJobID, processData, nextdata)
	case common.JobTypeJobnet:
		retErr = ProcessJobnetIconResult(innerJobID, processData, nextdata)
	case common.JobTypeM:
		retErr = ProcessMIconResult(innerJobID, processData, nextdata)
	case common.JobTypeW:
		eventFlag, retErr = ProcessWIconResult(innerJobID, processData, nextdata)
	case common.JobTypeL:
		retErr = ProcessLIconResult(innerJobID, processData, nextdata)
	case common.JobTypeExtJob:
		retErr = ProcessExtIconResult(innerJobID, processData, nextdata)
	case common.JobTypeCalc:
		retErr = ProcessCalcIconResult(innerJobID, processData, nextdata)
	case common.JobTypeTask:
		retErr = ProcessTaskIconResult(innerJobID, processData, nextdata)
	case common.JobTypeInfo:
		retErr = ProcessInfoIconResult(innerJobID, processData, nextdata)
	case common.JobTypeIfEnd:
		eventFlag, retErr = ProcessIfEndIconResult(innerJobID, processData, nextdata)
	case common.JobTypeFcopy:
		eventFlag, retErr = ProcessFcopyResult(innerJobID, fileType, filePath, processData, nextdata)
	case common.JobTypeRel:
		retErr = ProcessReleaseIconResult(innerJobID, processData, nextdata)
	case common.JobTypeLess:
		retErr = ProcessAgentLessIconResult(innerJobID, processData, nextdata)
	case common.JobTypeLink:
		retErr = ProcessZabbixLinkIconResult(innerJobID, processData, nextdata)
	default:
		return err
	}

	if retErr != nil {
		return retErr
	}

	if eventFlag {
		if err := SetValueAfter(innerJobID, processData, nextdata); err != nil {
			return err
		}
	}

	return nil
}

func ProcessIconSkip(innerJobID uint64, eventName common.EventName, processData *common.IconExecutionProcessData, nextdata *common.EventData) error {
	const funcName = "ProcessIconSkip"
	logData := &logger.Logging{}
	logData, err := getLogData(processData)
	if err != nil {
		message, _ := logger.JaLog("JAICONRESULTNORMAL200013", logger.Logging{}, funcName, err.Error())
		return handleIconError(innerJobID, 2, message, processData, nextdata)
	}

	jobType := processData.RunJobData.IconType

	logger.JaLog("JAICONRESULTSKIP400001", *logData, funcName, innerJobID, jobType)

	err = FillAfterVariable(innerJobID, processData, false)
	if err != nil {
		message, _ := logger.JaLog("JAICONRESULTNORMAL200007", *logData, funcName, err.Error(), innerJobID)
		return handleIconError(innerJobID, 2, message, processData, nextdata)
	}

	if jobType == common.IconTypeStart || jobType == common.IconTypeEnd || jobType == common.IconTypeM || jobType == common.IconTypeW || jobType == common.IconTypeIf || jobType == common.IconTypeIfEnd || jobType == common.IconTypeL {
		message, _ := logger.JaLog("JAICONRESULTSKIP200001", *logData, funcName, jobType, innerJobID)
		return handleIconError(innerJobID, 2, message, processData, nextdata)
	}

	if err := SetValueAfter(innerJobID, processData, nextdata); err != nil {
		message, _ := logger.JaLog("JAICONRESULTNORMAL200008", *logData, funcName, err.Error(), innerJobID)
		return handleIconError(innerJobID, 2, message, processData, nextdata)
	}
	return SetEnd(innerJobID, 1, processData, nextdata)

}

func ProcessIconTimeout(innerJobID uint64, eventName common.EventName, processData *common.IconExecutionProcessData, nextdata *common.EventData) error {
	const funcName = "ProcessIconTimeout"
	logData := &logger.Logging{}
	logData, err := getLogData(processData)
	if err != nil {
		message, _ := logger.JaLog("JAICONRESULTNORMAL200013", logger.Logging{}, funcName, err.Error())
		return handleIconError(innerJobID, 2, message, processData, nextdata)
	}

	jobType := processData.RunJobData.IconType
	startTime := processData.RunJobData.StartTime
	startTimeStr := time.Unix(0, startTime).Format("2006-01-02 15:04:05")

	logger.JaLog("JAICONRESULTTIMEOUT400001", *logData, funcName, innerJobID, jobType)

	err = FillAfterVariable(innerJobID, processData, false)
	if err != nil {
		message, _ := logger.JaLog("JAICONRESULTNORMAL200007", *logData, funcName, err.Error(), innerJobID)
		return handleIconError(innerJobID, 2, message, processData, nextdata)
	}

	logger.JaLog("JAICONRESULTTIMEOUT400002", *logData, funcName, innerJobID, startTimeStr)

	iconTimeoutData, err := ExtractIconData[common.IconTimeoutData](processData.RunJobData.Data)
	if err != nil {
		message, _ := logger.JaLog("JAICONRESULTTIMEOUT200001", *logData, funcName, innerJobID, err.Error())
		return handleIconError(innerJobID, 2, message, processData, nextdata)
	}

	timeoutRunType := iconTimeoutData.TimeoutRunType
	timeout := iconTimeoutData.Timeout
	timeoutStr := time.Unix(int64(timeout), 0).Format("2006-01-02 15:04:05")
	jobID := processData.RunJobData.JobID
	jobnetID := processData.RunJobnetData.JobnetID
	execUsername := processData.RunJobnetData.ExecutionUserName

	message, _ := logger.JaLog("JAICONRESULTTIMEOUT300001", *logData, funcName, innerJobID, timeoutStr, startTimeStr, jobnetID, jobID, execUsername)
	err = MergeAfterVariable(processData, "JOBARG_MESSAGE", message)
	if err != nil {
		message, _ := logger.JaLog("JAICONRESULTNORMAL200006", *logData, funcName, "JOBARG_MESSAGE", message, innerJobID)
		return handleIconError(innerJobID, 2, message, processData, nextdata)
	}

	updateQuery := fmt.Sprintf(`UPDATE %s SET timeout_flag = 1 WHERE inner_job_id = ?`, common.Ja2RunJobTable)
	nextdata.AddPQ(updateQuery, innerJobID)

	if timeoutRunType == 1 {
		nextdata.Event.Name = common.EventIconTimeoutStop
	} else {
		nextdata.Event.Name = common.EventIconTimeoutWarning
	}

	logger.JaJobLog(common.JC_JOB_TIMEOUT, *logData)

	if err := SetValueAfter(innerJobID, processData, nextdata); err != nil {
		message, _ := logger.JaLog("JAICONRESULTNORMAL200008", *logData, funcName, err.Error(), innerJobID)
		return handleIconError(innerJobID, 2, message, processData, nextdata)
	}

	return nil
}

func sendEventToDBSyncer(innerJobID, innerJobnetID uint64, jobnetID string, data common.EventData, processData *common.IconExecutionProcessData) error {
	const funcName = "sendEventToDBSyncer"
	logData := &logger.Logging{}
	logData, err := getLogData(processData)
	if err != nil {
		logger.JaLog("JAICONRESULTNORMAL200013", logger.Logging{}, funcName, err.Error())
		return err
	}

	logger.JaLog("JAICONRESULTNORMAL400007", *logData, funcName, innerJobID, innerJobnetID, jobnetID)

	if err = event.CreateNextEvent(data, innerJobnetID, jobnetID, innerJobID); err != nil {
		logger.JaLog("JAICONRESULTNORMAL200017", *logData, funcName, data, err.Error())
		return err
	}

	return nil
}
