// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

package requestconfig

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"math/rand"
	"mime"
	"net/http"
	"net/url"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/openai/openai-go/v3/internal"
	"github.com/openai/openai-go/v3/internal/apierror"
	"github.com/openai/openai-go/v3/internal/apiform"
	"github.com/openai/openai-go/v3/internal/apiquery"
	"github.com/tidwall/gjson"
)

func getDefaultHeaders() map[string]string {
	return map[string]string{
		"User-Agent": fmt.Sprintf("OpenAI/Go %s", internal.PackageVersion),
	}
}

func encodePathParam(value string) string {
	switch value {
	case ".":
		return "%2E"
	case "..":
		return "%2E%2E"
	}
	return url.PathEscape(value)
}

// FormatPath escapes path parameters and inserts them into a request path.
func FormatPath(format string, params ...string) string {
	args := make([]any, len(params))
	for i, param := range params {
		args[i] = encodePathParam(param)
	}
	return fmt.Sprintf(format, args...)
}

func getNormalizedOS() string {
	switch runtime.GOOS {
	case "ios":
		return "iOS"
	case "android":
		return "Android"
	case "darwin":
		return "MacOS"
	case "window":
		return "Windows"
	case "freebsd":
		return "FreeBSD"
	case "openbsd":
		return "OpenBSD"
	case "linux":
		return "Linux"
	default:
		return fmt.Sprintf("Other:%s", runtime.GOOS)
	}
}

func getNormalizedArchitecture() string {
	switch runtime.GOARCH {
	case "386":
		return "x32"
	case "amd64":
		return "x64"
	case "arm":
		return "arm"
	case "arm64":
		return "arm64"
	default:
		return fmt.Sprintf("other:%s", runtime.GOARCH)
	}
}

func getPlatformProperties() map[string]string {
	return map[string]string{
		"X-Stainless-Lang":            "go",
		"X-Stainless-Package-Version": internal.PackageVersion,
		"X-Stainless-OS":              getNormalizedOS(),
		"X-Stainless-Arch":            getNormalizedArchitecture(),
		"X-Stainless-Runtime":         "go",
		"X-Stainless-Runtime-Version": runtime.Version(),
	}
}

type RequestOption interface {
	Apply(*RequestConfig) error
}

type RequestOptionFunc func(*RequestConfig) error
type PreRequestOptionFunc func(*RequestConfig) error

// requestFinalizer is an internal extension point for provider integrations
// that must inspect the fully configured request and install middleware after
// all client- and method-level options have been applied.
type requestFinalizer func(*RequestConfig) error

type requestFinalizerOption struct {
	finalize func(*RequestConfig) error
}

// noRetryError marks deterministic request setup or policy failures that
// cannot be fixed by replaying the same request. It deliberately remains an
// internal implementation detail so public error strings and wrapping stay
// unchanged.
type noRetryError struct {
	err error
}

func (e *noRetryError) Error() string { return e.err.Error() }
func (e *noRetryError) Unwrap() error { return e.err }
func (e *noRetryError) noRetry()      {}

// WithNoRetryError marks err as deterministic for the generic request retry
// loop while preserving its message and unwrap chain. This function is
// internal API and may change without notice.
func WithNoRetryError(err error) error {
	if err == nil {
		return nil
	}
	return &noRetryError{err: err}
}

func (o requestFinalizerOption) Apply(cfg *RequestConfig) error {
	cfg.finalizers = append(cfg.finalizers, o.finalize)
	return nil
}

// WithRequestFinalizer registers an internal provider finalizer. Finalizers run
// after every RequestOption has been applied and before request security is
// selected. This keeps provider authentication closest to the wire, including
// when callers add method-level middleware.
//
// This function is internal API and may change without notice.
func WithRequestFinalizer(finalize func(*RequestConfig) error) RequestOption {
	return requestFinalizerOption{finalize: finalize}
}

type environmentDefaultsDisabledOption struct{}

