//go:build windows

package daemon

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

	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
	"golang.org/x/sys/windows"
)

// 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)
	}
	var overlapped windows.Overlapped
	flags := uint32(windows.LOCKFILE_EXCLUSIVE_LOCK | windows.LOCKFILE_FAIL_IMMEDIATELY)
	if err := windows.LockFileEx(windows.Handle(file.Fd()), flags, 0, 1, 0, &overlapped); err != nil {
		_ = file.Close()
		if errors.Is(err, windows.ERROR_LOCK_VIOLATION) {
			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
	var overlapped windows.Overlapped
	unlockErr := windows.UnlockFileEx(windows.Handle(file.Fd()), 0, 1, 0, &overlapped)
	closeErr := file.Close()
	return errors.Join(unlockErr, closeErr)
}
