//go:build !windows

package daemon

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

	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
)

// ErrInstanceLocked indicates that another daemon instance holds the lock.
var ErrInstanceLocked = errors.New("daemon instance is already locked")

// InstanceLock is a process-lifetime exclusive daemon lock.
type InstanceLock struct {
	mu   sync.Mutex
	file *os.File
}

// AcquireInstanceLock attempts to acquire the daemon instance lock without
// waiting.
func AcquireInstanceLock() (*InstanceLock, error) {
	runtimeDir, err := profile.EnsureRuntimeDir()
	if err != nil {
		return nil, fmt.Errorf("daemon: ensure runtime directory: %w", err)
	}
	lockPath := filepath.Join(runtimeDir, "daemon.lock")
	file, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0o600)
	if err != nil {
		return nil, fmt.Errorf("daemon: open instance lock %s: %w", lockPath, err)
	}
	if err := profile.SecureRuntimeFile(lockPath); err != nil {
		_ = file.Close()
		return nil, fmt.Errorf("daemon: secure instance lock %s: %w", lockPath, err)
	}
	if err := syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
		_ = file.Close()
		if errors.Is(err, syscall.EWOULDBLOCK) || errors.Is(err, syscall.EAGAIN) {
			return nil, fmt.Errorf("%w: %s", ErrInstanceLocked, lockPath)
		}
		return nil, fmt.Errorf("daemon: acquire instance lock %s: %w", lockPath, err)
	}
	return &InstanceLock{file: file}, nil
}

// Release unlocks and closes the instance lock. It is safe on nil receivers
// and across repeated calls.
func (l *InstanceLock) Release() error {
	if l == nil {
		return nil
	}
	l.mu.Lock()
	defer l.mu.Unlock()
	if l.file == nil {
		return nil
	}
	file := l.file
	l.file = nil
	unlockErr := syscall.Flock(int(file.Fd()), syscall.LOCK_UN)
	closeErr := file.Close()
	return errors.Join(unlockErr, closeErr)
}
