/*
** 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 agentutils

import (
	"bufio"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"jobarranger2/src/libs/golibs/common"
	"jobarranger2/src/libs/golibs/config_reader/agent"
	"jobarranger2/src/libs/golibs/logger/logger"
	"jobarranger2/src/libs/golibs/utils"
)

func CreateJsonFileAgent(eventData *common.EventData, jobID string, targetDir, currentTimestamp string) error {
	var jsonFile *os.File
	var err error

	filename := fmt.Sprintf("%s-%s", jobID, currentTimestamp)
	tmpFilename := fmt.Sprintf("%s.json.tmp", filename)
	tmpFilePath := filepath.Join(targetDir, tmpFilename)

	targetFilename := fmt.Sprintf("%s.%s", filename, "json")
	targetFilePath := filepath.Join(targetDir, targetFilename)

	if jsonFile, err = os.Create(tmpFilePath); err != nil {
		return fmt.Errorf("create json:%s file failed under %s. [%v] ", tmpFilename, targetDir, err)
	}

	eventData.Transfer.Files = []common.FileTransfer{
		{Source: targetFilePath},
	}

	// Add unique key
	eventData.Event.UniqueKey = fmt.Sprintf("%s_%s", jobID, common.GetUniqueKey(common.AgentManagerProcess))

	if err = WriteJSON(jsonFile, *eventData); err != nil {
		return fmt.Errorf("write json:%v file failed. [%v] ", (*jsonFile).Name(), err)
	}
	jsonFile.Close()

	if err = utils.MoveFileWithLockRetry(tmpFilePath, targetFilePath, common.FileLockRetryCount); err != nil {
		return fmt.Errorf("failed to rename file from %s to %s. [%v]", tmpFilePath, targetFilePath, err)
	}

	return err
}

func WriteJSON(jsonFile *os.File, eventData common.EventData) error {
	if *eventData.TCPMessage.JazVersion == 1 {
		eventData = common.EventData{
			Event:      common.Event{UniqueKey: eventData.Event.UniqueKey},
			TCPMessage: eventData.TCPMessage,
			Transfer:   eventData.Transfer,
		}
	}

	jsonData, err := json.MarshalIndent(eventData, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal full JSON struct: %v", err)
	}

	_, err = jsonFile.Write(jsonData)
	if err != nil {
		return fmt.Errorf("failed to write full JSON to file: %v", err)
	}

	// Optionally write a newline at the end
	_, _ = jsonFile.Write([]byte("\n"))

	return nil
}

// UpdateOrAppendJSON updates or adds an ID-IP entry in a JSON file, and prints what it did.
func UpdateOrAppendJSON(filePath, id, ip string) error {
	fn := "UpdateOrAppendJSON"
	data := make(map[string]string)

	// Read existing file if it exists
	content, err := os.ReadFile(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			logger.WriteLog("JAAGENTMNG000010", fn, filePath, utils.ErrMsgWithErrno(err), ip, id)
		} else {
			logger.WriteLog("JAAGENTMNG400008", fn, filePath, ip, id)
		}

		// read failure = recreate file
		data[id] = ip
		return writeJSON(filePath, data)
	}
	// Try to parse JSON if there's content
	if len(content) > 0 {
		if err := json.Unmarshal(content, &data); err != nil {
			data = map[string]string{id: ip}
			logger.WriteLog("JAAGENTMNG300002", fn, filePath, string(content), ip, id)
			return writeJSON(filePath, data)
		}
	}

	// Compare and decide what to do
	if existingIP, exists := data[id]; exists {
		if existingIP == ip {
			logger.WriteLog("JAAGENTMNG400009", fn, filePath, ip, id)
			return nil // No need to write
		}
		// Update existing
		logger.WriteLog("JAAGENTMNG400010", fn, id, existingIP, ip, filePath)
		data[id] = ip
	} else {
		// New entry
		logger.WriteLog("JAAGENTMNG400011", fn, ip, id, filePath)
		data[id] = ip
	}

	// Write updated JSON
	return writeJSON(filePath, data)
}

func writeJSON(filePath string, data map[string]string) error {
	newContent, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to marshal JSON: %w", err)
	}
	return os.WriteFile(filePath, newContent, 0644)
}

func GetIPByServerID(filePath, serverID string) (string, error) {
	data := make(map[string]string)
	content, err := os.ReadFile(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return "", fmt.Errorf("file does not exist: %s", filePath)
		}
		return "", fmt.Errorf("failed to read file: %w", err)
	}
	if err := json.Unmarshal(content, &data); err != nil {
		return "", fmt.Errorf("failed to unmarshal JSON: %w", err)
	}
	ip, ok := data[serverID]
	if !ok {
		return "", ErrServerIDNotExist
	}
	return ip, nil
}

// ReadLastLineOrNew reads the last line of the file at filePath.
// If the file does not exist, it returns "new".
func ReadLastLineOrNew(filePath string) (string, error) {
	file, err := os.Open(filePath)
	if errors.Is(err, os.ErrNotExist) {
		return "new", nil
	}
	if err != nil {
		return "", fmt.Errorf("error opening file: %w", err)
	}
	defer file.Close()

	var lastLine string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		lastLine = scanner.Text()
	}
	if err := scanner.Err(); err != nil {
		return "", fmt.Errorf("error reading file: %w", err)
	}

	return strings.TrimSpace(lastLine), nil
}

// OverwriteFile writes the given string to the file at filePath.
// If the file exists, it is truncated (overwritten).
func OverwriteFile(filePath, data string) error {
	// os.Create truncates the file if it exists
	file, err := os.Create(filePath)
	if err != nil {
		return fmt.Errorf("failed to create/truncate file: %w", err)
	}
	defer file.Close()

	_, err = file.WriteString(data)
	if err != nil {
		return fmt.Errorf("failed to write to file: %w", err)
	}

	return nil
}

// EnsureAgentFoldersExist creates required subdirectories under basePath.
func EnsureAgentFoldersExist(basePath string) error {
	// Define relative paths to create under basePath
	requiredDirs := []string{
		"serverIPs",
		"jobs",
		"data",
		"end",
		"in",
		"temp",
		"close",
		"error",
		"exec",
		"run_count",
		"abort",
		"lock",
	}

	for _, dir := range requiredDirs {
		fullPath := filepath.Join(basePath, dir)
		if err := os.MkdirAll(fullPath, 0755); err != nil {
			return fmt.Errorf("failed to create directory %s: %w", fullPath, err)
		}
	}

	return nil
}

const (
	SUCCEED = 0
	FAIL    = 1
)

// addUIDToJobFile appends a unique ID to the specified job file with file locking
func AddUIDToJobFile(jobfilePath string, uniqueID string) error {
	const functionName = "addUIDToJobFile"

	// Open file in append mode, create if doesn't exist
	file, err := os.OpenFile(jobfilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return fmt.Errorf("in %s() error opening file %s: %v", functionName, jobfilePath, err)
	}
	defer file.Close()

	// Write the unique ID with newline
	_, err = fmt.Fprintf(file, "%s\n", uniqueID)
	if err != nil {
		return fmt.Errorf("in %s() error writing to file %s: %v", functionName, jobfilePath, err)
	}

	// Ensure data is written to disk
	err = file.Sync()
	if err != nil {
		return fmt.Errorf("in %s() error syncing file %s: %v", functionName, jobfilePath, err)
	}

	return nil
}

// CreateFolderIfNotExist creates the specified folder and all necessary parent folders.
// If the folder already exists, it does nothing.
func CreateFolderIfNotExist(folderPath string) error {
	// Clean the path to avoid issues with trailing slashes, etc.
	cleanPath := filepath.Clean(folderPath)

	// MkdirAll creates all missing directories along the path.
	// If the folder already exists, it returns nil.
	err := os.MkdirAll(cleanPath, 0755)
	if err != nil {
		return err
	}
	return nil
}

// MoveFileToDir moves a file to the specified directory, preserving the filename.
func MoveFileToDir(filePath, dirPath string, lockFlag bool) error {
	funcName := "MoveFileToDir"
	// Ensure target directory exists
	if err := os.MkdirAll(dirPath, 0755); err != nil {
		return fmt.Errorf("failed to create target directory: %v", err)
	}

	// Extract the filename from the full filepath
	filename := filepath.Base(filePath)

	// Construct the target path
	targetPath := filepath.Join(dirPath, filename)

	if lockFlag {
		// Acquire a shared lock on the lock file before moving the file.
		file, err := utils.LockFile(utils.LOCKFILE_SHARE_LOCK | utils.LOCKFILE_FAIL_ON_LOCK)
		if err != nil {
			return fmt.Errorf("failed to acquire share lock. from dir: %s, to dir: %s, err: [%w]", filePath, targetPath, err)
		}

		defer func() {
			// Unlock the lock file
			err = utils.UnlockFile(file)
			if err != nil {
				logger.JaLog("JAUTILS200002", logger.Logging{}, funcName, common.Manager.Name, filePath, targetPath, err)
			}
		}()
	}

	// Move (rename) the file
	if err := utils.MoveFileWithLockRetry(filePath, targetPath, common.FileLockRetryCount); err != nil {
		return fmt.Errorf("failed to move file: %v", err)
	}

	return nil
}

// MoveMatchingToDir moves all files or directories in the current dir
// whose names match the given regular expression into dirPath.
func MoveMatchingToDir(pattern, baseDir, targetDir string, lockFlag bool) error {
	funcName := "MoveMatchingToDir"
	// Compile the pattern
	re, err := regexp.Compile(pattern)
	if err != nil {
		return fmt.Errorf("invalid regex pattern: %v", err)
	}

	// Ensure target directory exists
	if err := os.MkdirAll(targetDir, 0755); err != nil {
		return fmt.Errorf("failed to create target directory: %v", err)
	}

	if lockFlag {
		// Acquire a shared lock on the lock file before moving the file.
		file, err := utils.LockFile(utils.LOCKFILE_SHARE_LOCK | utils.LOCKFILE_FAIL_ON_LOCK)
		if err != nil {
			return fmt.Errorf("failed to acquire share lock. from dir: %s, to dir: %s, err: [%w]", baseDir, targetDir, err)
		}

		defer func() {
			// Unlock the lock file
			err = utils.UnlockFile(file)
			if err != nil {
				logger.JaLog("JAUTILS200002", logger.Logging{}, funcName, common.Manager.Name, baseDir, targetDir, err)
			}
		}()
	}

	// Walk through entries in the current directory
	entries, err := os.ReadDir(baseDir)
	if err != nil {
		return fmt.Errorf("failed to read current directory: %v", err)
	}

	for _, entry := range entries {
		name := entry.Name()

		if re.MatchString(name) {
			sourcePath := filepath.Join(baseDir, name)
			targetPath := filepath.Join(targetDir, name)

			// Move the file or directory
			if err := utils.MoveFileWithLockRetry(sourcePath, targetPath, common.FileLockRetryCount); err != nil {
				return fmt.Errorf("failed to move %s: %v", name, err)
			}
		}
	}

	return nil
}

// Create or truncate file
func CreateFile(filePath string) error {
	dir := filepath.Dir(filePath)
	if err := os.MkdirAll(dir, common.FilePermission); err != nil {
		return fmt.Errorf("failed to create dir %s: %v", dir, err)
	}

	file, err := os.Create(filePath)
	if err != nil {
		return fmt.Errorf("failed to create file %s: %v", filePath, err)
	}
	file.Close() // Close immediately since we just need an empty file

	return nil
}

func WriteCurrentTimeToFile(filePath string) error {
	file, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return fmt.Errorf("failed to open file %s: %v", filePath, err)
	}
	defer file.Close()

	// Format current time
	currentTime := time.Now().Format("20060102150405")

	// Write current time with newline
	if _, err := file.WriteString(currentTime + "\n"); err != nil {
		return fmt.Errorf("failed to write to file %s: %v", filePath, err)
	}

	return nil
}

func WriteToFile(filePath string, content any) error {
	bytes, err := json.MarshalIndent(content, "", "  ")
	if err != nil {
		return fmt.Errorf("marshal error: %w", err)
	}

	f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0)
	if os.IsNotExist(err) {
		return fmt.Errorf("file does not exist: %s", filePath)
	} else if err != nil {
		return fmt.Errorf("failed to open file: %w", err)
	}
	defer f.Close()

	if _, err := f.Write(bytes); err != nil {
		return fmt.Errorf("write error: %w", err)
	}

	return nil
}

func WriteStringToFile(filePath string, content string) error {
	file, err := os.OpenFile(filePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return fmt.Errorf("failed to open file %s: %v", filePath, err)
	}
	defer file.Close()

	// Write current time with newline
	if _, err := file.WriteString(content); err != nil {
		return fmt.Errorf("failed to write to file %s: %v", filePath, err)
	}

	return nil
}

func GetScriptExtension() string {
	var exeExtension string
	switch runtime.GOOS {
	case "windows":
		exeExtension = "bat"
	default:
		exeExtension = "sh"
	}

	return exeExtension
}

// WaitJobCompleteAndDeleteFlag waits until all other jobs complete, then deletes the target flag file.
func WaitJobCompleteAndDeleteFlag(dirPath, targetFile string, rebootWaitTime int, rebootModeFlag int) error {
	fn := "WaitJobCompleteAndDeleteFlag"

	rebootWaitDuration := time.Duration(rebootWaitTime) * time.Second

	logger.WriteLog("JAAGENTREBOOT400009", fn, dirPath, targetFile, rebootWaitTime, rebootModeFlag)

	// If reboot mode is off, delete immediately
	if rebootModeFlag == 0 {
		if err := os.Remove(targetFile); err != nil {
			return fmt.Errorf("failed to delete target file %s: %w", targetFile, err)
		}
		logger.WriteLog("JAAGENTREBOOT400010", fn, targetFile)
		return nil
	}

	// Reboot mode: wait logic with timeout
	start := time.Now()
	for {
		entries, err := os.ReadDir(dirPath)
		if err != nil {
			return fmt.Errorf("failed to read directory: %w", err)
		}

		// Filter files ending with ".job"
		var jobFiles []os.DirEntry
		for _, entry := range entries {
			if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".job") {
				jobFiles = append(jobFiles, entry)
			}
		}

		if len(jobFiles) == 1 {
			logger.WriteLog("JAAGENTREBOOT400011", fn, dirPath, targetFile)
			break
		}

		if rebootWaitTime != 0 && time.Since(start) >= rebootWaitDuration {
			logger.WriteLog("JAAGENTREBOOT400012", fn, targetFile)
			break
		}

		logger.WriteLog("JAAGENTREBOOT400013", fn, len(jobFiles))
		time.Sleep(1 * time.Second)
	}

	if err := os.Remove(targetFile); err != nil {
		return fmt.Errorf("failed to delete target file %s: %w", targetFile, err)
	}

	return nil
}

func GetFileNameWithoutExt(fullPath string) string {
	ext := filepath.Ext(fullPath)            // e.g., ".txt"
	return strings.TrimSuffix(fullPath, ext) // "example"
}

func GetBasenameWithoutExt(fullPath string) string {
	base := filepath.Base(fullPath)      // e.g., "example.txt"
	ext := filepath.Ext(base)            // e.g., ".txt"
	return strings.TrimSuffix(base, ext) // "example"
}

/* FCopy utility functions */

