package server

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

// errInstanceLocked reports another process holds the serve instance lock.
var errInstanceLocked = errors.New("another serve instance holds the migrations-dir lock")

// LockInstance acquires an exclusive advisory lock on <dir>/serve.lock and
// returns a release func.
//
// serve holds this lock for its whole lifetime: a second serve on the same
// dir refuses to start with errInstanceLocked. The migrations-dir may still
// see out-of-band writers (files copied straight onto the volume). Mkdirs
// dir. The release func closes the file, dropping the lock; the kernel also
// drops it on process exit.
func LockInstance(dir string) (func(), error) {
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return nil, err
	}
	path := filepath.Join(dir, "serve.lock")
	f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o644)
	if err != nil {
		return nil, err
	}
	if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
		f.Close()
		if errors.Is(err, syscall.EWOULDBLOCK) {
			return nil, fmt.Errorf("%w: %s", errInstanceLocked, path)
		}
		return nil, fmt.Errorf("flock %s: %w", path, err)
	}
	release := func() { _ = f.Close() }
	return release, nil
}
