// Package httpkit provides a shared HTTP client with retry, timeout, and error
// classification for outbound API calls.
//
// All outbound HTTP in this codebase should use httpkit.Client instead of
// raw http.Client or http.DefaultClient. This ensures consistent timeout,
// retry, body lifecycle, and error classification.
//
// Basic usage:
//
//	client := httpkit.NewClient(httpkit.Config{
//	    Timeout:    30 * time.Second,
//	    MaxRetries: 3,
//	    Headers:    map[string]string{"Authorization": "Bearer " + apiKey},
//	})
//
//	body, err := client.Do(ctx, httpkit.Request{
//	    Method: http.MethodPost,
//	    URL:    baseURL + "/chat/completions",
//	    Body:   chatReq,
//	})
//	if errors.Is(err, httpkit.ErrRateLimit) { ... }
package httpkit

import (
	"errors"
	"fmt"
	"net/http"
)

// Sentinel errors classify HTTP failures into retryable categories.
// Use errors.Is to check against these from any wrapped error.
var (
	ErrTimeout      = errors.New("httpkit: timeout")
	ErrRateLimit    = errors.New("httpkit: rate limited")
	ErrServerError  = errors.New("httpkit: server error")
	ErrUnauthorized = errors.New("httpkit: unauthorized")
	ErrForbidden    = errors.New("httpkit: forbidden")
	ErrBadRequest   = errors.New("httpkit: bad request")
	ErrNotFound     = errors.New("httpkit: not found")
)

// APIError represents a non-2xx HTTP response.
// Unwrap returns the classified sentinel error for use with errors.Is.
type APIError struct {
	StatusCode int
	Body       string
	sentinel   error
}

func (e *APIError) Error() string {
	body := e.Body
	if len(body) > 200 {
		body = body[:200] + "..."
	}
	return fmt.Sprintf("HTTP %d: %s", e.StatusCode, body)
}

// Unwrap returns the classified sentinel error, enabling errors.Is checks
// like errors.Is(err, httpkit.ErrRateLimit).
func (e *APIError) Unwrap() error {
	return e.sentinel
}

// ClassifyStatus maps an HTTP status code to a sentinel error.
// Returns nil for unrecognized or successful status codes.
func ClassifyStatus(code int) error {
	switch {
	case code == http.StatusUnauthorized:
		return ErrUnauthorized
	case code == http.StatusForbidden:
		return ErrForbidden
	case code == http.StatusNotFound:
		return ErrNotFound
	case code == http.StatusTooManyRequests:
		return ErrRateLimit
	case code >= 400 && code < 500:
		return ErrBadRequest
	case code >= 500:
		return ErrServerError
	default:
		return nil
	}
}

// IsRetryable reports whether err indicates a condition that may succeed on retry.
// Retryable conditions: rate limits (429), server errors (5xx), and timeouts.
func IsRetryable(err error) bool {
	return errors.Is(err, ErrRateLimit) ||
		errors.Is(err, ErrServerError) ||
		errors.Is(err, ErrTimeout)
}