// FindMatchingFiles searches for files in the given directory that match the given pattern.
// Supports "*" (any string), "?" (any single character), and Unicode-safe names.
func FindMatchingFiles(dir, pattern string) ([]string, error) {
	var matches []string

	if strings.TrimSpace(pattern) == "" {
		pattern = "*"
	}

	// Ensure pattern is safe and absolute
	searchPattern := filepath.Join(dir, pattern)

	// Walk through files matching the pattern
	files, err := filepath.Glob(searchPattern)
	if err != nil {
		return nil, fmt.Errorf("error during glob search: %w", err)
	}

	for _, file := range files {
		// Skip directories
		info, err := os.Stat(file)
		if err != nil {
			continue
		}
		if info.IsDir() {
			continue
		}

		// Optional: validate filename contains valid UTF-8 characters
		// _, name := filepath.Split(file)
		// if !utf8.ValidString(name) {
		// 	continue
		// }

		matches = append(matches, file)
	}

	return matches, nil
}

func CheckDir(path string) error {
	return checkExistence(path, 1)
}

func CheckFile(path string) error {
	return checkExistence(path, 0)
}

func FileExists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}
	return !os.IsNotExist(err)
}

// fileType: 0 - file, 1 - directory
func checkExistence(path string, fileType int) error {
	info, err := os.Stat(path)
	if os.IsNotExist(err) {
		return fmt.Errorf("path does not exist: %w", err)
	}
	if err != nil {
		return fmt.Errorf("stat error: %w", err)
	}

	// checking for file
	if fileType == 0 && info.IsDir() {
		return fmt.Errorf("path is a directory")
	}

	// checking for directory
	if fileType == 1 && !info.IsDir() {
		return fmt.Errorf("path is not a directory")
	}
	return nil
}

