/*
** 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 (
	"bytes"
	"encoding/json"
	"fmt"
	"strconv"
	"time"

	"jobarranger2/src/libs/golibs/builder"
	"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"
)

func IsHostLocked(conn database.DBConnection, hostname string) (bool, error) {
	var flag bool = false
	var err error
	funcName := "IsHostLocked"

	logger.JaLog("JAFLOW400001", logger.Logging{}, funcName,
		fmt.Sprintf("hostname: %s", hostname))

	var conditions []string
	var count int
	if hostname == "" {
		return flag, fmt.Errorf("hostname is empty")
	}
	conditions = append(conditions, fmt.Sprintf("lock_host_name = '%s'", hostname))
	count, err = utils.GetRecordCountFromTableByColumn(conn, common.Ja2HostLockTable, conditions)
	if err != nil {
		logger.JaLog("JAFLOW200008", logger.Logging{}, funcName,
			common.Ja2HostLockTable, 0, err)
		return flag, fmt.Errorf("GetRecordCountFromTableByColumn() failed")
	}
	if count > 0 {
		flag = true
	}
	logger.JaLog("JAFLOW400002", logger.Logging{}, funcName,
		fmt.Sprintf("hostname: %s, host", hostname))

	return flag, err
}

func GetBeforeVariableDataForStartIcon(conn database.DBConnection, innerJobnetId uint64, innerJobId uint64) (map[string]any, error) {
	var runJobnetVariableTable common.RunJobnetVariableTable
	var variableMap map[string]any
	var err error
	var funcName = "GetVariableDataForStartIcon"

	logger.JaLog("JAFLOW400001", logger.Logging{}, funcName,
		fmt.Sprintf("inner_jobnet_id: %d, inner_job_id: %d",
			innerJobnetId, innerJobId))

	runJobnetVariableTable, err = utils.GetJobnetVariableDataByInnerJobnetId(conn, innerJobnetId)
	if err != nil {
		logger.JaLog("JAFLOW200000", logger.Logging{}, funcName,
			"GetJobnetVariableDataByInnerJobnetId", err)
		return variableMap, fmt.Errorf("GetRunJobData() failed")
	}
	if len(runJobnetVariableTable.BeforeVariable) == 0 {
		return variableMap, fmt.Errorf("variable for jobnet:%d is empty", innerJobnetId)
	}
	variableMap, err = utils.RawJsonToMapStringAny(runJobnetVariableTable.BeforeVariable)
	if err != nil {
		return variableMap, fmt.Errorf("RawJsonToMapStringAny() failed, err: [%w]", err)
	}

	logger.JaLog("JAFLOW400002", logger.Logging{}, funcName,
		fmt.Sprintf("inner_jobnet_id: %d, inner_job_id: %d",
			innerJobnetId, innerJobId))

	return variableMap, nil
}

// MergeAfterVariablesForStartJobs merges after_variable JSON from all startInnerJobIDs
// and returns a single combined JSON object as RawMessage ([]byte).
func MergeAfterVariablesForStartJobs(conn database.DBConnection, startInnerJobIDs []uint64, currentJobId uint64) (json.RawMessage, error) {
	merged := make(map[string]any)

	for _, startID := range startInnerJobIDs {

		runJobVarData, err := utils.GetLastRunJobVariableData(conn, startID)
		if err != nil {
			return nil, fmt.Errorf("GetLastRunJobVariableData() failed for innerJobID %d: %w", startID, err)
		}

		if len(runJobVarData.AfterVariable) == 0 {
			continue
		}

		var afterVar map[string]any
		// Use Decoder + UseNumber to prevent precision loss
		decoder := json.NewDecoder(bytes.NewReader(runJobVarData.AfterVariable))
		decoder.UseNumber()
		if err := decoder.Decode(&afterVar); err != nil {
			return nil, fmt.Errorf("json decode failed for innerJobID %d: %w", startID, err)
		}

		for k, v := range afterVar {
			merged[k] = v
		}
	}

	runJobVarData, err := utils.GetLastRunJobVariableData(conn, currentJobId)
	if err != nil {
		return nil, fmt.Errorf("GetLastRunJobVariableData() failed for innerJobID %d: %w", currentJobId, err)
	}

	var beforeVar map[string]any
	// Use Decoder + UseNumber to prevent precision loss
	decoder := json.NewDecoder(bytes.NewReader(runJobVarData.BeforeVariable))
	decoder.UseNumber()
	if err := decoder.Decode(&beforeVar); err != nil {
		return nil, fmt.Errorf("json decode failed for innerJobID %d: %w", currentJobId, err)
	}

	for k, v := range beforeVar {
		merged[k] = v
	}

	// Convert final merged map → JSON
	jsonBytes, err := json.Marshal(merged)
	if err != nil {
		return nil, fmt.Errorf("json.Marshal failed when creating merged result: %w", err)
	}

	return jsonBytes, nil
}

func PrepareVariableTableData(
	conn database.DBConnection,
	nextProcessData *common.IconExecutionProcessData,
	processData common.FlowProcessData,
) (common.ParameterizedQuery, error) {

	var runJobVariableData common.RunJobVariableTable
	var previousProcessData common.IconExecutionProcessData
	var variableMap map[string]any
	var conditions []string
	var count int
	var fullJobId string
	var err error
	var funcName = "PrepareVariableTableData"

	PQ := common.ParameterizedQuery{}

	innerJobId := nextProcessData.RunJobData.InnerJobID
	innerJobnetId := nextProcessData.RunJobData.InnerJobnetID

	logger.JaLog("JAFLOW400001", logger.Logging{}, funcName,
		fmt.Sprintf("inner_jobnet_id: %d, inner_job_id: %d",
			innerJobnetId, innerJobId))

	if nextProcessData.RunJobData.IconType == common.IconTypeStart {
		variableMap, err = GetBeforeVariableDataForStartIcon(conn, innerJobnetId, innerJobId)
		if err != nil {
			logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "GetBeforeVariableDataForStartIcon", err)
			return PQ, fmt.Errorf("GetVariableDataForStartIcon() failed")
		}
	} else {
		err = utils.Convert(processData.Data, &previousProcessData)
		if err != nil {
			return PQ, fmt.Errorf("Convert() failed, err: [%w]", err)
		} else if previousProcessData.RunJobVariableData.AfterVariable == nil {
			return PQ, fmt.Errorf("received after variable is empty")
		}

		if processData.IconType == common.IconTypeM ||
			(previousProcessData.RunJobVariableData.AfterVariable == nil || string(previousProcessData.RunJobVariableData.AfterVariable) == "null") {
			previousProcessData.RunJobVariableData.AfterVariable = previousProcessData.RunJobVariableData.BeforeVariable
		}
		variableMap, err = utils.RawJsonToMapStringAny(previousProcessData.RunJobVariableData.AfterVariable)
		if err != nil {
			return PQ, fmt.Errorf("RawJsonToMapStringAny() failed, err: [%w]", err)
		}
	}
	if variableMap == nil {
		variableMap = make(map[string]any)
	}

	variableMap["CURRENT_TIME"] = time.Now().Format("20060102150405")
	timezone, ok := variableMap["TIMEZONE"].(string)
	if !ok {
		return PQ, fmt.Errorf(" received variable TIMEZONE is not string")
	}

	location, err := time.LoadLocation(timezone)
	if err != nil {
		logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "time.LoadLocation", err)
		return PQ, fmt.Errorf("time.LoadLocation() failed")
	}
	variableMap["CURRENT_TIME_TZ"] = time.Now().In(location).Format("20060102150405")

	if nextProcessData.RunJobnetData.RunType == common.JA_JOBNET_RUN_TYPE_TEST {
		baseUnix :=
			time.Now().Unix() -
				int64(nextProcessData.RunJobnetData.CreatedDate) +
				int64(nextProcessData.RunJobnetData.TestRunTime)

		t := time.Unix(baseUnix, 0)

		timestamp := t.Format("20060102150405")

		variableMap["CURRENT_TIME"] = timestamp
		variableMap["CURRENT_TIME_TZ"] = timestamp
	}

	variableMap["JOB_ID"] = nextProcessData.RunJobData.JobID
	fullJobId, err = utils.GetFullJobId(conn, nextProcessData.RunJobData.InnerJobnetMainID,
		nextProcessData.RunJobData.InnerJobnetID, nextProcessData.RunJobData.JobID)
	if err != nil {
		logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "GetFullJobId", err)
		return PQ, fmt.Errorf("GetFullJobId() failed")
	}
	variableMap["JOB_ID_FULL"] = fullJobId
	variableMap["JOB_NAME"] = nextProcessData.RunJobData.JobName

	conditions = append(conditions, fmt.Sprintf("inner_job_id = %d", nextProcessData.RunJobData.InnerJobID))
	count, err = utils.GetRecordCountFromTableByColumn(conn, common.Ja2RunJobVariableTable, conditions)
	if err != nil {
		logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "GetRecordCountFromTableByColumn", err)
		return PQ, fmt.Errorf("GetRecordCountFromTableByColumn() failed")
	}

	runJobVariableData.SeqNo = count + 1
	runJobVariableData.InnerJobID = innerJobId
	runJobVariableData.InnerJobnetID = innerJobnetId
	runJobVariableData.BeforeVariable, err = json.Marshal(variableMap)
	if err != nil {
		return PQ, fmt.Errorf("Marshal() failed, err: [%w]", err)
	}

	PQ, err = builder.GetRunJobVariableInsertQuery(runJobVariableData)
	if err != nil {
		return PQ, fmt.Errorf("GetRunJobVariableInsertQuery() failed, err: [%w]", err)
	}
	nextProcessData.RunJobVariableData = runJobVariableData
	logger.JaLog("JAFLOW400002", logger.Logging{}, funcName,
		fmt.Sprintf("inner_jobnet_id: %d, inner_job_id: %d",
			innerJobnetId, innerJobId))

	return PQ, nil
}

func PrepareIconDetailData(
	conn database.DBConnection,
	runJobData common.RunJobTable,
	data *common.IconExecutionProcessData,
	processType common.FlowProcessType,
) error {
	var status common.StatusType
	var innerJobId uint64
	var updateDate, timezone string
	var err error

	funcName := "PrepareIconDetailData"

	switch runJobData.IconType {

	case common.IconTypeInfo:
		var iconData common.IconInfoData
		err = utils.MapToStruct(runJobData.Data, &iconData)
		if err != nil {
			return fmt.Errorf("MapToStruct() failed, err: %w", err)
		}
		switch iconData.InfoFlag {
		case 0:
			status, err = utils.GetStatusFromRunJobTableByPhrase(
				conn, iconData.GetJobId, runJobData.InnerJobnetID)
			if err != nil {
				logger.JaLog("JAFLOW200000", logger.Logging{}, funcName,
					"GetStatusFromRunJobTableByPhrase", err)
				return fmt.Errorf("GetStatusFromRunJobTableByPhrase(%s, %d) failed",
					iconData.GetJobId, runJobData.InnerJobnetID)
			}

			if status == -1 {
				logger.JaLog("JAFLOW200014", logger.Logging{}, funcName, iconData.GetJobId, runJobData.InnerJobID)
				data.JobResult.Message = fmt.Sprintf("[%s] In %s(), cannot find job id [%s]. "+
					"inner_job_id: %d",
					"JAFLOW200014", funcName, iconData.GetJobId, runJobData.InnerJobID)
				data.RunJobData.Status = common.StatusRunErr

				data.JobResult.Result = common.JA_JOBRESULT_FAIL
				data.JobResult.JobID = strconv.FormatUint(runJobData.InnerJobID, 10)
			}

		case 3:
			updateDate, timezone, err = utils.GetUpdateDateAndTzFromCalenderControlTableByCalendarIdAndValidFlag(
				conn, iconData.GetCalendarId, common.FlagOn)
			if err != nil {
				logger.JaLog("JAFLOW200000", logger.Logging{}, funcName,
					"GetUpdateDateAndTzFromCalenderControlTableByCalendarIdAndValidFlag", err)
				return fmt.Errorf("GetUpdateDateAndTzFromCalenderControlTableByCalendarIdAndValidFlag(%s, %d) failed",
					iconData.GetCalendarId, common.FlagOn)
			}
			if updateDate == "" {
				logger.JaLog("JAFLOW200012", logger.Logging{}, funcName, iconData.GetCalendarId)
				data.JobResult.Message = fmt.Sprintf("[%s] In %s(), calendar does not exist or is not valid. "+
					"get_calendar_id: %s",
					"JAFLOW200012", funcName, iconData.GetCalendarId)
				data.RunJobData.Status = common.StatusRunErr

				data.JobResult.Result = common.JA_JOBRESULT_FAIL
				data.JobResult.JobID = strconv.FormatUint(runJobData.InnerJobID, 10)
			} else {
				loc, err := time.LoadLocation(timezone)
				if err != nil {
					return fmt.Errorf("cannot get timezone data")
				}
				today := time.Now().In(loc).Format("20060102")

				status, err = utils.GetActiveStatusFromCalendarDetailTableByCalendarIdUpdateDateAndOperatingDate(
					conn, iconData.GetCalendarId, updateDate, today)
				if err != nil {
					logger.JaLog("JAFLOW200000", logger.Logging{}, funcName,
						"GetActiveStatusFromCalendarDetailTableByCalendarIdUpdateDateAndOperatingDate", err)
					return fmt.Errorf("GetActiveStatusFromCalendarDetailTableByCalendarIdUpdateDateAndOperatingDate(%s, %s, %s) failed",
						iconData.GetCalendarId, updateDate, today)
				}
			}
		}
		data.RunJobData.Data["status"] = status

	case common.IconTypeFCopy:
		data.RunJobData.Data["fcopy_timeout"] = server.Options.JaFcopyTimeout
		data.RunJobData.Data["serverid"] = server.Options.ServerID

	case common.IconTypeRel:
		var iconData common.IconRelData
		utils.MapToStruct(runJobData.Data, &iconData)
		innerJobId, err = utils.GetInnerJobIdFromRunJobTableByPhrase(
			conn, iconData.ReleaseJobId, runJobData.InnerJobnetID)
		if err != nil {
			logger.JaLog("JAFLOW200000", logger.Logging{}, funcName,
				"GetInnerJobIdFromRunJobTableByPhrase", err)
			return fmt.Errorf("GetInnerJobIdFromRunJobTableByPhrase() failed")
		}
		if innerJobId == 0 {
			data.JobResult.Message = fmt.Sprintf("[%s] In %s(), cannot get the release job id."+
				"release_job_id [%s] inner_job_id: %d ",
				"JAFLOW200000", funcName, iconData.ReleaseJobId, data.RunJobData.InnerJobID)
			data.RunJobData.Status = common.StatusRunErr

			data.JobResult.Result = common.JA_JOBRESULT_FAIL
			data.JobResult.JobID = strconv.FormatUint(data.RunJobData.InnerJobID, 10)
		} else {
			data.RunJobData.Data["release_inner_job_id"] = innerJobId
		}

	case common.IconTypeExtJob:
		//ParameterData      ParameterTable preparation
		if data.ParameterData, err = utils.GetParameterData(conn, "ZBXSND_SENDER"); err != nil {
			logger.JaLog("JAFLOW200008", logger.Logging{}, funcName,
				common.Ja2ParameterTable, data.RunJobData.InnerJobID, err)
			return fmt.Errorf("GetParameterData() failed")
		}
		data.RunJobData.Data["time_out"] = server.Options.JaExtjobWaitTime

	case common.IconTypeJobnet:

		var runJobnetData common.RunJobnetTable
		runJobnetData, err = utils.GetRunJobnetTableDataByInnerJobId(conn, runJobData.InnerJobID)
		if err != nil {
			logger.JaLog("JAFLOW200000", logger.Logging{}, funcName,
				"GetRunJobnetTableDataByInnerJobId", err)
			return fmt.Errorf("GetRunJobData() failed")
		}

		// db data passing
		data.JobnetRunData.Status = runJobnetData.Status
		data.JobnetRunData.JobnetID = runJobnetData.JobnetID
		data.JobnetRunData.JobnetName = runJobnetData.JobnetName
		data.JobnetRunData.UserName = runJobnetData.UserName
		data.JobnetRunData.InnerJobnetMainId = runJobnetData.InnerJobnetMainID
		data.JobnetRunData.InnerJobnetId = runJobnetData.InnerJobnetID

		// manual data passing
		data.JobnetRunData.RunType = common.JA_JOBNET_RUN_TYPE_JOBNETICON
		data.JobnetRunData.MainFlag = 1
		data.JobnetRunData.ScheduledTime = 0
		data.JobnetRunData.MultipleOptions = 0
		data.JobnetRunData.StartPendingFlag = 0
		data.JobnetRunData.JobnetTimeout = 0

	case common.IconTypeLess:

		sessionIdVal, ok := data.RunJobData.Data["session_id"]
		if !ok {
			return fmt.Errorf("session_id not found in RunJobData.Data")
		}

		var sessionIdStr string
		switch v := sessionIdVal.(type) {
		case float64:
			// convert float64 to integer string
			sessionIdStr = strconv.Itoa(int(v))
		case int:
			sessionIdStr = strconv.Itoa(v)
		case string:
			sessionIdStr = v
		default:
			return fmt.Errorf("unsupported type for session_id: %T", v)
		}

		data.SessionData, err = utils.GetSessionData(conn, sessionIdStr, runJobData.InnerJobnetMainID)
		if err != nil {
			logger.JaLog("JAFLOW200008", logger.Logging{}, funcName,
				common.Ja2SessionTable, innerJobId, err)
			return fmt.Errorf("GetSessionData() failed")
		}

		switch processType {
		case common.FlowProcessNormal, common.FlowProcessIconRerun,
			common.FlowProcessFailoverIconRerun, common.FlowParallelStartRun:
			sessionFlagRaw, ok := data.RunJobData.Data["session_flag"]
			if !ok {
				return fmt.Errorf("session_flag not found in RunJobData.Data")
			}
			var sessionFlag int
			switch v := sessionFlagRaw.(type) {
			case int:
				sessionFlag = v
			case float64:
				sessionFlag = int(v)
			default:
				return fmt.Errorf("session_flag must be int, got %T", sessionFlagRaw)
			}
			runerr := false
			switch sessionFlag {
			case common.JA_SES_OPERATION_FLAG_ONETIME, common.JA_SES_OPERATION_FLAG_CONNECT:
				if data.SessionData.InnerJobnetMainID != 0 {
					runerr = true
					data.JobResult.Message = fmt.Sprintf("session_id: %s already exist,"+
						" inner_job_id [%d] inner_jobnet_main_id [%d]", sessionIdStr,
						data.RunJobData.InnerJobID, data.RunJobData.InnerJobnetMainID)
				}
			case common.JA_SES_OPERATION_FLAG_CONTINUE, common.JA_SES_OPERATION_FLAG_CLOSE:
				if data.SessionData.InnerJobnetMainID == 0 {
					runerr = true
					data.JobResult.Message = fmt.Sprintf("session_id: %s does not exist,"+
						" inner_job_id [%d] inner_jobnet_main_id [%d]", sessionIdStr,
						data.RunJobData.InnerJobID, data.RunJobData.InnerJobnetMainID)
				}
			}
			if runerr {
				data.JobResult.Result = common.JA_JOBRESULT_FAIL
				data.RunJobData.Status = common.StatusRunErr
				data.JobResult.JobID = strconv.FormatUint(data.RunJobData.InnerJobID, 10)
			}
		}

	case common.IconTypeLink:
		data.RunJobData.Data["execution_user_name"] = data.RunJobnetData.ExecutionUserName
		data.RunJobData.Data["test_flag"] = data.RunJobData.TestFlag
	}
	return nil
}

func PrepareDataForNextProcess(
	conn database.DBConnection,
	nextProcessData *common.IconExecutionProcessData,
	processData common.FlowProcessData,
	processType *common.FlowProcessType,
) (common.ParameterizedQuery, error) {
	var hostname string
	var hostFlag int
	var flag bool = false
	var err error
	var funcName = "PrepareDataForNextProcess"

	PQ := common.ParameterizedQuery{}

	innerJobnetId := nextProcessData.RunJobData.InnerJobnetID
	innerJobId := nextProcessData.RunJobData.InnerJobID

	logger.JaLog("JAFLOW400001", logger.Logging{}, funcName,
		fmt.Sprintf("inner_jobnet_id: %d, inner_job_id: %d, process type: %d",
			innerJobnetId, innerJobId, processType))

	//RunJobData         RunJobTable preparation
	nextProcessData.RunJobData, err = utils.GetRunJobData(conn, innerJobId)
	if err != nil {
		logger.JaLog("JAFLOW200008", logger.Logging{}, funcName,
			common.Ja2RunJobTable, innerJobId, err)
		return PQ, fmt.Errorf("GetRunJobData() failed")
	}
	if nextProcessData.RunJobData.InnerJobnetID == 0 {
		err = fmt.Errorf("RunJobTable result is considered empty. RunJobTable data: [%v]",
			nextProcessData.RunJobData)
		logger.JaLog("JAFLOW200008", logger.Logging{}, funcName,
			common.Ja2RunJobTable, innerJobId, err)
		return PQ, fmt.Errorf("GetRunJobData() failed")
	}

	// This condition is required to reset the status of a run-error icon in a loop-configured jobnet.
	if *processType < common.FlowProcessSubJobnet && nextProcessData.RunJobData.Status == common.StatusRunErr {
		nextProcessData.RunJobData.Status = common.StatusReady
	}

	innerJobnetId = nextProcessData.RunJobData.InnerJobnetID
	nextProcessData.RunJobnetData, err = utils.GetRunJobnetTableDataByInnerJobnetId(
		conn, nextProcessData.RunJobData.InnerJobnetID)
	if err != nil {
		logger.JaLog("JAFLOW200008", logger.Logging{}, funcName,
			common.Ja2RunJobnetTable, innerJobId, err)
		return PQ, fmt.Errorf("GetRunJobnetTableDataByInnerJobnetId() failed")
	}
	switch *processType {
	case common.FlowProcessNormal, common.FlowParallelStartRun:
		PQ, err = PrepareVariableTableData(conn, nextProcessData, processData)
		if err != nil {
			logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "PrepareVariableTableData", err)
			return PQ, fmt.Errorf("PrepareVariableTableData() failed")
		}

	default:
		nextProcessData.RunJobVariableData, err = utils.GetLastRunJobVariableData(conn, innerJobId)
		if err != nil {
			logger.JaLog("JAFLOW200008", logger.Logging{}, funcName,
				common.Ja2RunJobVariableTable, innerJobId, err)
			return PQ, fmt.Errorf("GetLastRunJobVariableData() failed")
		}
		switch *processType {
		case common.FlowProcessUnhold, common.FlowProcessAbort,
			common.FlowProcessIconRerun, common.FlowProcessFailoverIconRerun:
			nextProcessData.RunJobVariableData.AfterVariable = nextProcessData.RunJobVariableData.BeforeVariable
			processData.Data = common.IconExecutionProcessData{
				RunJobVariableData: nextProcessData.RunJobVariableData,
			}
		}

	}

	if nextProcessData.RunJobData.MethodFlag != common.MethodSkip {
		switch *processType {
		case common.FlowProcessNormal, common.FlowProcessUnhold,
			common.FlowProcessAbort, common.FlowProcessIconRerun,
			common.FlowProcessFailoverIconRerun, common.FlowParallelStartRun:

			switch nextProcessData.RunJobData.IconType {
			case common.IconTypeJob, common.IconTypeFWait,
				common.IconTypeReboot, common.IconTypeLess:
				hostFlag = int(nextProcessData.RunJobData.Data["host_flag"].(float64))
				hostname, err = GetHostName(hostFlag, nextProcessData.RunJobData.Data["host_name"].(string), processData)
				if err != nil {
					logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "GetHostName", err)
					return PQ, fmt.Errorf("GetHostName() failed")
				}
				if hostname == "" {
					break
				}
				flag, err = IsHostLocked(conn, hostname)
				if err != nil {
					logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "IsHostLocked", err)
					return PQ, fmt.Errorf("IsHostLocked() failed")
				}
				nextProcessData.RunJobData.Data["host_name"] = hostname
			case common.IconTypeFCopy:
				hostFlag = int(nextProcessData.RunJobData.Data["from_host_flag"].(float64))
				hostname, err = GetHostName(hostFlag, nextProcessData.RunJobData.Data["from_host_name"].(string), processData)
				if err != nil {
					logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "GetHostName", err)
					return PQ, fmt.Errorf("GetHostName() failed")
				}
				if hostname == "" {
					break
				}
				flag, err = IsHostLocked(conn, hostname)
				if err != nil {
					logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "IsHostLocked", err)
					return PQ, fmt.Errorf("IsHostLocked() failed")
				}
				if flag {
					//from host is locked
					break
				}
				nextProcessData.RunJobData.Data["from_host_name"] = hostname

				hostFlag = int(nextProcessData.RunJobData.Data["to_host_flag"].(float64))
				hostname, err = GetHostName(hostFlag, nextProcessData.RunJobData.Data["to_host_name"].(string), processData)
				if err != nil {
					logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "GetHostName", err)
					return PQ, fmt.Errorf("GetHostName() failed")
				}
				if hostname == "" {
					break
				}
				flag, err = IsHostLocked(conn, hostname)
				if err != nil {
					logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "IsHostLocked", err)
					return PQ, fmt.Errorf("IsHostLocked() failed")
				}
				nextProcessData.RunJobData.Data["to_host_name"] = hostname
			}
			if flag && *processType != common.FlowProcessAbort {
				nextProcessData.RunJobData.Data["host_name"] = hostname
				*processType = common.FlowProcessHostLockWait
				logger.JaLog("JAFLOW300005", logger.Logging{}, funcName,
					hostname, nextProcessData.RunJobData.InnerJobID)
				return PQ, nil
			}
		}
	}

	if flag, err = PrepareHostInfo(conn, nextProcessData); err != nil {
		logger.JaLog("JAFLOW200008", logger.Logging{}, funcName,
			"host", innerJobId, err)
		return PQ, fmt.Errorf("FetchHostInfo() failed")
	}

	if *processType != common.FlowProcessAbort &&
		(*processType != common.FlowProcessSkip && nextProcessData.RunJobData.MethodFlag != common.MethodSkip) {
		switch nextProcessData.RunJobData.IconType {
		case common.IconTypeJob, common.IconTypeFWait, common.IconTypeFCopy,
			common.IconTypeReboot, common.IconTypeLess:

			if nextProcessData.RunJobData.IconType == common.IconTypeLess {
				sessionFlagRaw, ok := nextProcessData.RunJobData.Data["session_flag"]
				if !ok {
					return PQ, fmt.Errorf("session_flag not found in RunJobData.Data")
				}
				var sessionFlag int
				switch v := sessionFlagRaw.(type) {
				case int:
					sessionFlag = v
				case float64:
					sessionFlag = int(v)
				default:
					return PQ, fmt.Errorf("session_flag must be int, got %T", sessionFlagRaw)
				}
				if sessionFlag != common.JA_SES_OPERATION_FLAG_ONETIME && sessionFlag != common.JA_SES_OPERATION_FLAG_CONNECT {
					break
				}
			}

			if (!flag && nextProcessData.RunJobData.ForceFlag != 1) || hostname == "" {
				//host is not active/invalid
				if hostname == "" {
					nextProcessData.JobResult.Message = fmt.Sprintf("hostname considered is empty"+
						" inner_job_id [%d] job_id [%s]",
						nextProcessData.RunJobData.InnerJobID, nextProcessData.RunJobData.JobID)
				}
				nextProcessData.RunJobData.Status = common.StatusRunErr

				nextProcessData.JobResult.Result = common.JA_JOBRESULT_FAIL
				if nextProcessData.RunJobData.IconType == common.IconTypeFCopy {
					nextProcessData.JobResult.Result = common.JA_RESPONSE_FAIL
					nextProcessData.JobResult.Type = common.AgentJobTypePutFile
				}
				nextProcessData.JobResult.JobID = strconv.FormatUint(nextProcessData.RunJobData.InnerJobID, 10)
				return PQ, nil
			}
		}
	}

	//RunValueJobConData []RunValueJobConTable preparation
	nextProcessData.RunValueJobConData, err = utils.GetRunValueJobConData(conn, innerJobId)
	if err != nil {
		logger.JaLog("JAFLOW200008", logger.Logging{}, funcName,
			common.Ja2RunValueJobconTable, innerJobId, err)
		return PQ, fmt.Errorf("GetRunValueJobConData() failed")
	}

	//RunValueJobData    []RunValueJobTable preparation
	nextProcessData.RunValueJobData, err = utils.GetRunValueJobData(conn, innerJobId)
	if err != nil {
		logger.JaLog("JAFLOW200008", logger.Logging{}, funcName,
			common.Ja2RunValueJobTable, innerJobId, err)
		return PQ, fmt.Errorf("GetRunValueJobData() failed")
	}
	// skip && wait ->
	if *processType != common.FlowProcessSkip &&
		nextProcessData.RunJobData.MethodFlag != common.MethodSkip &&
		(nextProcessData.RunJobData.MethodFlag != common.MethodWait || *processType == common.FlowProcessUnhold) {

		err = PrepareIconDetailData(conn, nextProcessData.RunJobData, nextProcessData, *processType)
		if err != nil {
			logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "PrepareIconDetailData", err)
			return PQ, fmt.Errorf("PrepareIconDetailData() failed")
		}
	}

	switch nextProcessData.RunJobData.IconType {
	case common.IconTypeJob, common.IconTypeFWait,
		common.IconTypeFCopy, common.IconTypeReboot:
		nextProcessData.RunJobData.Data["serverid"] = server.Options.ServerID
		nextProcessData.RunJobData.Data["jaz1_support_flag"] = server.Options.Jaz1Support

	case common.IconTypeExtJob:
		// fetch and populate ExtIcon-specific data
		nextProcessData.RunTimeoutData, err = utils.GetRunTimeoutData(conn, nextProcessData.RunJobData.InnerJobID)
		if err != nil {
			logger.JaLog("JAFLOW200008", logger.Logging{}, funcName,
				common.Ja2RunTimeOutTable, nextProcessData.RunJobData.InnerJobID, err)
			return PQ, fmt.Errorf("GetRunTimeoutData() failed")
		}
	}
	logger.JaLog("JAFLOW400002", logger.Logging{}, funcName,
		fmt.Sprintf("inner_jobnet_id: %d, inner_job_id: %d, process type: %d",
			innerJobnetId, innerJobId, processType))

	return PQ, nil
}

func AssignRunCount(
	processData common.FlowProcessData,
	nextProcessData *common.IconExecutionProcessData,
) error {
	funcName := "RunCountIdentifier"
	var err error
	var previousProcessData common.IconExecutionProcessData

	logger.JaLog("JAFLOW400001", logger.Logging{}, funcName,
		fmt.Sprintf("inner_jobnet_id: %d, inner_job_id: %d",
			nextProcessData.RunJobData.InnerJobnetID, nextProcessData.RunJobData.InnerJobID))

	switch nextProcessData.RunJobData.IconType {
	case common.IconTypeStart:
		nextProcessData.RunJobData.RunCount += 1
	case common.IconTypeL:
		nextProcessData.RunJobData.RunCount += 1
	default:
		err = utils.Convert(processData.Data, &previousProcessData)
		if err != nil {
			return fmt.Errorf("Convert() failed, err: [%w]", err)
		}
		nextProcessData.RunJobData.RunCount = previousProcessData.RunJobData.RunCount
	}
	return err
}

func AddHostLockCheckSQLCondition(
	nextEventData *common.EventData,
	runJobData common.RunJobTable,
) {
	switch runJobData.IconType {
	case common.IconTypeJob, common.IconTypeFWait, common.IconTypeReboot, common.IconTypeLess:
		sqlCond := BuildSQlConditionForHostLockCheck(runJobData.Data["host_name"].(string))
		nextEventData.SQLConditions = append(nextEventData.SQLConditions, sqlCond)
	case common.IconTypeFCopy:
		sqlCond := BuildSQlConditionForHostLockCheck(runJobData.Data["from_host_name"].(string), runJobData.Data["to_host_name"].(string))
		nextEventData.SQLConditions = append(nextEventData.SQLConditions, sqlCond)
	default:
		break
	}
}

func PrepareJobLogData(nextProcessData common.IconExecutionProcessData, logData *logger.Logging) error {
	*logData = logger.Logging{
		InnerJobnetMainID: nextProcessData.RunJobnetData.InnerJobnetMainID,
		InnerJobnetID:     nextProcessData.RunJobnetData.InnerJobnetID,
		UpdateDate:        nextProcessData.RunJobnetData.UpdateDate,
		JobnetStatus:      nextProcessData.RunJobnetData.Status,
		RunType:           nextProcessData.RunJobnetData.RunType,
		PublicFlag:        nextProcessData.RunJobnetData.PublicFlag,
		JobnetID:          nextProcessData.RunJobnetData.JobnetID,
		JobnetName:        nextProcessData.RunJobnetData.JobnetName,
		UserName:          nextProcessData.RunJobnetData.UserName,

		InnerJobID: nextProcessData.RunJobData.InnerJobID,
		JobType:    nextProcessData.RunJobData.IconType,
		MethodFlag: nextProcessData.RunJobData.MethodFlag,
		JobStatus:  nextProcessData.RunJobData.Status,
		JobID:      nextProcessData.RunJobData.JobID,
		JobName:    nextProcessData.RunJobData.JobName,
	}
	return nil
}
