// Code generated by django-cfg/django_client - DO NOT EDIT.
// Generated at: {{ generated_at }}

package {{ package_name }}

import (
	"context"
	"log"
	"math"
	"net/http"
	"net/http/httputil"
	"time"
)

// RetryConfig configures retry behavior.
type RetryConfig struct {
	// Maximum number of retry attempts (default: 3)
	MaxRetries int

	// Initial backoff duration (default: 1s)
	InitialBackoff time.Duration

	// Maximum backoff duration (default: 30s)
	MaxBackoff time.Duration

	// Backoff multiplier (default: 2.0)
	BackoffMultiplier float64

	// Retry on these status codes (default: [429, 500, 502, 503, 504])
	RetryableStatusCodes []int
}

// DefaultRetryConfig returns default retry configuration.
func DefaultRetryConfig() *RetryConfig {
	return &RetryConfig{
		MaxRetries:           3,
		InitialBackoff:       1 * time.Second,
		MaxBackoff:           30 * time.Second,
		BackoffMultiplier:    2.0,
		RetryableStatusCodes: []int{429, 500, 502, 503, 504},
	}
}

// RetryTransport implements automatic retry with exponential backoff.
type RetryTransport struct {
	Base   http.RoundTripper
	Config *RetryConfig
}

// NewRetryTransport creates a new retry transport.
func NewRetryTransport(base http.RoundTripper, config *RetryConfig) *RetryTransport {
	if base == nil {
		base = http.DefaultTransport
	}
	if config == nil {
		config = DefaultRetryConfig()
	}
	return &RetryTransport{
		Base:   base,
		Config: config,
	}
}

// RoundTrip executes a single HTTP transaction with retry logic.
func (t *RetryTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	var resp *http.Response
	var err error

	for attempt := 0; attempt <= t.Config.MaxRetries; attempt++ {
		// Clone request for retry (body can only be read once)
		reqClone := req.Clone(req.Context())

		resp, err = t.Base.RoundTrip(reqClone)

		// Success - no retry needed
		if err == nil && !t.shouldRetry(resp.StatusCode) {
			return resp, nil
		}

		// Last attempt - return error
		if attempt == t.Config.MaxRetries {
			return resp, err
		}

		// Calculate backoff
		backoff := t.calculateBackoff(attempt)

		// Check context cancellation
		select {
		case <-req.Context().Done():
			return resp, req.Context().Err()
		case <-time.After(backoff):
			// Continue to next attempt
		}

		// Close previous response body if exists
		if resp != nil {
			resp.Body.Close()
		}
	}

	return resp, err
}

// shouldRetry determines if a status code should trigger a retry.
func (t *RetryTransport) shouldRetry(statusCode int) bool {
	for _, code := range t.Config.RetryableStatusCodes {
		if statusCode == code {
			return true
		}
	}
	return false
}

// calculateBackoff calculates exponential backoff duration.
func (t *RetryTransport) calculateBackoff(attempt int) time.Duration {
	backoff := float64(t.Config.InitialBackoff) * math.Pow(t.Config.BackoffMultiplier, float64(attempt))
	maxBackoff := float64(t.Config.MaxBackoff)

	if backoff > maxBackoff {
		backoff = maxBackoff
	}

	return time.Duration(backoff)
}

// LoggingTransport logs HTTP requests and responses.
type LoggingTransport struct {
	Base    http.RoundTripper
	Logger  *log.Logger
	Verbose bool // If true, logs request/response bodies
}

// NewLoggingTransport creates a new logging transport.
func NewLoggingTransport(base http.RoundTripper, logger *log.Logger, verbose bool) *LoggingTransport {
	if base == nil {
		base = http.DefaultTransport
	}
	return &LoggingTransport{
		Base:    base,
		Logger:  logger,
		Verbose: verbose,
	}
}

// RoundTrip executes a single HTTP transaction with logging.
func (t *LoggingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	start := time.Now()

	// Log request
	if t.Logger != nil {
		if t.Verbose {
			reqDump, _ := httputil.DumpRequestOut(req, true)
			t.Logger.Printf("→ Request:\n%s\n", string(reqDump))
		} else {
			t.Logger.Printf("→ %s %s", req.Method, req.URL.String())
		}
	}

	// Execute request
	resp, err := t.Base.RoundTrip(req)
	duration := time.Since(start)

	// Log response
	if t.Logger != nil {
		if err != nil {
			t.Logger.Printf("← Error after %v: %v", duration, err)
		} else {
			if t.Verbose {
				respDump, _ := httputil.DumpResponse(resp, true)
				t.Logger.Printf("← Response after %v:\n%s\n", duration, string(respDump))
			} else {
				t.Logger.Printf("← %d %s after %v", resp.StatusCode, resp.Status, duration)
			}
		}
	}

	return resp, err
}

// RateLimitTransport implements client-side rate limiting.
type RateLimitTransport struct {
	Base     http.RoundTripper
	Limiter  *time.Ticker
	requests chan struct{}
}

// NewRateLimitTransport creates a new rate limit transport.
// requestsPerSecond: maximum number of requests per second
func NewRateLimitTransport(base http.RoundTripper, requestsPerSecond int) *RateLimitTransport {
	if base == nil {
		base = http.DefaultTransport
	}

	interval := time.Second / time.Duration(requestsPerSecond)
	limiter := time.NewTicker(interval)
	requests := make(chan struct{}, requestsPerSecond)

	return &RateLimitTransport{
		Base:     base,
		Limiter:  limiter,
		requests: requests,
	}
}

