package remotewrite

import (
	"math"
	"net/http"
	"testing"
	"time"

	"github.com/VictoriaMetrics/VictoriaMetrics/lib/encoding"
	"github.com/golang/snappy"
)

func TestCalculateRetryDuration(t *testing.T) {
	// `testFunc` call `calculateRetryDuration` for `n` times
	// and evaluate if the result of `calculateRetryDuration` is
	// 1. >= expectMinDuration
	// 2. <= expectMinDuration + 10% (see timeutil.AddJitterToDuration)
	f := func(retryAfterDuration, retryDuration time.Duration, n int, expectMinDuration time.Duration) {
		t.Helper()

		for i := 0; i < n; i++ {
			retryDuration = getRetryDuration(retryAfterDuration, retryDuration, time.Minute)
		}

		expectMaxDuration := helper(expectMinDuration)
		expectMinDuration = expectMinDuration - (1000 * time.Millisecond) // Avoid edge case when calculating time.Until(now)

		if retryDuration < expectMinDuration || retryDuration > expectMaxDuration {
			t.Fatalf(
				"incorrect retry duration, want (ms): [%d, %d], got (ms): %d",
				expectMinDuration.Milliseconds(), expectMaxDuration.Milliseconds(),
				retryDuration.Milliseconds(),
			)
		}
	}

	// Call calculateRetryDuration for 1 time.
	{
		// default backoff policy
		f(0, time.Second, 1, 2*time.Second)
		// default backoff policy exceed max limit"
		f(0, 10*time.Minute, 1, time.Minute)

		// retry after > default backoff policy
		f(10*time.Second, 1*time.Second, 1, 10*time.Second)
		// retry after < default backoff policy
		f(1*time.Second, 10*time.Second, 1, 1*time.Second)
		// retry after invalid and < default backoff policy
		f(0, time.Second, 1, 2*time.Second)

	}

	// Call calculateRetryDuration for multiple times.
	{
		// default backoff policy 2 times
		f(0, time.Second, 2, 4*time.Second)
		// default backoff policy 3 times
		f(0, time.Second, 3, 8*time.Second)
		// default backoff policy N times exceed max limit
		f(0, time.Second, 10, time.Minute)

		// retry after 120s 1 times
		f(120*time.Second, time.Second, 1, 120*time.Second)
		// retry after 120s 2 times
		f(120*time.Second, time.Second, 2, 120*time.Second)
	}
}

func TestParseRetryAfterHeader(t *testing.T) {
	f := func(retryAfterString string, expectResult time.Duration) {
		t.Helper()

		result := parseRetryAfterHeader(retryAfterString)
		// expect `expectResult == result` when retryAfterString is in seconds or invalid
		// expect the difference between result and expectResult to be lower than 10%
		if !(expectResult == result || math.Abs(float64(expectResult-result))/float64(expectResult) < 0.10) {
			t.Fatalf(
				"incorrect retry after duration, want (ms): %d, got (ms): %d",
				expectResult.Milliseconds(), result.Milliseconds(),
			)
		}
	}

	// retry after header in seconds
	f("10", 10*time.Second)
	// retry after header in date time
	f(time.Now().Add(30*time.Second).UTC().Format(http.TimeFormat), 30*time.Second)
	// retry after header invalid
	f("invalid-retry-after", 0)
	// retry after header not in GMT
	f(time.Now().Add(10*time.Second).Format("Mon, 02 Jan 2006 15:04:05 FAKETZ"), 0)
}

// helper calculate the max possible time duration calculated by timeutil.AddJitterToDuration.
func helper(d time.Duration) time.Duration {
	dv := d / 10
	if dv > 10*time.Second {
		dv = 10 * time.Second
	}

	return d + dv
}

func TestRepackBlockFromZstdToSnappy(t *testing.T) {
	expectedPlainBlock := []byte(`foobar`)

	zstdBlock := encoding.CompressZSTDLevel(nil, expectedPlainBlock, 1)
	snappyBlock, err := repackBlockFromZstdToSnappy(zstdBlock)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	actualPlainBlock, err := snappy.Decode(nil, snappyBlock)
	if err != nil {
		t.Fatalf("unexpected error: %s", err)
	}

	if string(actualPlainBlock) != string(expectedPlainBlock) {
		t.Fatalf("unexpected plain block; got %q; want %q", actualPlainBlock, expectedPlainBlock)
	}
}

func TestRepackBlockFromZstdToSnappyInvalidBlock(t *testing.T) {
	snappyBlock, err := repackBlockFromZstdToSnappy([]byte("invalid zstd block"))

	if err == nil {
		t.Fatalf("expected error for invalid zstd block; got nil")
	}
	if len(snappyBlock) != 0 {
		t.Fatalf("expected empty snappy block; got %d bytes", len(snappyBlock))
	}
}
