//go:build !windows

package profile

import (
	"fmt"
	"os"
	"path/filepath"
	"syscall"
)

// FileLock provides cooperative file-based locking using POSIX flock(2).
// It is used to prevent concurrent CLI and daemon processes from corrupting
// shared configuration YAML files.
type FileLock struct {
	path string
	file *os.File
}

// AcquireLock creates (or opens) a .lock file in configDir and obtains an
// exclusive flock. If another process holds the lock, this call blocks until
// it becomes available.
func AcquireLock(configDir string) (*FileLock, error) {
	lockPath := filepath.Join(configDir, ".lock")

	f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600)
	if err != nil {
		return nil, fmt.Errorf("failed to open lock file %s: %w", lockPath, err)
	}

	if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil {
		f.Close()
		return nil, fmt.Errorf("failed to acquire lock %s: %w", lockPath, err)
	}

	return &FileLock{path: lockPath, file: f}, nil
}

// TryAcquireLock attempts to acquire the lock without blocking.
// Returns ErrLocked if the lock is held by another process.
func TryAcquireLock(configDir string) (*FileLock, error) {
	lockPath := filepath.Join(configDir, ".lock")

	f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0600)
	if err != nil {
		return nil, fmt.Errorf("failed to open lock file %s: %w", lockPath, err)
	}

	if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
		f.Close()
		return nil, fmt.Errorf("%w: %s", ErrLocked, lockPath)
	}

	return &FileLock{path: lockPath, file: f}, nil
}

// Release releases the flock and closes the lock file.
func (fl *FileLock) Release() {
	if fl == nil || fl.file == nil {
		return
	}
	_ = syscall.Flock(int(fl.file.Fd()), syscall.LOCK_UN)
	_ = fl.file.Close()
}

// ErrLocked is returned by TryAcquireLock when the lock is held by another process.
var ErrLocked = fmt.Errorf("lock is held by another process")
