/*
** 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 (
	"fmt"
	"os"
	"strconv"
	"strings"
	"syscall"
	"time"

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

var log logger.Logging

// ----------------- Helpers -----------------

func isProcessAlive(pid int) bool {
	process, err := os.FindProcess(pid)
	if err != nil {
		return false
	}
	// Signal 0 = only existence/permission check
	return process.Signal(syscall.Signal(0)) == nil
}

func moveTrFile(srcPath string) {
	funcName := "MoveTrFile"
	// Replace "/in/" with "/end/" in the path
	dstPath := strings.Replace(srcPath, "/in/", "/end/", 1)

	// Move the file
	if err := os.Rename(srcPath, dstPath); err != nil {
		log.JaLog("JAEXECCHKPROS300001", funcName, fmt.Sprintf("transaction move failed: [error: %v]", err))
	}
}

func logAndMove(err error, funcName, srcPath string) {
	log.JaLog("JAEXECCHKPROS300001", funcName, err.Error())
	moveTrFile(srcPath)
}

func setRunError(processData common.IconExecutionProcessData, message string) {
	funcName := "setRunError"

	processData.JobResult.Message = message
	processData.JobResult.JobRunRequestData.JobID = strconv.FormatUint(processData.RunJobData.InnerJobID, 10)
	// Adding file type for fcopy
	if processData.RunJobData.IconType == common.IconTypeFCopy {
		processData.JobResult.JobRunRequestData.Type = common.AgentJobTypePutFile
		processData.JobResult.Result = common.JA_RESPONSE_FAIL
	}

	nextProcessData := common.EventData{
		Event: common.Event{
			Name:      common.EventIconResultUpdate,
			UniqueKey: common.GetUniqueKey(common.IconExecManagerProcess),
		},
		NextProcess: common.NextProcess{
			Name: common.IconResultManagerProcess,
			Data: processData,
		},
	}

	if err := event.CreateNextEvent(
		nextProcessData,
		processData.RunJobData.InnerJobnetID,
		processData.RunJobnetData.JobnetID,
		processData.RunJobData.InnerJobID,
	); err != nil {
		log.JaLog("JAEXECCHKPROS300001", funcName, fmt.Sprintf("CreateNextEvent: %v", err))
	}
}

func validateIDs(processData common.IconExecutionProcessData, funcName, srcPath string) bool {
	if processData.RunJobData.InnerJobnetID <= 0 {
		logAndMove(fmt.Errorf("invalid innerJobnetId=%d", processData.RunJobData.InnerJobnetID), funcName, srcPath)
		return false
	}
	log.InnerJobnetID = processData.RunJobData.InnerJobnetID

	if processData.RunJobData.InnerJobID <= 0 {
		logAndMove(fmt.Errorf("invalid innerJobId=%d", processData.RunJobData.InnerJobID), funcName, srcPath)
		return false
	}
	log.InnerJobID = processData.RunJobData.InnerJobID

	if processData.RunJobnetData.JobnetID == "" {
		logAndMove(fmt.Errorf("invalid jobnetId (empty)"), funcName, srcPath)
		return false
	}
	return true
}

func handleSSHCheck(processData common.IconExecutionProcessData, srcPath, funcName string) {
	sshPid := processData.SessionData.PID
	if sshPid == 0 {
		setRunError(processData, "ssh_client Pid is 0")
		logAndMove(fmt.Errorf("invalid sshClientPid"), funcName, srcPath)
		return
	}

	if isProcessAlive(sshPid) {
		log.JaLog("JAEXECCHKPROS000001", funcName, fmt.Sprintf("ssh_pid %d is alive", sshPid))
		moveTrFile(srcPath)
		return
	}

	msg := fmt.Sprintf("ssh_pid %d is not alive", sshPid)
	log.JaLog("JAEXECCHKPROS000001", funcName, msg)
	setRunError(processData, msg)
	moveTrFile(srcPath)

}

func handleTimeout(processData common.IconExecutionProcessData, funcName string) string {
	if processData.RunTimeoutData.InnerJobID == 0 {
		log.JaLog("JAEXECCHKPROS300001", funcName,
			fmt.Sprintf("in ja_2_run_timeout_table, record is null for %d", processData.RunTimeoutData.InnerJobID))
		return "timeout data is null"
	}

	if processData.RunJobData.InnerJobID != processData.RunTimeoutData.InnerJobID {
		return "jobID is not match runJobData and timeoutData"
	}

	timeout := processData.RunTimeoutData.Timeout
	adjustedNow := time.Now().Unix() - 5 // adjust DB fetch delay
	if timeout > uint64(adjustedNow) {
		return "" // still within timeout window
	}
	return "Aborted the job"
}

// ----------------- Main -----------------

func EventExecCheckLocal(eventData common.EventData) error {
	funcName := "EventExecCheckLocal"
	loadCommonVars()

	// Type assertion
	processData, ok := eventData.NextProcess.Data.(common.IconExecutionProcessData)
	if !ok {
		logAndMove(fmt.Errorf("expected common.IconExecutionProcessData, got %T", eventData.NextProcess.Data), funcName, "")
		return nil
	}

	processData.JobResult.Result = common.JA_JOBRESULT_FAIL
	srcPath := eventData.Transfer.Files[0].Source

	// Validation
	if !validateIDs(processData, funcName, srcPath) {
		return nil
	}

	clientPid := processData.RunJobData.ClientPID
	if clientPid <= 0 {
		setRunError(processData, "client Pid is 0")
		logAndMove(fmt.Errorf("invalid client_pid"), funcName, srcPath)
		return nil
	}

	log.JaLog("JAEXECCHKPROS400003", funcName, clientPid, processData.RunJobData.InnerJobID, processData.RunJobData.InnerJobnetID)

	// Check client process
	if isProcessAlive(clientPid) {
		log.JaLog("JAEXECCHKPROS000001", funcName, fmt.Sprintf("pid %d is alive", clientPid))

		// Special handling for IconTypeLess
		if processData.RunJobData.IconType == common.IconTypeLess {
			var iconData common.IconLessData
			if err := utils.Convert(processData.RunJobData.Data, &iconData); err != nil {
				logAndMove(fmt.Errorf("failed to convert Data: %w", err), funcName, srcPath)
				return nil
			}
			if iconData.SessionFlag == 0 || iconData.SessionFlag == 2 {
				handleSSHCheck(processData, srcPath, funcName)
				return nil
			}
		}
	} else {
		msg := fmt.Sprintf("pid %d is not alive", clientPid)
		log.JaLog("JAEXECCHKPROS000001", funcName, msg)
		processData.JobResult.Message = msg
		var timeoutMsg string
		// Extra check for external jobs
		if processData.RunJobData.IconType == common.IconTypeExtJob {
			if timeoutMsg = handleTimeout(processData, funcName); timeoutMsg == "" {
				moveTrFile(srcPath)
				return nil
			}
			processData.JobResult.Message = timeoutMsg
		}
		setRunError(processData, processData.JobResult.Message)
	}

	moveTrFile(srcPath)
	return nil
}
