package connector_callout

import (
	"net/http"
	"strconv"
	"strings"
	"time"
)

// defaultRetryableStatuses is used when RetryPolicy.RetryableStatusCodes is nil or empty.
var defaultRetryableStatuses = []int{429, 500, 502, 503, 504}

// absoluteMaxRetryAfter is the hard ceiling applied to any Retry-After value
// returned by parseRetryAfter, regardless of RetryPolicy.MaxDelay. A malicious
// server returning Retry-After: 999999 (≈11 days) would otherwise stall a
// caller using context.Background() for the full duration.
// 5 minutes is generous enough to honor real vendor cooldowns while bounding abuse.
const absoluteMaxRetryAfter = 5 * time.Minute

// RetryPolicy controls how Engine.Do retries failed requests.
type RetryPolicy struct {
	// MaxAttempts is the total number of attempts (1 means no retry).
	// Values < 1 are treated as 1.
	MaxAttempts int

	// BaseDelay is the initial delay between attempts.
	BaseDelay time.Duration

	// Multiplier is the exponential backoff multiplier applied per attempt.
	// Values ≤ 0 are treated as 1 (constant backoff).
	Multiplier float64

	// MaxDelay caps the per-attempt backoff including any Retry-After value.
	// 0 means no explicit policy cap, but parseRetryAfter still clamps
	// Retry-After values to an absolute ceiling of 5 minutes (absoluteMaxRetryAfter).
	MaxDelay time.Duration

	// RetryableStatusCodes is the set of HTTP status codes that trigger a
	// retry. When nil or empty, defaults to {429, 500, 502, 503, 504}.
	RetryableStatusCodes []int
}

// maxAttempts returns at least 1.
func (p *RetryPolicy) maxAttempts() int {
	if p.MaxAttempts < 1 {
		return 1
	}
	return p.MaxAttempts
}

// isRetryableStatus returns true when status is in the configured retryable set.
func isRetryableStatus(policy *RetryPolicy, status int) bool {
	codes := policy.RetryableStatusCodes
	if len(codes) == 0 {
		codes = defaultRetryableStatuses
	}
	for _, c := range codes {
		if c == status {
			return true
		}
	}
	return false
}

// delayForAttempt computes the wait before attempt number attempt (1-based).
// retryAfter, if > 0, is used as a floor (whichever is larger wins), then
// capped by MaxDelay.
func delayForAttempt(policy *RetryPolicy, attempt int, retryAfter time.Duration) time.Duration {
	mult := policy.Multiplier
	if mult <= 0 {
		mult = 1
	}
	base := policy.BaseDelay
	if base <= 0 {
		base = 0
	}

	// Exponential: base * mult^(attempt-1)
	delay := base
	for i := 1; i < attempt; i++ {
		delay = time.Duration(float64(delay) * mult)
	}

	if retryAfter > delay {
		delay = retryAfter
	}
	if policy.MaxDelay > 0 && delay > policy.MaxDelay {
		delay = policy.MaxDelay
	}
	return delay
}

// parseRetryAfter parses the value of the Retry-After HTTP response header.
// Supports both integer-seconds and HTTP-date (RFC 7231) formats.
// Returns 0 for malformed or past-date values.
func parseRetryAfter(header string, now time.Time) time.Duration {
	header = strings.TrimSpace(header)
	if header == "" {
		return 0
	}

	// Try integer seconds first.
	if secs, err := strconv.ParseInt(header, 10, 64); err == nil {
		if secs <= 0 {
			return 0
		}
		d := time.Duration(secs) * time.Second
		if d > absoluteMaxRetryAfter {
			return absoluteMaxRetryAfter
		}
		return d
	}

	// Try HTTP-date format (RFC 7231 / RFC 1123).
	for _, layout := range []string{
		http.TimeFormat,
		"Monday, 02-Jan-06 15:04:05 MST", // RFC 850 (legacy)
		"Mon Jan _2 15:04:05 2006",       // ANSI C
	} {
		if t, err := time.Parse(layout, header); err == nil {
			d := t.Sub(now)
			if d <= 0 {
				return 0
			}
			if d > absoluteMaxRetryAfter {
				return absoluteMaxRetryAfter
			}
			return d
		}
	}

	return 0
}
