package errors

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"time"
)

// HTTPError preserves HTTP response details
type HTTPError struct {
	Response  *http.Response
	Operation string
	Message   string
	Body      []byte
}

func (e *HTTPError) Error() string {
	if e.Message != "" {
		return fmt.Sprintf("%s: %s (status %d)", e.Operation, e.Message, e.Response.StatusCode)
	}
	return fmt.Sprintf("%s: status %d", e.Operation, e.Response.StatusCode)
}

// Temporary implements net.Error interface
func (e *HTTPError) Temporary() bool {
	code := e.Response.StatusCode
	return code == 408 || code == 429 || code >= 500
}

// RetryAfter returns how long to wait before retrying based on
// rate limit headers in the response
func (e *HTTPError) RetryAfter() time.Duration {
	if v := e.Response.Header.Get("Retry-After"); v != "" {
		if seconds, err := strconv.Atoi(v); err == nil && seconds > 0 {
			return time.Duration(seconds) * time.Second
		}
		if t, err := http.ParseTime(v); err == nil {
			if delay := time.Until(t); delay > 0 {
				return delay
			}
		}
	}
	return 0
}

// StatusCode returns the HTTP status code
func (e *HTTPError) StatusCode() int {
	return e.Response.StatusCode
}

// FileTooLargeError is returned when an upload is rejected client-side
// because the file size exceeds the account's MaxUploadFileSize limit
type FileTooLargeError struct {
	Size    int64
	MaxSize int64
}

func (e *FileTooLargeError) Error() string {
	return fmt.Sprintf("file too large: %d bytes exceeds account upload limit of %d bytes", e.Size, e.MaxSize)
}

// NewHTTPError creates an HTTPError from a response
func NewHTTPError(resp *http.Response, operation string) error {
	body, _ := io.ReadAll(resp.Body)

	httpErr := &HTTPError{
		Response:  resp,
		Operation: operation,
		Body:      body,
	}

	var backendErr struct {
		Error   string `json:"error"`
		Message string `json:"message"`
	}

	if json.Unmarshal(body, &backendErr) == nil {
		if backendErr.Message != "" {
			httpErr.Message = backendErr.Message
		} else if backendErr.Error != "" {
			httpErr.Message = backendErr.Error
		}
	} else {
		httpErr.Message = string(body)
	}

	return httpErr
}
