package main

import (
	"context"
	"fmt"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"git.d-ma.be/mathias/tapir/internal/runner"
)

// serialize must guarantee at most one discovery pass runs at a time, so a
// connect-triggered pass never fetches concurrently with the scheduler.
func TestSerializeRunsOneAtATime(t *testing.T) {
	var active, maxActive int32
	run := func(_ context.Context, _ string) (runner.Stats, error) {
		n := atomic.AddInt32(&active, 1)
		for { // record the high-water mark of concurrent runs
			m := atomic.LoadInt32(&maxActive)
			if n <= m || atomic.CompareAndSwapInt32(&maxActive, m, n) {
				break
			}
		}
		time.Sleep(2 * time.Millisecond)
		atomic.AddInt32(&active, -1)
		return runner.Stats{}, nil
	}

	s := serialize(&sync.Mutex{}, run)
	var wg sync.WaitGroup
	for i := 0; i < 20; i++ {
		wg.Add(1)
		go func(i int) { defer wg.Done(); _, _ = s(context.Background(), fmt.Sprintf("u%d", i)) }(i)
	}
	wg.Wait()

	require.Equal(t, int32(1), atomic.LoadInt32(&maxActive),
		"serialize must run at most one pass at a time")
}

// Enqueue runs the user's pass out-of-band (non-blocking) on the trigger's ctx.
func TestDiscoveryTriggerEnqueueRunsUser(t *testing.T) {
	done := make(chan string, 1)
	run := func(_ context.Context, userID string) (runner.Stats, error) {
		done <- userID
		return runner.Stats{}, nil
	}
	tr := &discoveryTrigger{ctx: context.Background(), run: run, log: quietLog()}

	tr.Enqueue("u1")

	select {
	case got := <-done:
		require.Equal(t, "u1", got)
	case <-time.After(2 * time.Second):
		t.Fatal("Enqueue did not run the user's pass")
	}
}