func (environmentDefaultsDisabledOption) Apply(*RequestConfig) error {
	return nil
}

// WithEnvironmentDefaultsDisabled marks a provider-owned client that must not
// inherit ambient OPENAI_* configuration. The regular transport defaults still
// apply.
//
// This function is internal API and may change without notice.
func WithEnvironmentDefaultsDisabled() RequestOption {
	return environmentDefaultsDisabledOption{}
}

// EnvironmentDefaultsDisabled reports whether opts contains the internal
// provider marker returned by WithEnvironmentDefaultsDisabled.
func EnvironmentDefaultsDisabled(opts ...RequestOption) bool {
	for _, opt := range opts {
		if _, ok := opt.(environmentDefaultsDisabledOption); ok {
			return true
		}
	}
	return false
}

func (s RequestOptionFunc) Apply(r *RequestConfig) error    { return s(r) }
func (s PreRequestOptionFunc) Apply(r *RequestConfig) error { return s(r) }

func NewRequestConfig(ctx context.Context, method string, u string, body any, dst any, opts ...RequestOption) (*RequestConfig, error) {
	var reader io.Reader

	contentType := "application/json"
	hasSerializationFunc := false

	if body, ok := body.(json.Marshaler); ok {
		content, err := body.MarshalJSON()
		if err != nil {
			return nil, err
		}
		reader = bytes.NewBuffer(content)
		hasSerializationFunc = true
	}
	if body, ok := body.(apiform.Marshaler); ok {
		var (
			content []byte
			err     error
		)
		content, contentType, err = body.MarshalMultipart()
		if err != nil {
			return nil, err
		}
		reader = bytes.NewBuffer(content)
		hasSerializationFunc = true
	}
	if body, ok := body.(apiquery.Queryer); ok {
		hasSerializationFunc = true
		q, err := body.URLQuery()
		if err != nil {
			return nil, err
		}
		params := q.Encode()
		if params != "" {
			parsed, _ := url.Parse(u)
			if parsed.RawQuery != "" {
				parsed.RawQuery = parsed.RawQuery + "&" + params
				u = parsed.String()
			} else {
				u = u + "?" + params
			}
		}
	}
	if body, ok := body.([]byte); ok {
		reader = bytes.NewBuffer(body)
		hasSerializationFunc = true
	}
	if body, ok := body.(io.Reader); ok {
		reader = body
		hasSerializationFunc = true
	}

	// Fallback to json serialization if none of the serialization functions that we expect
	// to see is present.
	if body != nil && !hasSerializationFunc {
		buf := new(bytes.Buffer)
		enc := json.NewEncoder(buf)
		enc.SetEscapeHTML(false)
		if err := enc.Encode(body); err != nil {
			return nil, err
		}
		reader = buf
	}

	req, err := http.NewRequestWithContext(ctx, method, u, nil)
	if err != nil {
		return nil, err
	}
	if reader != nil {
		req.Header.Set("Content-Type", contentType)
	}

	req.Header.Set("Accept", "application/json")
	req.Header.Set("X-Stainless-Retry-Count", "0")
	req.Header.Set("X-Stainless-Timeout", "0")
	for k, v := range getDefaultHeaders() {
		req.Header.Add(k, v)
	}

	for k, v := range getPlatformProperties() {
		req.Header.Add(k, v)
	}
	cfg := RequestConfig{
		MaxRetries: 2,
		Context:    ctx,
		Request:    req,
		HTTPClient: http.DefaultClient,
		Body:       reader,
	}
	cfg.ResponseBodyInto = dst
	cfg.Security = Security{
		BearerAuth:      true,
		AdminAPIKeyAuth: true,
	}
	err = cfg.Apply(opts...)
	if err != nil {
		return nil, err
	}

	// Provider finalizers intentionally run after every regular option so they
	// can sign the final URL, headers, and serialized body on each attempt.
	for _, finalizer := range cfg.finalizers {
		if err := finalizer(&cfg); err != nil {
			return nil, err
		}
	}

	// This must run after `cfg.Apply(...)` above so we know which specific security scheme to add
	ApplySecurity(cfg)

	// This must run after `cfg.Apply(...)` above in case the request timeout gets modified. We also only
	// apply our own logic for it if it's still "0" from above. If it's not, then it was deleted or modified
	// by the user and we should respect that.
	if req.Header.Get("X-Stainless-Timeout") == "0" {
		if cfg.RequestTimeout == time.Duration(0) {
			req.Header.Del("X-Stainless-Timeout")
		} else {
			req.Header.Set("X-Stainless-Timeout", strconv.Itoa(int(cfg.RequestTimeout.Seconds())))
		}
	}

	return &cfg, nil
}