// Get checksum of a file in md5
func ComputeChecksum(filename string) (string, error) {
	file, err := os.Open(filename)
	if err != nil {
		return "", fmt.Errorf("failed to open file: %w", err)
	}
	defer file.Close()

	hasher := md5.New()

	// Use a 16 KiB buffer (same as your C function)
	buf := make([]byte, 16*1024)
	if _, err := io.CopyBuffer(hasher, file, buf); err != nil {
		return "", fmt.Errorf("failed to read file: %w", err)
	}

	sum := hasher.Sum(nil)
	return hex.EncodeToString(sum), nil
}

func VerifyChecksums(chksumJSON string, dir string) error {
	var checksumMap map[string]string
	var err error
	if err = json.Unmarshal([]byte(chksumJSON), &checksumMap); err != nil {
		return fmt.Errorf("cannot parse checksum JSON: %w", err)
	}

	for filename, expectedChecksum := range checksumMap {
		filename, err = EncodeString(filename, agent.Options.Locale)
		if err != nil {
			return fmt.Errorf("failed to encode file name from checksums: %v, error: %v", filename, err)
		}

		fullpath := filepath.Join(dir, filename)

		actualChecksum, err := ComputeChecksum(fullpath)
		if err != nil {
			return fmt.Errorf("cannot compute checksum for %s: %w", fullpath, err)
		}

		if actualChecksum != expectedChecksum {
			return fmt.Errorf("checksum mismatch for %s: expected %s, got %s",
				filename, expectedChecksum, actualChecksum)
		}
	}

	return nil // All checksums match
}

