/*
** 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"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

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

func getRunSqlCondition(innerJobId uint64) []common.SQLCondition {
	sqlCond := builder.NewSQLCondition(
				fmt.Sprintf("select status from ja_2_run_job_table where inner_job_id = %d;", innerJobId),
			).
				AddCondition(
					builder.NewCondition(common.ActionIgnore).
						Field("status", common.OpNe, common.StatusReady).
						Build(),
				).
				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.ActionIgnore).
						Field("status", common.OpEq, common.StatusEndErr).
						Build(),
				).
				DefaultAction(common.ActionExec). // move to error folder
				Build()

	return []common.SQLCondition{sqlCond}
}

func getCommonQuries(processData common.IconExecutionProcessData, clientPid int, currentTime time.Time) []common.ParameterizedQuery {
	var commonQuries []common.ParameterizedQuery

	commonQuries = append(commonQuries, common.GetPQ(
		"select status from ja_2_run_job_table where inner_job_id = ? for update",
		processData.RunJobData.InnerJobID),
	)
	commonQuries = append(commonQuries, common.GetPQ(
		"update ja_2_run_job_table set status = ?, start_time = ?, end_time = 0, client_pid = ?  where inner_job_id = ?",
		2, currentTime.UnixNano(), clientPid, processData.RunJobData.InnerJobID),
	)

	// jobnet summary information update
	commonQuries = append(commonQuries, common.GetPQ(
		"select status from ja_2_run_jobnet_summary_table where inner_jobnet_id = ? for update",
		processData.RunJobData.InnerJobnetMainID),
	)
	commonQuries = append(commonQuries, common.GetPQ(
		`update ja_2_run_jobnet_summary_table
			set running_job_id = ?, running_job_name = ?
			where inner_jobnet_id  = ?`,
		processData.RunJobnetData.JobnetID + "/"+ processData.RunJobData.JobID, processData.RunJobData.JobName, 
		processData.RunJobData.InnerJobnetMainID),
	)

	// jobnet management information update
	commonQuries = append(commonQuries, common.GetPQ(
		`select status from ja_2_run_jobnet_table
			where inner_jobnet_id = ? for update`,
		processData.RunJobData.InnerJobnetMainID),
	)
	commonQuries = append(commonQuries, common.GetPQ(
		`update ja_2_run_jobnet_table
			set running_job_id = ?, running_job_name = ?
			where inner_jobnet_id  = ?`,
		processData.RunJobData.JobID, processData.RunJobData.JobName, processData.RunJobData.InnerJobnetMainID),
	)

	return commonQuries
}

// icons for the following flow, exec_manager -> run -> dbsyncer -> result_manager
func oneWayIconRun(processData common.IconExecutionProcessData, clientPid int) error {
	processData.JobResult = common.JobResultData{
		JobRunRequestData: common.JobRunRequestData{
			JobID: strconv.FormatUint(processData.RunJobData.InnerJobID, 10),
		},
		Result: common.JA_JOBRESULT_SUCCEED,
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventOneWayIconRun,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.DBSyncerManagerProcess,
			Data: processData,
		},
		ParameterizedQueries: getCommonQuries(processData, clientPid, time.Now()),
		SQLConditions: getRunSqlCondition(processData.RunJobData.InnerJobID),
	}

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

func mIconRun(processData common.IconExecutionProcessData, clientPid int) error {
	processData.JobResult = common.JobResultData{
		JobRunRequestData: common.JobRunRequestData{
			JobID: strconv.FormatUint(processData.RunJobData.InnerJobID, 10),
		},
		Result: common.JA_JOBRESULT_SUCCEED,
	}

	flowProcessData := common.FlowProcessData{
		InnerJobnetId: processData.RunJobData.InnerJobnetID,
		JobnetId:      processData.RunJobnetData.JobnetID,
		InnerJobId:    processData.RunJobData.InnerJobID,
		IconType:      processData.RunJobData.IconType,
		Data: 		   processData,
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventMIconRun,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.DBSyncerManagerProcess,
			Data: flowProcessData,
		},
		ParameterizedQueries: getCommonQuries(processData, clientPid, time.Now()),
		SQLConditions: getRunSqlCondition(processData.RunJobData.InnerJobID),
	}

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

func ifEndIconRun(processData common.IconExecutionProcessData, clientPid int) error {
	processData.RunJobData.Data = map[string]interface{}{
		"end_count_plus_flag": false,
	}

	processData.JobResult = common.JobResultData{
		JobRunRequestData: common.JobRunRequestData{
			JobID: strconv.FormatUint(processData.RunJobData.InnerJobID, 10),
		},
		Result: common.JA_JOBRESULT_SUCCEED,
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventOneWayIconRun,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.DBSyncerManagerProcess,
			Data: processData,
		},
		ParameterizedQueries: getCommonQuries(processData, clientPid, time.Now()),
		SQLConditions: getRunSqlCondition(processData.RunJobData.InnerJobID),
	}

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

func wIconRun(processData common.IconExecutionProcessData, clientPid int) error {
	processData.RunJobData.Data = map[string]interface{}{
		"end_count_plus_flag": false,
	}

	processData.JobResult = common.JobResultData{
		JobRunRequestData: common.JobRunRequestData{
			JobID: strconv.FormatUint(processData.RunJobData.InnerJobID, 10),
		},
		Result: common.JA_JOBRESULT_SUCCEED,
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventOneWayIconRun,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.DBSyncerManagerProcess,
			Data: processData,
		},
		ParameterizedQueries: getCommonQuries(processData, clientPid, time.Now()),
		SQLConditions: getRunSqlCondition(processData.RunJobData.InnerJobID),
	}

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

func commonIconRun(processData common.IconExecutionProcessData, clientPid int) error {
	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventTwoWayIconRun,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.DBSyncerManagerProcess,
			Data: processData,
		},
		ParameterizedQueries: getCommonQuries(processData, clientPid, time.Now()),
		SQLConditions: getRunSqlCondition(processData.RunJobData.InnerJobID),
	}

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

func timerIconRun(runData common.IconRunData, processData common.IconExecutionProcessData, clientPid int) error {
	var quries []common.ParameterizedQuery
	var extData common.IconExtData

	// read client data file
	data, err := os.ReadFile(
		filepath.Join(clientDataPath, runData.TransactionFileId+common.ClientExts[0]))
	if err != nil {
		return fmt.Errorf("os.ReadFile() failed. common.ClientExts[0]: %s, error: %s", common.ClientExts[0], err.Error())
	}

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

	quries = append(quries, common.GetPQ(`
		insert into ja_2_run_timeout_table
		(inner_jobnet_id, inner_job_id, timeout, jobnet_id, execution_user_name, command_id, icon_type, job_id)
		values (?, ?, ?, ?, ?, ?, ?, ?)`,
		processData.RunJobData.InnerJobnetID, processData.RunJobData.InnerJobID,
		extData.WaitTime, processData.RunJobnetData.JobnetID, processData.RunJobnetData.ExecutionUserName,
		extData.CommandId, common.IconTypeExtJob, processData.RunJobData.JobID,
	))

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventTwoWayIconRun,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.DBSyncerManagerProcess,
			Data: processData,
		},
		ParameterizedQueries: quries,
		SQLConditions: getRunSqlCondition(processData.RunJobData.InnerJobID),
	}

	// add common quries
	eventData.ParameterizedQueries = append(getCommonQuries(processData, clientPid, time.Now()), eventData.ParameterizedQueries...)

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

func extJobIconRun(runData common.IconRunData, processData common.IconExecutionProcessData, clientPid int) error {
	var extData common.IconExtData

	iconDataByte, err := json.Marshal(processData.RunJobData.Data)
	if err != nil {
		return fmt.Errorf("Marshal() failed, err: %s", err.Error())
	}

	err = json.Unmarshal(iconDataByte, &extData)
	if err != nil {
		return fmt.Errorf("Unmarshal() failed, err: %s", err.Error())
	}

	switch extData.CommandId {
	case common.JA_CMD_TIME, common.JA_CMD_SLEEP:
		err = timerIconRun(runData, processData, clientPid)
	case common.JA_CMD_WEEK, common.ZABBIX_SENDER:
		err = commonIconRun(processData, clientPid)
	default:
		err = fmt.Errorf("unknown commandID: %s", extData.CommandId)
	}

	if err != nil {
		err = fmt.Errorf("IconRun() failed, CommandId: %s, err: %s", extData.CommandId, err.Error())
	}

	return err
}

func rebootIconRun(processData common.IconExecutionProcessData, clientPid int) error {
	var iconData common.IconRebootData

	iconDataByte, err := json.Marshal(processData.RunJobData.Data)
	if err != nil {
		return fmt.Errorf("json.Marshal(processData.RunJobData.Data) failed. error: %s", err.Error())
	}

	err = json.Unmarshal(iconDataByte, &iconData)
	if err != nil {
		return fmt.Errorf("json.Unmarshal common.IconJobData failed. error: %s", err.Error())
	}

	// current time to use in quries
	currentTime := time.Now()

	// get common quries
	quries := getCommonQuries(processData, clientPid, currentTime)

	// set timeout
	if iconData.TimeOut > 0 {
		timeout := currentTime.Add(time.Duration(iconData.TimeOut) * time.Minute).Unix()

		quries = append(quries, common.GetPQ(`
			insert into ja_2_run_timeout_table
			(inner_jobnet_id, inner_job_id, timeout, jobnet_id, execution_user_name, icon_type, timeout_run_type, job_id)
			values (?, ?, ?, ?, ?, ?, ?, ?)`,
			processData.RunJobData.InnerJobnetID, processData.RunJobData.InnerJobID,
			timeout, processData.RunJobnetData.JobnetID, processData.RunJobnetData.ExecutionUserName,
			common.IconTypeReboot, 0, processData.RunJobData.JobID,
		))
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventTwoWayIconRun,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.DBSyncerManagerProcess,
			Data: processData,
		},
		ParameterizedQueries: quries,
		SQLConditions: getRunSqlCondition(processData.RunJobData.InnerJobID),
	}

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

func jobIconRun(processData common.IconExecutionProcessData, clientPid int) error {
	var iconData common.IconJobData

	iconDataByte, err := json.Marshal(processData.RunJobData.Data)
	if err != nil {
		return fmt.Errorf("json.Marshal(processData.RunJobData.Data) failed. error: %s", err.Error())
	}

	err = json.Unmarshal(iconDataByte, &iconData)
	if err != nil {
		return fmt.Errorf("json.Unmarshal common.IconJobData failed. error: %s", err.Error())
	}

	// current time to use in quries
	currentTime := time.Now()

	// get common quries
	quries := getCommonQuries(processData, clientPid, currentTime)

	// set timeout
	if iconData.Timeout > 0 {
		timeout := currentTime.Add(time.Duration(iconData.Timeout) * time.Minute).Unix()

		quries = append(quries, common.GetPQ(`
			insert into ja_2_run_timeout_table
			(inner_jobnet_id, inner_job_id, timeout, jobnet_id, execution_user_name, icon_type, timeout_run_type, job_id)
			values (?, ?, ?, ?, ?, ?, ?, ?)`,
			processData.RunJobData.InnerJobnetID, processData.RunJobData.InnerJobID,
			timeout, processData.RunJobnetData.JobnetID,
			processData.RunJobnetData.ExecutionUserName, common.IconTypeJob, iconData.TimeoutRunType,
			processData.RunJobData.JobID,
		))
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventTwoWayIconRun,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.DBSyncerManagerProcess,
			Data: processData,
		},
		ParameterizedQueries: quries,
		SQLConditions: getRunSqlCondition(processData.RunJobData.InnerJobID),
	}

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

func agentlessIconRun(runData common.IconRunData, processData common.IconExecutionProcessData, clientPid int) error {
	var agentlessData common.IconLessData
	var agentlessQuries []common.ParameterizedQuery

	iconDataByte, err := json.Marshal(processData.RunJobData.Data)
	if err != nil {
		return fmt.Errorf("json.Marshal(processData.RunJobData.Data) failed. error: %s", err.Error())
	}

	err = json.Unmarshal(iconDataByte, &agentlessData)
	if err != nil {
		return fmt.Errorf("json.Unmarshal(iconDataByte, &agentlessData) failed. error: %s", err.Error())
	}

	// current time to use in quries
	currentTime := time.Now()

	// set timeout
	if agentlessData.Timeout > 0 {
		timeout := currentTime.Add(time.Duration(agentlessData.Timeout) * time.Minute).Unix()

		agentlessQuries = append(agentlessQuries, common.GetPQ(`
			insert into ja_2_run_timeout_table
			(inner_jobnet_id, inner_job_id, timeout, jobnet_id, execution_user_name, icon_type, timeout_run_type, job_id)
			values (?, ?, ?, ?, ?, ?, ?, ?)`,
			processData.RunJobData.InnerJobnetID, processData.RunJobData.InnerJobID,
			timeout, processData.RunJobnetData.JobnetID,
			processData.RunJobnetData.ExecutionUserName, common.IconTypeLess, 0,
			processData.RunJobData.JobID,
		))
	}

	switch agentlessData.SessionFlag {
	case common.JA_SES_OPERATION_FLAG_ONETIME, common.JA_SES_OPERATION_FLAG_CONNECT:
		var agentlessData common.IconLessData

		// agentlessData
		data, err := os.ReadFile(
			filepath.Join(clientDataPath, runData.TransactionFileId+common.ClientExts[0]))
		if err != nil {
			return fmt.Errorf("os.ReadFile() failed. common.ClientExts[0]: %s, error: %s", common.ClientExts[0], err.Error())
		}

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

		// prepare quries
		agentlessQuries = append(agentlessQuries, common.GetPQ(
			"update ja_2_run_job_table set client_pid = ? where inner_job_id = ?",
			clientPid, processData.RunJobData.InnerJobID))

		agentlessQuries = append(agentlessQuries, common.GetPQ(`
							insert into ja_2_session_table
							(session_id, inner_jobnet_main_id, inner_job_id, operation_flag, status, force_stop, ssh_client_socket, pid)
							values (?, ?, ?, ?, ?, ?, ?, ?)`,
			agentlessData.SessionID, processData.RunJobData.InnerJobnetMainID, processData.RunJobData.InnerJobID,
			agentlessData.SessionFlag, common.JA_SES_STATUS_BEGIN, common.JA_SES_FORCE_STOP_OFF,
			agentlessData.SshClientUdsPath, agentlessData.SshClientPid))

	case common.JA_SES_OPERATION_FLAG_CONTINUE, common.JA_SES_OPERATION_FLAG_CLOSE:
		/* session management data update */
		agentlessQuries = append(agentlessQuries, common.GetPQ(`
							update ja_2_session_table set
							inner_job_id = ?, operation_flag = ?, status = ?, force_stop = ?
							where session_id = ? and inner_jobnet_main_id = ?`,
			processData.RunJobData.InnerJobID, agentlessData.SessionFlag, common.JA_SES_STATUS_BEGIN, common.JA_SES_FORCE_STOP_OFF,
			agentlessData.SessionID, processData.RunJobData.InnerJobnetMainID))
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventTwoWayIconRun,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.DBSyncerManagerProcess,
			Data: processData,
		},
		ParameterizedQueries: agentlessQuries,
		SQLConditions: getRunSqlCondition(processData.RunJobData.InnerJobID),
	}

	// add common quries
	eventData.ParameterizedQueries = append(getCommonQuries(processData, clientPid, currentTime), eventData.ParameterizedQueries...)

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