// This interface is primarily used to describe an [*http.Client], but also
// supports custom HTTP implementations.
type HTTPDoer interface {
	Do(req *http.Request) (*http.Response, error)
}

// RequestConfig represents all the state related to one request.
//
// Editing the variables inside RequestConfig directly is unstable api. Prefer
// composing the RequestOption instead if possible.
type RequestConfig struct {
	MaxRetries     int
	RequestTimeout time.Duration
	Context        context.Context
	Request        *http.Request
	BaseURL        *url.URL
	// DefaultBaseURL will be used if BaseURL is not explicitly overridden using
	// WithBaseURL.
	DefaultBaseURL     *url.URL
	CustomHTTPDoer     HTTPDoer
	HTTPClient         *http.Client
	Middlewares        []middleware
	APIKey             string
	AdminAPIKey        string
	Organization       string
	Project            string
	WebhookSecret      string
	finalizers         []requestFinalizer
	authHeaderOverride bool
	authPreference     authCredentialPreference
	// Configure which security scheme(s) should be enabled for this request
	Security Security
	// If ResponseBodyInto not nil, then we will attempt to deserialize into
	// ResponseBodyInto. If Destination is a []byte, then it will return the body as
	// is.
	ResponseBodyInto any
	// ResponseInto copies the \*http.Response of the corresponding request into the
	// given address
	ResponseInto **http.Response
	Body         io.Reader
}

// middleware is exactly the same type as the Middleware type found in the [option] package,
// but it is redeclared here for circular dependency issues.
type middleware = func(*http.Request, middlewareNext) (*http.Response, error)

// middlewareNext is exactly the same type as the MiddlewareNext type found in the [option] package,
// but it is redeclared here for circular dependency issues.
type middlewareNext = func(*http.Request) (*http.Response, error)

func applyMiddleware(middleware middleware, next middlewareNext) middlewareNext {
	return func(req *http.Request) (res *http.Response, err error) {
		return middleware(req, next)
	}
}

func shouldRetry(req *http.Request, res *http.Response, err error) bool {
	// If there is no way to recover the Body, then we shouldn't retry.
	if req.Body != nil && req.GetBody == nil {
		return false
	}

	var deterministic interface{ noRetry() }
	if errors.As(err, &deterministic) {
		return false
	}

	// If there is no response, that indicates that there is a connection error
	// so we retry the request.
	if res == nil {
		return true
	}

	// If the header explicitly wants a retry behavior, respect that over the
	// http status code.
	if res.Header.Get("x-should-retry") == "true" {
		return true
	}
	if res.Header.Get("x-should-retry") == "false" {
		return false
	}

	return res.StatusCode == http.StatusRequestTimeout ||
		res.StatusCode == http.StatusConflict ||
		res.StatusCode == http.StatusTooManyRequests ||
		res.StatusCode >= http.StatusInternalServerError
}