// ExtractPIDFromFilename parses the PID from a filename like "1004-20250725145515-1790887.ext".
// It works with any file extension.
func ExtractPIDFromFilename(filename string) (int, error) {
	base := filepath.Base(filename)
	ext := filepath.Ext(base)

	// Remove the extension
	name := strings.TrimSuffix(base, ext)

	// Split by "-"
	parts := strings.Split(name, "-")
	if len(parts) != 3 {
		return 0, fmt.Errorf("unexpected filename format: %s", filename)
	}

	// Last part should be the PID
	pidStr := parts[2]
	pid, err := strconv.Atoi(pidStr)
	if err != nil {
		return 0, fmt.Errorf("invalid PID in filename: %v", err)
	}

	return pid, nil
}

// CreateDataFiles creates all the job-related files based on DataFileExtensions.
// Skips JSON, writes the script file with content, others as empty files.
func CreateDataFiles(basepath, script string) error {
	for _, ext := range DataFileExtensions {
		filename := fmt.Sprintf("%s.%s", basepath, ext)

		switch {
		case ext == GetScriptExtension():
			// Create and write the script file
			if err := os.WriteFile(filename, []byte(script), 0755); err != nil {
				return fmt.Errorf("cannot write script file %s: %w", filename, err)
			}

		case ext != "json":
			// Create an empty file
			f, err := os.Create(filename)
			if err != nil {
				return fmt.Errorf("cannot create file %s: %w", filename, err)
			}
			f.Close()
		}
	}
	return nil
}