// RoundTrip executes a single HTTP transaction with rate limiting.
func (t *RateLimitTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	// Wait for rate limiter
	select {
	case <-req.Context().Done():
		return nil, req.Context().Err()
	case <-t.Limiter.C:
		// Proceed with request
	}

	return t.Base.RoundTrip(req)
}

// Close stops the rate limiter.
func (t *RateLimitTransport) Close() {
	if t.Limiter != nil {
		t.Limiter.Stop()
	}
}

// TimeoutTransport adds request-level timeout.
type TimeoutTransport struct {
	Base    http.RoundTripper
	Timeout time.Duration
}

// NewTimeoutTransport creates a new timeout transport.
func NewTimeoutTransport(base http.RoundTripper, timeout time.Duration) *TimeoutTransport {
	if base == nil {
		base = http.DefaultTransport
	}
	return &TimeoutTransport{
		Base:    base,
		Timeout: timeout,
	}
}

// RoundTrip executes a single HTTP transaction with timeout.
func (t *TimeoutTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	ctx, cancel := context.WithTimeout(req.Context(), t.Timeout)
	defer cancel()

	return t.Base.RoundTrip(req.WithContext(ctx))
}

// ChainTransports chains multiple http.RoundTrippers together.
// The order matters: first transport wraps second, second wraps third, etc.
//
// Example:
//   transport := ChainTransports(
//       NewLoggingTransport(nil, logger, false),  // Outermost
//       NewRetryTransport(nil, nil),               // Middle
//       http.DefaultTransport,                     // Innermost (base)
//   )
func ChainTransports(transports ...http.RoundTripper) http.RoundTripper {
	if len(transports) == 0 {
		return http.DefaultTransport
	}

	// Start from the innermost (last) transport
	base := transports[len(transports)-1]

	// Wrap each transport from right to left
	for i := len(transports) - 2; i >= 0; i-- {
		switch t := transports[i].(type) {
		case *RetryTransport:
			t.Base = base
			base = t
		case *LoggingTransport:
			t.Base = base
			base = t
		case *RateLimitTransport:
			t.Base = base
			base = t
		case *TimeoutTransport:
			t.Base = base
			base = t
		default:
			// For custom transports, just use as-is
			base = t
		}
	}

	return base
}

// HTTPClientWithMiddleware creates an HTTP client with middleware.
//
// Example:
//   client := HTTPClientWithMiddleware(
//       WithRetry(3),
//       WithLogging(logger, false),
//       WithRateLimit(10), // 10 requests per second
//       WithTimeout(30 * time.Second),
//   )
func HTTPClientWithMiddleware(options ...MiddlewareOption) *http.Client {
	config := &middlewareConfig{
		baseTransport: http.DefaultTransport,
	}

	// Apply options
	for _, option := range options {
		option(config)
	}

	// Build transport chain
	var transports []http.RoundTripper

	if config.loggingTransport != nil {
		transports = append(transports, config.loggingTransport)
	}
	if config.retryTransport != nil {
		transports = append(transports, config.retryTransport)
	}
	if config.rateLimitTransport != nil {
		transports = append(transports, config.rateLimitTransport)
	}
	if config.timeoutTransport != nil {
		transports = append(transports, config.timeoutTransport)
	}

	transports = append(transports, config.baseTransport)

	return &http.Client{
		Transport: ChainTransports(transports...),
	}
}

// middlewareConfig holds middleware configuration.
type middlewareConfig struct {
	baseTransport       http.RoundTripper
	retryTransport      *RetryTransport
	loggingTransport    *LoggingTransport
	rateLimitTransport  *RateLimitTransport
	timeoutTransport    *TimeoutTransport
}

// MiddlewareOption configures middleware.
type MiddlewareOption func(*middlewareConfig)

// WithRetry adds retry middleware.
func WithRetry(maxRetries int) MiddlewareOption {
	return func(c *middlewareConfig) {
		config := DefaultRetryConfig()
		config.MaxRetries = maxRetries
		c.retryTransport = NewRetryTransport(nil, config)
	}
}

// WithRetryConfig adds retry middleware with custom config.
func WithRetryConfig(config *RetryConfig) MiddlewareOption {
	return func(c *middlewareConfig) {
		c.retryTransport = NewRetryTransport(nil, config)
	}
}

// WithLogging adds logging middleware.
func WithLogging(logger *log.Logger, verbose bool) MiddlewareOption {
	return func(c *middlewareConfig) {
		c.loggingTransport = NewLoggingTransport(nil, logger, verbose)
	}
}

// WithRateLimit adds rate limiting middleware.
func WithRateLimit(requestsPerSecond int) MiddlewareOption {
	return func(c *middlewareConfig) {
		c.rateLimitTransport = NewRateLimitTransport(nil, requestsPerSecond)
	}
}

// WithTimeout adds timeout middleware.
func WithTimeout(timeout time.Duration) MiddlewareOption {
	return func(c *middlewareConfig) {
		c.timeoutTransport = NewTimeoutTransport(nil, timeout)
	}
}

// WithBaseTransport sets custom base transport.
func WithBaseTransport(transport http.RoundTripper) MiddlewareOption {
	return func(c *middlewareConfig) {
		c.baseTransport = transport
	}
}