func parseRetryAfterHeader(resp *http.Response) (time.Duration, bool) {
	if resp == nil {
		return 0, false
	}

	type retryData struct {
		header string
		units  time.Duration

		// custom is used when the regular algorithm failed and is optional.
		// the returned duration is used verbatim (units is not applied).
		custom func(string) (time.Duration, bool)
	}

	nop := func(string) (time.Duration, bool) { return 0, false }

	// the headers are listed in order of preference
	retries := []retryData{
		{
			header: "Retry-After-Ms",
			units:  time.Millisecond,
			custom: nop,
		},
		{
			header: "Retry-After",
			units:  time.Second,

			// retry-after values are expressed in either number of
			// seconds or an HTTP-date indicating when to try again
			custom: func(ra string) (time.Duration, bool) {
				t, err := time.Parse(time.RFC1123, ra)
				if err != nil {
					return 0, false
				}
				return time.Until(t), true
			},
		},
	}

	for _, retry := range retries {
		v := resp.Header.Get(retry.header)
		if v == "" {
			continue
		}
		if retryAfter, err := strconv.ParseFloat(v, 64); err == nil {
			return time.Duration(retryAfter * float64(retry.units)), true
		}
		if d, ok := retry.custom(v); ok {
			return d, true
		}
	}

	return 0, false
}

// isBeforeContextDeadline reports whether the non-zero Time t is
// before ctx's deadline. If ctx does not have a deadline, it
// always reports true (the deadline is considered infinite).
func isBeforeContextDeadline(t time.Time, ctx context.Context) bool {
	d, ok := ctx.Deadline()
	if !ok {
		return true
	}
	return t.Before(d)
}

// bodyWithTimeout is an io.ReadCloser which can observe a context's cancel func
// to handle timeouts etc. It wraps an existing io.ReadCloser.
type bodyWithTimeout struct {
	stop func() // stops the time.Timer waiting to cancel the request
	rc   io.ReadCloser
}

func (b *bodyWithTimeout) Read(p []byte) (n int, err error) {
	n, err = b.rc.Read(p)
	if err == nil {
		return n, nil
	}
	if err == io.EOF {
		return n, err
	}
	return n, err
}

func (b *bodyWithTimeout) Close() error {
	err := b.rc.Close()
	b.stop()
	return err
}

// closeOnceReadCloser lets Execute clean up bodies when middleware returns an
// error before reaching the transport, without double-closing bodies that a
// transport has already closed.
type closeOnceReadCloser struct {
	io.ReadCloser
	once sync.Once
	err  error
}

func (b *closeOnceReadCloser) Close() error {
	b.once.Do(func() {
		b.err = b.ReadCloser.Close()
	})
	return b.err
}

func retryDelay(res *http.Response, retryCount int) time.Duration {
	// If the backend tells us to wait a certain amount of time, use that value
	if retryAfterDelay, ok := parseRetryAfterHeader(res); ok {
		return max(0, retryAfterDelay)
	}

	maxDelay := 8 * time.Second
	delay := time.Duration(0.5 * float64(time.Second) * math.Pow(2, float64(retryCount)))
	if delay > maxDelay {
		delay = maxDelay
	}

	jitter := rand.Int63n(int64(delay / 4))
	delay -= time.Duration(jitter)
	return delay
}

