package backoff

import (
	"context"
	"fmt"
	"strings"
	"testing"
	"time"
)

func TestBackoffRetry_Failure(t *testing.T) {
	f := func(backoffFactor float64, backoffRetries int, cancelTimeout time.Duration, retryFunc func() error, resultExpected int) {
		t.Helper()

		r := &Backoff{
			retries:     backoffRetries,
			factor:      backoffFactor,
			minDuration: time.Millisecond * 10,
		}
		ctx := context.Background()
		if cancelTimeout != 0 {
			newCtx, cancelFn := context.WithTimeout(context.Background(), cancelTimeout)
			ctx = newCtx
			defer cancelFn()
		}

		result, err := r.Retry(ctx, retryFunc)
		if err == nil {
			t.Fatalf("expecting non-nil error")
		}
		if result != uint64(resultExpected) {
			t.Fatalf("unexpected result: got %d; want %d", result, resultExpected)
		}
	}

	// return bad request
	retryFunc := func() error {
		return ErrBadRequest
	}
	f(0, 0, 0, retryFunc, 0)

	// empty retries values
	retryFunc = func() error {
		time.Sleep(time.Millisecond * 100)
		return nil
	}
	f(0, 0, 0, retryFunc, 0)

	// all retries failed test
	backoffFactor := 0.1
	backoffRetries := 5
	cancelTimeout := time.Second * 0
	retryFunc = func() error {
		t := time.NewTicker(time.Millisecond * 5)
		defer t.Stop()
		for range t.C {
			return fmt.Errorf("got some error")
		}
		return nil
	}
	resultExpected := 5
	f(backoffFactor, backoffRetries, cancelTimeout, retryFunc, resultExpected)

	// cancel context
	backoffFactor = 1.7
	backoffRetries = 5
	cancelTimeout = time.Millisecond * 40
	retryFunc = func() error {
		return fmt.Errorf("got some error")
	}
	resultExpected = 3
	f(backoffFactor, backoffRetries, cancelTimeout, retryFunc, resultExpected)
}

func TestBackoffRetry_Success(t *testing.T) {
	f := func(retryFunc func() error, resultExpected int) {
		t.Helper()

		r := &Backoff{
			retries:     5,
			factor:      1.7,
			minDuration: time.Millisecond * 10,
		}
		ctx := context.Background()

		result, err := r.Retry(ctx, retryFunc)
		if err != nil {
			t.Fatalf("Retry() error: %s", err)
		}
		if result != uint64(resultExpected) {
			t.Fatalf("unexpected result: got %d; want %d", result, resultExpected)
		}
	}

	// only one retry test
	counter := 0
	retryFunc := func() error {
		t := time.NewTicker(time.Millisecond * 5)
		defer t.Stop()
		for range t.C {
			counter++
			if counter%2 == 0 {
				return fmt.Errorf("got some error")
			}
			if counter%3 == 0 {
				return nil
			}
		}
		return nil
	}
	resultExpected := 1
	f(retryFunc, resultExpected)
}

func TestBackoff_New(t *testing.T) {
	f := func(retries int, factor float64, minDuration time.Duration, errExpected string) {
		t.Helper()

		_, err := New(retries, factor, minDuration)
		if err == nil {
			if errExpected != "" {
				t.Fatalf("expecting non-nil error")
			}
			return
		}
		if !strings.Contains(err.Error(), errExpected) {
			t.Fatalf("unexpected error: got %q; want %q", err.Error(), errExpected)
		}
	}

	// empty retries
	f(0, 1.1, time.Millisecond*10, "retries must be greater than 0")

	// empty factor
	f(1, 0, time.Millisecond*10, "factor must be greater than 1")

	// empty minDuration
	f(1, 1.1, 0, "minimum duration must be greater than 0")

	// no errors
	f(1, 1.1, time.Millisecond*10, "")
}
