/*
** 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 (
	"fmt"
	"os"
	"path/filepath"
	"strconv"

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

func ProcessIcon(
	conn database.DBConnection,
	processData common.FlowProcessData,
	nextProcessData *common.IconExecutionProcessData,
	processType *common.FlowProcessType,
) error {
	var nextEventData common.EventData
	var jobLogData logger.Logging
	var lockPQ, insertPQ, updatePQ common.ParameterizedQuery
	var err error
	var funcName = "ProcessIcon"

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

	// this condition is needed to perform runerr skip and subjobnet status change
	if *processType != common.FlowProcessSkip && *processType != common.FlowProcessSubJobnet {
		jobnetStatus, innerJobnetMainId, err := utils.GetStatusFromRunJobnetTable(conn, processData.InnerJobnetId)
		if err != nil {
			logger.JaLog("JAFLOW200011", logger.Logging{}, funcName, processData.InnerJobnetId, err)
			return fmt.Errorf("GetStatusFromRunJobnetTable() failed")
		}
		if jobnetStatus != common.StatusRun && jobnetStatus != common.StatusRunErr {
			logger.JaLog("JAFLOW300002", logger.Logging{}, funcName, jobnetStatus, processData.InnerJobnetId)
			return nil
		}

		runJobnetSummaryData, err := utils.GetRunJobnetSummaryTableData(conn, innerJobnetMainId)
		if err != nil {
			logger.JaLog("JAFLOW200011", logger.Logging{}, funcName, processData.InnerJobnetId, err)
			return fmt.Errorf("GetRunJobnetSummaryTableData() failed")
		}
		if runJobnetSummaryData.JobnetAbortFlag == 1 || runJobnetSummaryData.EndingFlag == 1 {
			logger.JaLog("JAFLOW300004", logger.Logging{}, funcName, innerJobnetMainId)
			return nil
		}
	}

	var runJobData common.RunJobTable
	// this code is needed for failed over rerun for hold/skip ready icons
	if *processType == common.FlowProcessFailoverIconRerun {
		runJobData, err = utils.GetRunJobData(conn, processData.InnerJobId)
		if err != nil {
			logger.JaLog("JAFLOW200008", logger.Logging{}, funcName,
				common.Ja2RunJobTable, processData.InnerJobId, err)
			return err
		}
		if runJobData.Status == common.StatusReady {
			switch runJobData.MethodFlag {
			case common.MethodWait:
				return nil
			case common.MethodSkip:
				*processType = common.FlowProcessNormal

			}
		}
	}

	nextEventData.Event.Name = common.EventIconReady
	nextEventData.Event.UniqueKey = common.GetUniqueKey(processName)
	nextEventData.NextProcess.Name = common.IconExecManagerProcess

	nextProcessData.RunJobData.InnerJobID = processData.InnerJobId
	insertPQ, err = PrepareDataForNextProcess(conn, nextProcessData, processData, processType)
	if err != nil {
		err = fmt.Errorf("data preparation for inner_job_id: %d has failed. error: [%v]",
			processData.InnerJobId, err)
		logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "PrepareDataForNextProcess", err)
		return fmt.Errorf("PrepareDataForNextProcess() failed")
	} // status = runerr if host is disabled/invalid in data preparation

	switch *processType {
	case common.FlowProcessHostLockWait:
		return nil
	case common.FlowProcessNormal, common.FlowParallelStartRun:
		nextEventData.AppendPQs(insertPQ)
	}

	switch *processType {
	case common.FlowProcessIconRerun, common.FlowProcessFailoverIconRerun,
		common.FlowProcessUnhold, common.FlowProcessSkip,
		common.FlowProcessSubJobnet:
		break
	default:
		err = AssignRunCount(processData, nextProcessData)
		if err != nil {
			logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "AssignRunCount", err)
			return fmt.Errorf("AssignRunCount() failed")
		}
	}

	// if the previous job was MethodRerun for parallel start icon, set MethodRerun for the next job as well
	if processData.IconType == common.IconTypeM && *processType == common.FlowParallelStartRun {
		previousProcessData := common.IconExecutionProcessData{}
		err = utils.Convert(processData.Data, &previousProcessData)
		if err != nil {
			return fmt.Errorf("Convert() failed, err: [%w]", err)
		}
		if previousProcessData.RunJobData.MethodFlag == common.MethodRerun {
			switch nextProcessData.RunJobData.MethodFlag {
			case common.MethodSkip, common.MethodWait:
				break
			default:
				nextProcessData.RunJobData.MethodFlag = common.MethodRerun
				nextProcessData.RunJobData.Status = common.StatusReady
			}
		}
	}

	switch *processType {
	case common.FlowProcessUnhold:
		// set icon method as normal
		nextProcessData.RunJobData.MethodFlag = common.MethodNormal
		lockPQ, updatePQ = builder.GetRunJobMethodFlagChangeQuery(
			processData.InnerJobId, common.MethodNormal)

		sqlCond := BuildSQlConditionForHoldIconUserActionUnhold(nextProcessData.RunJobData.InnerJobID)
		nextEventData.SQLConditions = []common.SQLCondition{sqlCond}

	case common.FlowProcessSkip:
		// set icon method as skip
		prevMethodFlag := nextProcessData.RunJobData.MethodFlag
		nextProcessData.RunJobData.MethodFlag = common.MethodSkip
		lockPQ, updatePQ = builder.GetRunJobMethodFlagChangeQuery(
			processData.InnerJobId, common.MethodSkip)

		switch {
		case prevMethodFlag == common.MethodWait:
			sqlCond := BuildSQlConditionForHoldIconUserActionSkip(nextProcessData.RunJobData.InnerJobID)
			nextEventData.SQLConditions = []common.SQLCondition{sqlCond}
		case nextProcessData.RunJobData.Status == common.StatusEndErr:
			sqlCond := BuildSQlConditionForEndErrorUserActionSkip(nextProcessData.RunJobData.InnerJobID)
			nextEventData.SQLConditions = []common.SQLCondition{sqlCond}
		default:
			sqlCond := BuildSQlConditionForRunErrIconUserActionSkip(nextProcessData.RunJobData.InnerJobID)
			nextEventData.SQLConditions = []common.SQLCondition{sqlCond}
		}

	case common.FlowProcessIconRerun, common.FlowProcessFailoverIconRerun:
		// set icon method as rerun
		nextProcessData.RunJobData.MethodFlag = common.MethodRerun
		lockPQ, updatePQ = builder.GetJobStatusChangeQuery(
			processData.InnerJobId, common.StatusReady, common.MethodRerun,
			nextProcessData.RunJobData.RunCount, 0, 0)
		if *processType == common.FlowProcessIconRerun {
			sqlCond := BuildSQlConditionForRunErrIconUserActionRerun(nextProcessData.RunJobData.InnerJobID)
			nextEventData.SQLConditions = []common.SQLCondition{sqlCond}
		}

	default:
		if nextProcessData.RunJobData.MethodFlag == common.MethodRerun {
			lockPQ, updatePQ = builder.GetJobStatusChangeQuery(
				processData.InnerJobId, common.StatusReady, common.MethodNormal,
				nextProcessData.RunJobData.RunCount, 0, 0)
		} else {
			lockPQ, updatePQ = builder.GetJobStatusChangeQuery(
				processData.InnerJobId, common.StatusReady, -1,
				nextProcessData.RunJobData.RunCount, 0, 0)
		}
	}

	if *processType < common.FlowProcessSubJobnet &&
		(nextProcessData.RunJobData.IconType != common.IconTypeW ||
			*processType == common.FlowProcessUnhold) &&
		(nextProcessData.RunJobData.IconType != common.IconTypeIfEnd ||
			nextProcessData.RunJobData.TestFlag == common.FlagOff) {
		nextEventData.AppendPQs(lockPQ, updatePQ)
	}

	switch *processType {
	case common.FlowProcessSkip,
		common.FlowProcessIconRerun, common.FlowProcessFailoverIconRerun:
		err = PrepareJobLogData(*nextProcessData, &jobLogData)
		if err != nil {
			logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "PrepareJobLogData", err)
			return fmt.Errorf("PrepareJobLogData() failed")
		}
	}

	//assign nextProcessData to next event for event creation
	if (nextProcessData.RunJobData.Status != common.StatusRunErr ||
		*processType == common.FlowProcessIconRerun) &&
		nextProcessData.RunJobData.IconType != common.IconTypeW {
		nextProcessData.RunJobData.Status = common.StatusReady // default status
	}

	switch {
	case nextProcessData.RunJobData.MethodFlag == common.MethodWait &&
		nextProcessData.RunJobData.IconType != common.IconTypeW &&
		*processType != common.FlowProcessSkip:
		logger.JaLog("JAFLOW400006", logger.Logging{}, funcName, nextProcessData.RunJobData.InnerJobID)
		nextEventData.Event.Name = common.EventIconHold

	case *processType == common.FlowProcessSkip ||
		nextProcessData.RunJobData.MethodFlag == common.MethodSkip:
		nextEventData.Event.Name = common.EventIconSkip
		nextProcessData.JobResult.JobID = strconv.FormatUint(nextProcessData.RunJobData.InnerJobID, 10)
		if nextProcessData.RunJobData.IconType == common.IconTypeReboot {
			nextProcessData.JobResult.Hostname = nextProcessData.RunJobData.Data["host_name"].(string)
		}
		jobLogData.JobStatus = common.StatusReady
		logger.JaJobLog(common.JC_JOB_SKIP, jobLogData)

	case *processType == common.FlowProcessSubJobnet:
		nextEventData.Event.Name = common.EventIconResultUpdate
		nextProcessData.JobResult.ReturnCode = processData.ExitCode
		nextProcessData.RunJobData.Status = processData.Status

	case nextProcessData.RunJobData.Status == common.StatusRunErr:
		nextEventData.Event.Name = common.EventFlowRunErr
		nextProcessData.JobResult.Result = common.JA_JOBRESULT_PRE_EXEC_FAIL

	case *processType == common.FlowProcessIconRerun || *processType == common.FlowProcessFailoverIconRerun:
		jobLogData.JobStatus = common.StatusReady
		logger.JaJobLog(common.JC_JOB_RERUN, jobLogData)
	}

	if nextProcessData.RunJobData.IconType == common.IconTypeReboot {
		if nextEventData.Event.Name != common.EventIconHold && nextEventData.Event.Name != common.EventIconSkip &&
			nextEventData.Event.Name != common.EventIconHoldSkip {
			hostname := nextProcessData.RunJobData.Data["host_name"].(string)
			nextProcessData.JobResult.Hostname = hostname
			err = builder.CreateUniqueFile(
				filepath.Join(common.ServerTmpFolderPath, common.FlowManagerFolder, common.HostLockSubFolder),
				hostname,
			)
			if err != nil {
				if os.IsExist(err) {
					//other reboot icon is currently processing
					logger.JaLog("JAFLOW300009", logger.Logging{}, funcName, hostname, nextProcessData.RunJobData.InnerJobID)
					*processType = common.FlowProcessHostLockWait
					return nil
				}
				return err
			}

			lockPQ, insertPQ = builder.GetHostLockQuery(hostname)
			nextEventData.AppendPQs(lockPQ, insertPQ)
		}
	}

	switch nextEventData.Event.Name {
	case common.EventIconReady:
		AddHostLockCheckSQLCondition(&nextEventData, nextProcessData.RunJobData)
	}

	nextEventData.NextProcess.Data = nextProcessData

	cond := fmt.Sprintf("select method_flag from ja_2_run_job_table where inner_job_id = %d", nextProcessData.RunJobData.InnerJobID)

	if nextEventData.Event.Name == common.EventIconHold {
		nextEventData.SQLConditions = []common.SQLCondition{buildHoldSQLCondition(cond, common.ActionExec, common.ActionIgnore)}
	}
	err = event.CreateNextEvent(nextEventData, nextProcessData.RunJobData.InnerJobnetID, processData.JobnetId, processData.InnerJobId)
	if err != nil {
		logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "CreateNextEvent", err)
		return fmt.Errorf("CreateNextEvent() failed")
	}

	if nextEventData.Event.Name == common.EventIconHold {
		nextProcessData.RunJobData.MethodFlag = common.MethodNormal
		err = PrepareIconDetailData(conn, nextProcessData.RunJobData, nextProcessData, *processType)
		if err != nil {
			logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "PrepareIconDetailData", err)
			return fmt.Errorf("PrepareIconDetailData() failed")
		}
		nextEventData.SQLConditions = []common.SQLCondition{buildHoldSQLCondition(cond, common.ActionIgnore, common.ActionExec)}

		nextEventData.Event.Name = common.EventIconReady
		nextEventData.Event.UniqueKey = common.GetUniqueKey(processName)
		nextEventData.NextProcess.Data = nextProcessData

		err = event.CreateNextEvent(nextEventData, nextProcessData.RunJobData.InnerJobnetID, processData.JobnetId, processData.InnerJobId)
		if err != nil {
			logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "CreateNextEvent", err)
			return fmt.Errorf("CreateNextEvent() failed")
		}
	}

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

func buildHoldSQLCondition(cond string, actionWhenMethodWait, defaultAction common.Action) common.SQLCondition {
	return builder.NewSQLCondition(cond).
		AddCondition(
			builder.NewCondition(actionWhenMethodWait).
				Field("method_flag", common.OpEq, common.MethodWait).
				Build(),
		).
		DefaultAction(defaultAction).
		Build()
}

func ProcessMiconStatusChange(processData common.FlowProcessData, forceStopFlag bool) {
	funcName := "ProcessIconEnd"
	nextEventData := common.EventData{}
	nextProcessData := common.IconExecutionProcessData{}
	var err error

	err = utils.Convert(processData.Data, &nextProcessData)
	if err != nil {
		logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "Convert", err)
	}

	nextProcessData.JobResult.JobID = strconv.FormatUint(nextProcessData.RunJobData.InnerJobID, 10)
	if forceStopFlag {
		nextProcessData.JobResult.Message = "Aborted the multiple start icon"
		nextProcessData.RunJobData.Status = common.StatusRunErr
		nextProcessData.JobResult.Result = common.JA_JOBRESULT_FAIL
		nextEventData.NextProcess.Name = common.IconResultManagerProcess
	} else {
		nextProcessData.JobResult.Result = common.JobResultRespStatusSuccess
		nextEventData.NextProcess.Name = common.IconExecManagerProcess
	}

	nextEventData.Event.Name = common.EventIconResultUpdate
	nextEventData.Event.UniqueKey = common.GetUniqueKey(processName)
	nextEventData.NextProcess.Data = nextProcessData
	err = event.CreateNextEvent(nextEventData, nextProcessData.RunJobData.InnerJobnetID, processData.JobnetId, nextProcessData.RunJobData.InnerJobID)
	if err != nil {
		logger.JaLog("JAFLOW200000", logger.Logging{}, funcName, "CreateNextEvent", err)
	}
}
