package cryptorand_test

import (
	"crypto/rand"
	"encoding/binary"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/coder/coder/cryptorand"
)

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

	for i := 0; i < 20; i++ {
		v, err := cryptorand.Int63()
		require.NoError(t, err, "unexpected error from Int63")
		t.Logf("value: %v <- random?", v)
		require.True(t, v >= 0, "values must be positive")
	}
}

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

	for i := 0; i < 20; i++ {
		v, err := cryptorand.Uint64()
		require.NoError(t, err, "unexpected error from Uint64")
		t.Logf("value: %v <- random?", v)
	}
}

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

	for i := 0; i < 20; i++ {
		v, err := cryptorand.Int31()
		require.NoError(t, err, "unexpected error from Int31")
		t.Logf("value: %v <- random?", v)
		require.True(t, v >= 0, "values must be positive")
	}
}

func TestUnbiasedModulo32(t *testing.T) {
	t.Parallel()
	const mod = 7
	dist := [mod]uint32{}

	_, err := cryptorand.UnbiasedModulo32(0, mod)
	require.NoError(t, err)

	for i := 0; i < 1000; i++ {
		b := [4]byte{}
		_, _ = rand.Read(b[:])
		v, err := cryptorand.UnbiasedModulo32(binary.BigEndian.Uint32(b[:]), mod)
		require.NoError(t, err, "unexpected error from UnbiasedModulo32")
		dist[v]++
	}

	t.Logf("dist: %+v <- evenly distributed?", dist)
}

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

	for i := 0; i < 20; i++ {
		v, err := cryptorand.Uint32()
		require.NoError(t, err, "unexpected error from Uint32")
		t.Logf("value: %v <- random?", v)
	}
}

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

	for i := 0; i < 20; i++ {
		v, err := cryptorand.Int()
		require.NoError(t, err, "unexpected error from Int")
		t.Logf("value: %v <- random?", v)
		require.True(t, v >= 0, "values must be positive")
	}
}

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

	for i := 0; i < 20; i++ {
		v, err := cryptorand.Int63n(1 << 35)
		require.NoError(t, err, "unexpected error from Int63n")
		t.Logf("value: %v <- random?", v)
		require.True(t, v >= 0, "values must be positive")
		require.True(t, v < 1<<35, "values must be less than 1<<35")
	}

	// Expect a panic if max is negative
	require.PanicsWithValue(t, "invalid argument to Int63n", func() {
		cryptorand.Int63n(0)
	})
}

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

	for i := 0; i < 20; i++ {
		v, err := cryptorand.Int31n(100)
		require.NoError(t, err, "unexpected error from Int31n")
		t.Logf("value: %v <- random?", v)
		require.True(t, v >= 0, "values must be positive")
		require.True(t, v < 100, "values must be less than 100")
	}
}

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

	for i := 0; i < 20; i++ {
		v, err := cryptorand.Intn(100)
		require.NoError(t, err, "unexpected error from Intn")
		t.Logf("value: %v <- random?", v)
		require.True(t, v >= 0, "values must be positive")
		require.True(t, v < 100, "values must be less than 100")
	}

	// Ensure Intn works for int larger than 32 bits
	_, err := cryptorand.Intn(1 << 35)
	require.NoError(t, err, "expected Intn to work for 64-bit int")

	// Expect a panic if max is negative
	require.PanicsWithValue(t, "n must be a positive nonzero number", func() {
		cryptorand.Intn(0)
	})
}

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

	for i := 0; i < 20; i++ {
		v, err := cryptorand.Float64()
		require.NoError(t, err, "unexpected error from Float64")
		t.Logf("value: %v <- random?", v)
		require.True(t, v >= 0.0, "values must be positive")
		require.True(t, v < 1.0, "values must be less than 1.0")
	}
}

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

	for i := 0; i < 20; i++ {
		v, err := cryptorand.Float32()
		require.NoError(t, err, "unexpected error from Float32")
		t.Logf("value: %v <- random?", v)
		require.True(t, v >= 0.0, "values must be positive")
		require.True(t, v < 1.0, "values must be less than 1.0")
	}
}

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

	const iterations = 10000
	trueCount := 0

	for i := 0; i < iterations; i++ {
		v, err := cryptorand.Bool()
		require.NoError(t, err, "unexpected error from Bool")
		if v {
			trueCount++
		}
	}

	percentage := (float64(trueCount) / iterations) * 100
	t.Logf("number of true values: %d of %d total (%.2f%%)", trueCount, iterations, percentage)
	require.True(t, percentage > 48, "expected more than 48 percent of values to be true")
	require.True(t, percentage < 52, "expected less than 52 percent of values to be true")
}

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

	for i := 0; i < 20; i++ {
		v, err := cryptorand.Duration()
		require.NoError(t, err, "unexpected error from Duration")
		t.Logf("value: %v <- random?", v)
		require.True(t, v >= 0.0, "values must be positive")
	}
}
