package wlcontext

import (
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"
	"golang.org/x/sys/unix"
)

func newTestSharedContext(t *testing.T, queueSize int) *SharedContext {
	t.Helper()
	fds := make([]int, 2)
	if err := unix.Pipe(fds); err != nil {
		t.Fatalf("failed to create test pipe: %v", err)
	}
	t.Cleanup(func() {
		unix.Close(fds[0])
		unix.Close(fds[1])
	})
	return &SharedContext{
		cmdQueue: make(chan func(), queueSize),
		stopChan: make(chan struct{}),
		wakeR:    fds[0],
		wakeW:    fds[1],
	}
}

func TestSharedContext_ConcurrentPostNonBlocking(t *testing.T) {
	sc := newTestSharedContext(t, 256)

	var wg sync.WaitGroup
	const goroutines = 100
	const iterations = 50

	for i := 0; i < goroutines; i++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			for j := 0; j < iterations; j++ {
				sc.Post(func() {
					_ = id + j
				})
			}
		}(i)
	}

	wg.Wait()
}

func TestSharedContext_PostQueueFull(t *testing.T) {
	sc := newTestSharedContext(t, 2)

	sc.Post(func() {})
	sc.Post(func() {})
	sc.Post(func() {})
	sc.Post(func() {})

	assert.Len(t, sc.cmdQueue, 2)
}

func TestSharedContext_StartMultipleTimes(t *testing.T) {
	sc := newTestSharedContext(t, 256)
	sc.started = true

	var wg sync.WaitGroup
	const goroutines = 10

	for i := 0; i < goroutines; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			sc.Start()
		}()
	}

	wg.Wait()

	assert.True(t, sc.started)
}

func TestSharedContext_DrainCmdQueue(t *testing.T) {
	sc := newTestSharedContext(t, 256)

	counter := 0
	for i := 0; i < 10; i++ {
		sc.cmdQueue <- func() {
			counter++
		}
	}

	sc.drainCmdQueue()

	assert.Equal(t, 10, counter)
	assert.Len(t, sc.cmdQueue, 0)
}

func TestSharedContext_DrainCmdQueueEmpty(t *testing.T) {
	sc := newTestSharedContext(t, 256)

	sc.drainCmdQueue()

	assert.Len(t, sc.cmdQueue, 0)
}

func TestSharedContext_ConcurrentDrainAndPost(t *testing.T) {
	sc := newTestSharedContext(t, 256)

	var wg sync.WaitGroup

	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 0; i < 100; i++ {
			sc.Post(func() {})
		}
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		for i := 0; i < 50; i++ {
			sc.drainCmdQueue()
		}
	}()

	wg.Wait()
}