func jobnetIconRun(processData common.IconExecutionProcessData, clientPid int) error {
	processData.JobnetRunData.JobnetIconBeforeVariable = processData.RunJobVariableData.BeforeVariable

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventJobnetIconRun,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.DBSyncerManagerProcess,
			Data: processData.JobnetRunData,
		},

		ParameterizedQueries: getCommonQuries(processData, clientPid, time.Now()),
		SQLConditions: getRunSqlCondition(processData.RunJobData.InnerJobID),
	}

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

func releaseIconRun(processData common.IconExecutionProcessData, clientPid int) error {
	var releaseIconQuries []common.ParameterizedQuery

	releaseInnerJobId, ok := processData.RunJobData.Data["release_inner_job_id"].(float64)
	if !ok {
		return fmt.Errorf("could not get releaseInnerJobId. Data[release_inner_job_id]: %v", processData.RunJobData.Data["release_inner_job_id"])
	}

	releaseIconQuries = append(releaseIconQuries, common.GetPQ(
		"select status from ja_2_run_job_table where inner_job_id = ?  for update",
		uint64(releaseInnerJobId),
	))
	releaseIconQuries = append(releaseIconQuries, common.GetPQ(
		"update ja_2_run_job_table set method_flag = ? where inner_job_id = ? and status = ? and method_flag = ?",
		common.MethodNormal, uint64(releaseInnerJobId), common.StatusBegin, common.MethodWait,
	))
	releaseIconQuries = append(releaseIconQuries, common.GetPQ(`
		insert into ja_2_run_action_table
		(inner_jobnet_id, inner_job_id, action_flag, scheduled_time, update_date)
		select j.inner_jobnet_id, j.inner_job_id, 0, 0, ?
		from ja_2_run_job_table j
		where j.inner_job_id = ? and status = ?;`,
		time.Now().Unix(), uint64(releaseInnerJobId), common.StatusReady,
	))

	processData.JobResult = common.JobResultData{
		JobRunRequestData: common.JobRunRequestData{
			JobID: strconv.FormatUint(processData.RunJobData.InnerJobID, 10),
		},
		Result: common.JA_JOBRESULT_SUCCEED,
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventOneWayIconRun,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.DBSyncerManagerProcess,
			Data: processData,
		},
		ParameterizedQueries: releaseIconQuries,
		SQLConditions: getRunSqlCondition(processData.RunJobData.InnerJobID),
	}

	eventData.ParameterizedQueries = append(getCommonQuries(processData, clientPid, time.Now()), eventData.ParameterizedQueries...)

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