func (cfg *RequestConfig) Execute() (err error) {
	if cfg.BaseURL == nil {
		if cfg.DefaultBaseURL != nil {
			cfg.BaseURL = cfg.DefaultBaseURL
		} else {
			return fmt.Errorf("requestconfig: base url is not set")
		}
	}

	cfg.Request.URL, err = cfg.BaseURL.Parse(strings.TrimLeft(cfg.Request.URL.String(), "/"))
	if err != nil {
		return err
	}

	if cfg.Body != nil && cfg.Request.Body == nil {
		switch body := cfg.Body.(type) {
		case *bytes.Buffer:
			b := body.Bytes()
			cfg.Request.ContentLength = int64(body.Len())
			cfg.Request.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil }
			cfg.Request.Body, _ = cfg.Request.GetBody()
		case *bytes.Reader:
			cfg.Request.ContentLength = int64(body.Len())
			cfg.Request.GetBody = func() (io.ReadCloser, error) {
				_, err := body.Seek(0, 0)
				return io.NopCloser(body), err
			}
			cfg.Request.Body, _ = cfg.Request.GetBody()
		default:
			if rc, ok := body.(io.ReadCloser); ok {
				cfg.Request.Body = rc
			} else {
				cfg.Request.Body = io.NopCloser(body)
			}
		}
	}

	handler := cfg.HTTPClient.Do
	if cfg.CustomHTTPDoer != nil {
		handler = cfg.CustomHTTPDoer.Do
	}
	for i := len(cfg.Middlewares) - 1; i >= 0; i -= 1 {
		handler = applyMiddleware(cfg.Middlewares[i], handler)
	}

	// Don't send the current retry count in the headers if the caller modified the header defaults.
	shouldSendRetryCount := cfg.Request.Header.Get("X-Stainless-Retry-Count") == "0"

	var res *http.Response
	var cancel context.CancelFunc
	for retryCount := 0; retryCount <= cfg.MaxRetries; retryCount += 1 {
		ctx := cfg.Request.Context()
		if cfg.RequestTimeout != time.Duration(0) && isBeforeContextDeadline(time.Now().Add(cfg.RequestTimeout), ctx) {
			ctx, cancel = context.WithTimeout(ctx, cfg.RequestTimeout)
			defer func() {
				// The cancel function is nil if it was handed off to be handled in a different scope.
				if cancel != nil {
					cancel()
				}
			}()
		}

		req := cfg.Request.Clone(ctx)
		if req.Body != nil {
			req.Body = &closeOnceReadCloser{ReadCloser: req.Body}
		}
		if shouldSendRetryCount {
			req.Header.Set("X-Stainless-Retry-Count", strconv.Itoa(retryCount))
		}

		res, err = handler(req)
		if err != nil && req.Body != nil {
			_ = req.Body.Close()
		}
		if ctx != nil && ctx.Err() != nil {
			return ctx.Err()
		}
		if !shouldRetry(cfg.Request, res, err) || retryCount >= cfg.MaxRetries {
			break
		}

		// Prepare next request and wait for the retry delay
		if cfg.Request.GetBody != nil {
			cfg.Request.Body, err = cfg.Request.GetBody()
			if err != nil {
				return err
			}
		}

		// Can't actually refresh the body, so we don't attempt to retry here
		if cfg.Request.GetBody == nil && cfg.Request.Body != nil {
			break
		}

		// Close the response body before retrying to prevent connection leaks
		if res != nil && res.Body != nil {
			_ = res.Body.Close()
		}

		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-time.After(retryDelay(res, retryCount)):
		}
	}

	// Save *http.Response if it is requested to, even if there was an error making the request. This is
	// useful in cases where you might want to debug by inspecting the response. Note that if err != nil,
	// the response should be generally be empty, but there are edge cases.
	if cfg.ResponseInto != nil {
		*cfg.ResponseInto = res
	}
	if responseBodyInto, ok := cfg.ResponseBodyInto.(**http.Response); ok {
		*responseBodyInto = res
	}

	// If there was a connection error in the final request or any other transport error,
	// return that early without trying to coerce into an APIError.
	if err != nil {
		return err
	}

	if res.StatusCode >= 400 {
		contents, err := io.ReadAll(res.Body)
		_ = res.Body.Close()
		if err != nil {
			return err
		}

		// If there is an APIError, re-populate the response body so that debugging
		// utilities can conveniently dump the response without issue.
		res.Body = io.NopCloser(bytes.NewBuffer(contents))

		// Load the contents into the error format if it is provided.
		aerr := apierror.Error{Request: cfg.Request, Response: res, StatusCode: res.StatusCode}
		unwrapped := gjson.GetBytes(contents, "error").Raw
		err = aerr.UnmarshalJSON([]byte(unwrapped))
		if err != nil {
			return err
		}
		return &aerr
	}

	_, intoCustomResponseBody := cfg.ResponseBodyInto.(**http.Response)
	if cfg.ResponseBodyInto == nil || intoCustomResponseBody {
		// We aren't reading the response body in this scope, but whoever is will need the
		// cancel func from the context to observe request timeouts.
		// Put the cancel function in the response body so it can be handled elsewhere.
		if cancel != nil {
			res.Body = &bodyWithTimeout{rc: res.Body, stop: cancel}
			cancel = nil
		}
		return nil
	}

	contents, err := io.ReadAll(res.Body)
	_ = res.Body.Close()
	if err != nil {
		return fmt.Errorf("error reading response body: %w", err)
	}

	// If we are not json, return plaintext
	contentType := res.Header.Get("content-type")
	mediaType, _, _ := mime.ParseMediaType(contentType)
	isJSON := strings.Contains(mediaType, "application/json") || strings.HasSuffix(mediaType, "+json")
	if !isJSON {
		switch dst := cfg.ResponseBodyInto.(type) {
		case *string:
			*dst = string(contents)
		case **string:
			tmp := string(contents)
			*dst = &tmp
		case *[]byte:
			*dst = contents
		default:
			return fmt.Errorf("expected destination type of 'string' or '[]byte' for responses with content-type '%s' that is not 'application/json'", contentType)
		}
		return nil
	}

	switch dst := cfg.ResponseBodyInto.(type) {
	// If the response happens to be a byte array, deserialize the body as-is.
	case *[]byte:
		*dst = contents
	default:
		err = json.NewDecoder(bytes.NewReader(contents)).Decode(cfg.ResponseBodyInto)
		if err != nil {
			return fmt.Errorf("error parsing response json: %w", err)
		}
	}

	return nil
}

