//go:build windows

package profile

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

	"golang.org/x/sys/windows"
)

// FileLock provides cooperative file-based locking for Windows callers.
type FileLock struct {
	path string
	file *os.File
}

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

// AcquireLock creates (or opens) a .lock file in configDir and obtains an
// exclusive file-region lock. 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 := lockFileRegion(f, false); 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 := lockFileRegion(f, true); err != nil {
		_ = f.Close()
		if err == windows.ERROR_LOCK_VIOLATION {
			return nil, fmt.Errorf("%w: %s", ErrLocked, lockPath)
		}
		return nil, fmt.Errorf("failed to acquire lock %s: %w", lockPath, err)
	}

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

// Release releases the file lock and closes the lock file.
func (fl *FileLock) Release() {
	if fl == nil || fl.file == nil {
		return
	}
	_ = unlockFileRegion(fl.file)
	_ = fl.file.Close()
}

func lockFileRegion(file *os.File, nonBlocking bool) error {
	var overlapped windows.Overlapped
	flags := uint32(windows.LOCKFILE_EXCLUSIVE_LOCK)
	if nonBlocking {
		flags |= windows.LOCKFILE_FAIL_IMMEDIATELY
	}

	return windows.LockFileEx(
		windows.Handle(file.Fd()),
		flags,
		0,
		1,
		0,
		&overlapped,
	)
}

func unlockFileRegion(file *os.File) error {
	var overlapped windows.Overlapped
	return windows.UnlockFileEx(
		windows.Handle(file.Fd()),
		0,
		1,
		0,
		&overlapped,
	)
}