func taskIconRun(runData common.IconRunData, processData common.IconExecutionProcessData, clientPid int) error {
	// icon data
	var iconData common.IconTaskData
	data, err := os.ReadFile(
		filepath.Join(clientDataPath, runData.TransactionFileId+common.ClientExts[0]))
	if err != nil {
		return fmt.Errorf("os.ReadFile() failed. common.ClientExts[0]: %s, error: %s", common.ClientExts[0], err.Error())
	}

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

	var runType int
	if processData.RunJobData.TestFlag == common.FlagOn {
		runType = common.JA_JOBNET_RUN_TYPE_TEST
	} else {
		runType = common.JA_JOBNET_RUN_TYPE_IMMEDIATE
	}

	var quries []common.ParameterizedQuery

	quries = append(quries, common.GetPQ(
		`select nextid from ja_2_index_table where count_id = ? for update`,
		common.JA_RUN_ID_JOBNET_EX,
	))

	quries = append(quries, common.GetPQ(
		`insert into ja_2_run_jobnet_table
		(inner_jobnet_id, inner_jobnet_main_id, inner_job_id,
		update_date, run_type, main_flag, status,
		public_flag, jobnet_id, user_name, jobnet_name, memo, execution_user_name)
		(select (select nextid from ja_2_index_table where count_id = ?), (select nextid from ja_2_index_table where count_id = ?),
		0, update_date, ?, 0, ?, public_flag, jobnet_id, user_name, jobnet_name, memo, ?
		from ja_2_jobnet_control_table where valid_flag = 1 and jobnet_id = ?)`,
		common.JA_RUN_ID_JOBNET_EX, common.JA_RUN_ID_JOBNET_EX, runType, 0, processData.RunJobnetData.ExecutionUserName, iconData.SubmitJobnetId,
	))

	// nextid++
	quries = append(quries, common.GetPQ(`
		UPDATE ja_2_index_table
		SET
		nextid = CASE
					WHEN nextid > 1699999999999999999 THEN 1600000000000000000
             		ELSE nextid + 1
           		END
		WHERE count_id = ?;`,
		common.JA_RUN_ID_JOBNET_EX,
	))

	processData.JobResult = common.JobResultData{
		JobRunRequestData: common.JobRunRequestData{
			JobID: strconv.FormatUint(processData.RunJobData.InnerJobID, 10),
		},
		Result: common.JA_JOBRESULT_SUCCEED,
	}

	eventData := common.EventData{
		Event: common.Event{
			Name:      common.EventOneWayIconRun,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.DBSyncerManagerProcess,
			Data: processData,
		},
		ParameterizedQueries: quries,
		SQLConditions: getRunSqlCondition(processData.RunJobData.InnerJobID),
	}

	eventData.ParameterizedQueries = append(getCommonQuries(processData, clientPid, time.Now()), eventData.ParameterizedQueries...)

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

func EventIconRun(eventData common.EventData) error {
	loadCommonVars()

	var err error
	log := logger.Logging{}

	runData, ok := eventData.NextProcess.Data.(common.IconRunData)
	if !ok {
		err := fmt.Errorf(
			"type cast failed, exiting. expected 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

	// if there is an error before icon status is RUN, we consider it as pre exec failure
	processData.JobResult.PreExecFailFlag = true

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

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

	defer func() {
		// send signal to client helper to exit on finish
		if err := common.SendSIG(filepath.Join(clientDataPath, runData.TransactionFileId+common.ClientExts[5]), syscall.SIGUSR1); err != nil {
			log.JaLog("JAEXECRUN300001", log.InnerJobnetID, log.InnerJobID,
				log.JobnetID, log.JobID, processData.RunJobData.IconType, err.Error())
		}

		// move transaction file to run/ on finish
		err = os.Rename(filepath.Join(execManagerInPath, runData.TransactionFileId+".json"),
			filepath.Join(execManagerRunPath, runData.TransactionFileId+".json"))
		if err != nil {
			log.JaLog("JAEXECRUN000001", log.InnerJobnetID, log.InnerJobID,
				log.JobnetID, log.JobID, processData.RunJobData.IconType, err.Error())
		}
	}()

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

	// get icon client pid
	byteData, err := os.ReadFile(
		filepath.Join(clientDataPath, runData.TransactionFileId+common.ClientExts[1]))
	if err != nil {
		err = fmt.Errorf("could not get icon client PID. common.ClientExts[1]: %s, error: %s", common.ClientExts[1], err.Error())
		logMessage, _ := log.JaLog("JAEXECRUN200001", log.InnerJobnetID, log.InnerJobID,
			log.JobnetID, log.JobID, processData.RunJobData.IconType, err.Error())

		return setRunErr(logMessage, processData)
	}

	clientPid, err := strconv.Atoi(strings.TrimSuffix(string(byteData), "\n"))
	if err != nil {
		err = fmt.Errorf("could not get icon client PID. error: %s", err.Error())
		logMessage, _ := log.JaLog("JAEXECRUN200001", log.InnerJobnetID, log.InnerJobID,
			log.JobnetID, log.JobID, processData.RunJobData.IconType, err.Error())

		return setRunErr(logMessage, processData)
	}

	// process according to the icon types
	switch processData.RunJobData.IconType {

	case common.IconTypeStart,
		common.IconTypeEnd,
		common.IconTypeL:
		err = oneWayIconRun(processData, clientPid)

	case common.IconTypeIf,
		common.IconTypeInfo,
		common.IconTypeCalc,
		common.IconTypeFCopy,
		common.IconTypeFWait,
		common.IconTypeLink,
		common.IconTypeValue:
		err = commonIconRun(processData, clientPid)
	
	case common.IconTypeM:
		err = mIconRun(processData, clientPid)

	case common.IconTypeJob:
		err = jobIconRun(processData, clientPid)

	case common.IconTypeReboot:
		err = rebootIconRun(processData, clientPid)

	case common.IconTypeW:
		err = wIconRun(processData, clientPid)

	case common.IconTypeIfEnd:
		err = ifEndIconRun(processData, clientPid)

	case common.IconTypeLess:
		err = agentlessIconRun(runData, processData, clientPid)

	case common.IconTypeExtJob:
		err = extJobIconRun(runData, processData, clientPid)

	case common.IconTypeJobnet:
		err = jobnetIconRun(processData, clientPid)

	case common.IconTypeRel:
		err = releaseIconRun(processData, clientPid)

	case common.IconTypeTask:
		err = taskIconRun(runData, processData, clientPid)

	default:
		err = fmt.Errorf("unknown icon type")
	}

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

		return setRunErr(logMessage, processData)
	}

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

	return nil
}