func ExecuteNewRequest(ctx context.Context, method string, u string, body any, dst any, opts ...RequestOption) error {
	cfg, err := NewRequestConfig(ctx, method, u, body, dst, opts...)
	if err != nil {
		return err
	}
	return cfg.Execute()
}

func (cfg *RequestConfig) Clone(ctx context.Context) *RequestConfig {
	if cfg == nil {
		return nil
	}
	req := cfg.Request.Clone(ctx)
	var err error
	if req.Body != nil {
		req.Body, err = req.GetBody()
	}
	if err != nil {
		return nil
	}
	new := &RequestConfig{
		MaxRetries:         cfg.MaxRetries,
		RequestTimeout:     cfg.RequestTimeout,
		Context:            ctx,
		Request:            req,
		BaseURL:            cfg.BaseURL,
		HTTPClient:         cfg.HTTPClient,
		Middlewares:        cfg.Middlewares,
		APIKey:             cfg.APIKey,
		AdminAPIKey:        cfg.AdminAPIKey,
		Organization:       cfg.Organization,
		Project:            cfg.Project,
		WebhookSecret:      cfg.WebhookSecret,
		finalizers:         append([]requestFinalizer(nil), cfg.finalizers...),
		authHeaderOverride: cfg.authHeaderOverride,
		authPreference:     cfg.authPreference,
	}

	return new
}

func (cfg *RequestConfig) SetHeader(key, value string) {
	cfg.Request.Header.Set(key, value)
	if strings.EqualFold(key, "Authorization") {
		cfg.authHeaderOverride = true
	}
}

func (cfg *RequestConfig) AddHeader(key, value string) {
	cfg.Request.Header.Add(key, value)
	if strings.EqualFold(key, "Authorization") {
		cfg.authHeaderOverride = true
	}
}

func (cfg *RequestConfig) DelHeader(key string) {
	cfg.Request.Header.Del(key)
	if strings.EqualFold(key, "Authorization") {
		cfg.authHeaderOverride = true
	}
}

func (cfg *RequestConfig) SetAPIKey(value string) {
	cfg.APIKey = value
	cfg.authHeaderOverride = false
	cfg.authPreference = authCredentialPreferenceBearer
}

