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

import (
	"fmt"
	"jobarranger2/src/libs/golibs/common"
	"jobarranger2/src/libs/golibs/logger/logger"
	"os"
	"syscall"
	"time"
)

const (
	CreateIfNotExist    = true
	OpenOnly            = false
	MoveFileWithLock    = true
	MoveFileWithoutLock = false

	LOCKFILE_EXCLUSIVE_LOCK = syscall.LOCK_EX
	LOCKFILE_SHARE_LOCK     = syscall.LOCK_SH
	LOCKFILE_FAIL_ON_LOCK   = syscall.LOCK_NB
	LOCKFILE_UNLOCK         = syscall.LOCK_UN
)

// File open function with retry if the file is locked by another process
func OpenFileWithLockRetry(path string, retryCount int, createIfNotExist bool) (*os.File, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	return f, nil
}

// File move function with retry if the file is locked by another process
func MoveFileWithLockRetry(src, dst string, retryCount int) error {
	if err := os.Rename(src, dst); err != nil {
		return err
	}
	return nil
}

// Lock the lock file without retry
func TryLockFile(file *os.File, flags int) error {
	if file == nil {
		return fmt.Errorf("nil file passed to TryLockFile")
	}
	return syscall.Flock(int(file.Fd()), flags)
}

// Lock the lock file with retry
func LockFile(flags int) (*os.File, error) {
	funcName := "LockFile"

	for {
		// Open or create the lock file
		file, err := os.OpenFile(common.LockFilePath, os.O_CREATE|os.O_RDWR, 0644)
		if err != nil {
			return nil, err
		}

		// Try to acquire the lock
		err = syscall.Flock(int(file.Fd()), flags)
		if err == nil {
			// Lock acquired successfully
			return file, nil
		}

		// Lock failed → close file before retrying
		file.Close()

		// If file is already locked, wait and retry
		if err == syscall.EWOULDBLOCK {
			if common.Manager.Name == common.AgentManagerProcess || common.Manager.Name == common.JobRunClientProcess {
				logger.WriteLog("JAUTILS400009", funcName, flags)
			} else {
				logger.WriteLog("JAUTILS400009", logger.Logging{}, funcName, flags)
			}

			time.Sleep(1 * time.Second)
			continue
		}

		// Any other error → return immediately
		return nil, err
	}
}

// Releases the lock from lock file
func UnlockFile(file *os.File) error {
	// Release the lock
	if err := syscall.Flock(int(file.Fd()), LOCKFILE_UNLOCK); err != nil {
		return err
	}

	// Close the file descriptor
	return file.Close()
}
