package common

import (
	"fmt"
	"net"
	"net/http"
	"reflect"
	"time"

	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
)

const (
	tplNetworkFailureRetry = "[WARN] temporary network failure, retrying (%d/%d) in %f seconds: %s"
)

func (c *Client) sendWithNetworkFailureRetry(req *http.Request, retryable bool) (resp *http.Response, err error) {
	// make sure maxRetries is more than or equal 0
	var maxRetries int
	if retryable {
		maxRetries = maxInt(c.profile.NetworkFailureMaxRetries, 0)
	}
	durationFunc := safeDurationFunc(c.profile.NetworkFailureRetryDuration)
	retryer := newRequestRetryer(req)

	for idx := 0; idx <= maxRetries; idx++ {

		if idx > 0 {
			err = retryer()
			if err != nil {
				return nil, err
			}
		}

		resp, err = c.sendHttp(req)

		// retry when error occurred and retryable and not the last retry
		// should not sleep on last retry even if it's retryable
		if err != nil && retryable && idx < maxRetries {
			if err, ok := err.(net.Error); ok && (err.Timeout() || err.Temporary()) {
				duration := durationFunc(idx)
				if c.debug {
					c.logger.Printf(tplNetworkFailureRetry, idx, maxRetries, duration.Seconds(), err.Error())
				}

				time.Sleep(duration)
				continue
			}
		}

		if err != nil {
			msg := fmt.Sprintf("Fail to get response because %s", err)
			err = errors.NewTencentCloudSDKError("ClientError.NetworkError", msg, "")
		}

		return resp, err
	}

	return resp, err
}

func maxInt(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func safeDurationFunc(durationFunc profile.DurationFunc) profile.DurationFunc {
	if durationFunc != nil {
		return durationFunc
	}
	return profile.ExponentialBackoff
}

// isRetryable means if request is retryable or not,
// depends on if request has a `ClientToken` field or not,
// request with `ClientToken` means it's idempotent and retryable,
// unretryable request SHOULDN'T retry for temporary network failure
func isRetryable(obj interface{}) bool {
	// obj Must be struct ptr
	getType := reflect.TypeOf(obj)
	if getType.Kind() != reflect.Ptr || getType.Elem().Kind() != reflect.Struct {
		return false
	}

	// obj Must exist named field
	_, ok := getType.Elem().FieldByName(fieldClientToken)
	return ok
}