func (cfg *RequestConfig) SetAdminAPIKey(value string) {
	cfg.AdminAPIKey = value
	cfg.authHeaderOverride = false
	cfg.authPreference = authCredentialPreferenceAdmin
}

func (cfg *RequestConfig) Apply(opts ...RequestOption) error {
	for _, opt := range opts {
		err := opt.Apply(cfg)
		if err != nil {
			return err
		}
	}
	return nil
}

// PreRequestOptions is used to collect all the options which need to be known before
// a call to [RequestConfig.ExecuteNewRequest], such as path parameters
// or global defaults.
// PreRequestOptions will return a [RequestConfig] with the options applied.
//
// Only request option functions of type [PreRequestOptionFunc] are applied.
func PreRequestOptions(opts ...RequestOption) (RequestConfig, error) {
	cfg := RequestConfig{}
	for _, opt := range opts {
		if opt, ok := opt.(PreRequestOptionFunc); ok {
			err := opt.Apply(&cfg)
			if err != nil {
				return cfg, err
			}
		}
	}
	return cfg, nil
}

// WithDefaultBaseURL returns a RequestOption that sets the client's default Base URL.
// This is always overridden by setting a base URL with WithBaseURL.
// WithBaseURL should be used instead of WithDefaultBaseURL except in internal code.
func WithDefaultBaseURL(baseURL string) RequestOption {
	u, err := url.Parse(baseURL)
	return RequestOptionFunc(func(r *RequestConfig) error {
		if err != nil {
			return err
		}
		r.DefaultBaseURL = u
		return nil
	})
}

type Security struct {
	BearerAuth      bool
	AdminAPIKeyAuth bool
}

type authCredentialPreference int

const (
	authCredentialPreferenceNone authCredentialPreference = iota
	authCredentialPreferenceBearer
	authCredentialPreferenceAdmin
)

func WithSecurity(security Security) RequestOption {
	return RequestOptionFunc(func(r *RequestConfig) error {
		r.Security = security
		return nil
	})
}

// WithBearerAuthSecurity() should only be used within a method, not provided to at
// the client-level.
func WithBearerAuthSecurity() RequestOption {
	return RequestOptionFunc(func(r *RequestConfig) error {
		r.Security = Security{
			BearerAuth:      true,
			AdminAPIKeyAuth: false,
		}
		return nil
	})
}

// WithAdminAPIKeyAuthSecurity() should only be used within a method, not provided
// to at the client-level.
func WithAdminAPIKeyAuthSecurity() RequestOption {
	return RequestOptionFunc(func(r *RequestConfig) error {
		r.Security = Security{
			BearerAuth:      false,
			AdminAPIKeyAuth: true,
		}
		return nil
	})
}

// WithBearerAuthPreference() should only be used when a request supports multiple
// auth schemes and has no endpoint-specific security preference.
func WithBearerAuthPreference() RequestOption {
	return RequestOptionFunc(func(r *RequestConfig) error {
		r.authPreference = authCredentialPreferenceBearer
		return nil
	})
}

func ApplySecurity(r RequestConfig) {
	if r.authHeaderOverride {
		return
	}

	if r.authPreference == authCredentialPreferenceBearer && r.Security.BearerAuth && r.APIKey != "" {
		r.Request.Header.Set("authorization", fmt.Sprintf("Bearer %s", r.APIKey))
		return
	}

	if r.authPreference == authCredentialPreferenceAdmin && r.Security.AdminAPIKeyAuth && r.AdminAPIKey != "" {
		r.Request.Header.Set("authorization", fmt.Sprintf("Bearer %s", r.AdminAPIKey))
		return
	}

	if r.Security.AdminAPIKeyAuth && r.AdminAPIKey != "" {
		r.Request.Header.Set("authorization", fmt.Sprintf("Bearer %s", r.AdminAPIKey))
		return
	}

	if r.Security.BearerAuth && r.APIKey != "" {
		r.Request.Header.Set("authorization", fmt.Sprintf("Bearer %s", r.APIKey))
	}
}