func CheckDataFiles(jobRunFilePath string) ([]string, error) {
	base := strings.TrimSuffix(jobRunFilePath, ".json")

	var missing []string

	for _, ext := range DataFileExtensions {
		path := base + "-*." + ext

		matches, err := filepath.Glob(path)
		if err != nil {
			return nil, fmt.Errorf("glob error for %s: %w", path, err)
		}

		if len(matches) == 0 {
			missing = append(missing, ext)
			continue
		}

	}

	return missing, nil
}

func CreateEmptyDataFilesCore(dir, baseName string) error {
	for _, ext := range DataFileExtensions {
		filename := fmt.Sprintf("%s.%s", baseName, ext)
		filePath := filepath.Join(dir, filename)

		_, err := os.Stat(filePath)
		if err == nil {
			// file already exists
			continue
		}

		if err := CreateFile(filePath); err != nil {
			return fmt.Errorf("failed to create data file %s: %v", filePath, err)
		}
	}

	return nil
}

func CreateEmptyDataFiles(dir, jobID, timestamp, pid string) error {
	baseName := fmt.Sprintf("%s-%s-%s", jobID, timestamp, pid)
	return CreateEmptyDataFilesCore(dir, baseName)
}

func PrepareJobResultHeader(eventData *common.EventData, jobResultData *common.JobResultData, pid int) {
	// Header data preparation
	eventData.Event.Name = common.EventAgentJobResult
	eventData.Event.UniqueKey = common.GetUniqueKey(common.AgentManagerProcess)
	eventData.NextProcess.Name = common.TrapperManagerProcess
	eventData.TCPMessage.Kind = common.KindJobResult
	sendRetry := 0
	eventData.TCPMessage.SendRetry = &sendRetry

	// JobResult data preparation
	jobResultData.PID = pid
	jobResultData.Signal = 0
	jobResultData.Hostname = eventData.TCPMessage.Hostname
}

