package security

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

func TestFlock_acquireAndRelease(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "test.lock")

	release, err := Flock(path)
	if err != nil {
		t.Fatalf("Flock: %v", err)
	}
	if _, statErr := os.Stat(path); statErr != nil {
		t.Fatalf("lock file not created: %v", statErr)
	}
	release()
}

func TestFlock_reentrantAfterRelease(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "test.lock")

	r1, err := Flock(path)
	if err != nil {
		t.Fatalf("first Flock: %v", err)
	}
	r1()

	r2, err := Flock(path)
	if err != nil {
		t.Fatalf("second Flock after release: %v", err)
	}
	r2()
}

func TestFlock_concurrentBlocks(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "test.lock")

	r1, err := Flock(path)
	if err != nil {
		t.Fatalf("first Flock: %v", err)
	}

	// Give the blocking goroutine a chance to start and block.
	time.Sleep(50 * time.Millisecond)

	// Verify the second lock is blocked by checking it hasn't acquired after a short window.
	// Use a non-blocking attempt: open the file and try LOCK_EX|LOCK_NB.
	blocked := make(chan bool, 1)
	go func() {
		f, err := os.OpenFile(path, os.O_RDWR, 0o600)
		if err != nil {
			blocked <- false
			return
		}
		defer f.Close()
		// LOCK_NB = non-blocking; returns EWOULDBLOCK if locked.
		if err := tryFlockEx(int(f.Fd())); err != nil {
			blocked <- true // got EWOULDBLOCK = the lock is held by r1
			return
		}
		releaseFlock(int(f.Fd()))
		blocked <- false // acquired = r1 didn't hold the lock (bug)
	}()

	select {
	case b := <-blocked:
		if !b {
			t.Fatal("second lock acquired while first holds it — lock not working")
		}
	case <-time.After(2 * time.Second):
		t.Fatal("non-blocking try-lock timed out")
	}

	r1()
}
