package push

import (
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestCursorGeneratorMonotonicAndConcurrent(t *testing.T) {
	t.Parallel()

	var g cursorGenerator
	const goroutines = 64
	const perGoroutine = 1000
	results := make(chan uint64, goroutines*perGoroutine)
	var wg sync.WaitGroup
	wg.Add(goroutines)
	for range goroutines {
		go func() {
			defer wg.Done()
			for range perGoroutine {
				results <- g.next()
			}
		}()
	}
	wg.Wait()
	close(results)

	seen := make(map[uint64]struct{}, goroutines*perGoroutine)
	var max uint64
	for n := range results {
		_, dup := seen[n]
		require.Falsef(t, dup, "duplicate cursor %d", n)
		seen[n] = struct{}{}
		if n > max {
			max = n
		}
	}
	assert.EqualValues(t, goroutines*perGoroutine, max)
}

func TestFormatAndParseCursor(t *testing.T) {
	t.Parallel()

	cases := []struct {
		in  uint64
		out string
	}{
		{0, "00000000000000000000"},
		{1, "00000000000000000001"},
		{1234567890, "00000000001234567890"},
	}
	for _, tc := range cases {
		s := formatCursor(tc.in)
		assert.Equal(t, tc.out, s)
		assert.Len(t, s, cursorWidth)
		n, ok := parseCursor(s)
		require.True(t, ok)
		assert.Equal(t, tc.in, n)
	}

	n, ok := parseCursor("")
	assert.True(t, ok)
	assert.Zero(t, n)

	n, ok = parseCursor("not-a-number")
	assert.False(t, ok)
	assert.Zero(t, n)
}

func TestFormatCursorLexicographicOrder(t *testing.T) {
	t.Parallel()

	a := formatCursor(9)
	b := formatCursor(10)
	assert.Less(t, a, b, "lexicographic order must match numeric order")
}