func CreateJobResultJsonFile(eventData *common.EventData, jobFilePath, jobRunFilePath, inFolderPath, endFolderPath, jobID string) error {
	jobRunFileName := filepath.Base(jobRunFilePath)
	// Create jobresult transaction file
	jobRunFile, err := os.Create(jobRunFilePath)
	if err != nil {
		return fmt.Errorf("failed to overwrite the jobrun file '%s': %v", jobRunFilePath, err)
	}

	// Add file path
	sourceFilePath := filepath.Join(inFolderPath, jobRunFileName)
	eventData.Transfer.Files = []common.FileTransfer{
		{
			Source: sourceFilePath,
		},
	}

	// Add unique key
	eventData.Event.UniqueKey = fmt.Sprintf("%s_%s", jobID, common.GetUniqueKey(common.AgentManagerProcess))

	if err := WriteJSON(jobRunFile, *eventData); err != nil {
		return fmt.Errorf("failed to write the jobresult data: %v", err)
	}
	jobRunFile.Close()

	// Move .job file to end
	if err := MoveFileToDir(jobFilePath, endFolderPath, utils.MoveFileWithLock); err != nil {
		return fmt.Errorf("failed to move job file '%s' to end: %v", jobFilePath, err)
	}

	// Move .json to /in for event trigger
	if err := MoveFileToDir(jobRunFilePath, inFolderPath, utils.MoveFileWithLock); err != nil {
		return fmt.Errorf("failed to write the jobresult data: %v", err)
	}

	return nil
}
