package lockfile

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

const (
	lockFileName  = ".ekdns.lock"
	staleTimeout  = 2 * time.Minute
	retryInterval = 200 * time.Millisecond
	maxWait       = 2 * time.Second
)

// Lock represents an acquired lock file that can be released.
type Lock interface {
	Release() error
}

type fileLock struct {
	path string
}

// Release deletes the lock file. It is safe to call more than once.
func (l *fileLock) Release() error {
	err := os.Remove(l.path)
	if err != nil && os.IsNotExist(err) {
		return nil
	}
	return err
}

// Acquire creates a lock file in dir and returns a Lock that can release it.
// It waits up to maxWait for a stale or otherwise held lock to clear.
func Acquire(dir string) (Lock, error) {
	lockPath := filepath.Join(dir, lockFileName)
	deadline := time.Now().Add(maxWait)
	for {
		f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
		if err == nil {
			// Write PID for debugging (not used for staleness detection)
			fmt.Fprintf(f, "%d\n", os.Getpid())
			f.Close()
			return &fileLock{path: lockPath}, nil
		}
		if !os.IsExist(err) {
			return nil, fmt.Errorf("creating lock file %s: %w", lockPath, err)
		}
		// Lock file exists — check staleness
		info, statErr := os.Stat(lockPath)
		if statErr == nil && time.Since(info.ModTime()) > staleTimeout {
			os.Remove(lockPath)
			continue // Retry after breaking stale lock
		}
		if time.Now().After(deadline) {
			return nil, fmt.Errorf("could not acquire lock file %s: another instance may be running", lockPath)
		}
		time.Sleep(retryInterval)
	}
}
