package bunny

import (
	"fmt"
	"net/http"
)

type errorKind int

const (
	// errorKindNetwork: the request never got a response (DNS, connection, TLS,
	// context cancellation surfaced as a transport error).
	errorKindNetwork errorKind = iota
	// errorKindAuth: the access key was rejected (401/403).
	errorKindAuth
	// errorKindRateLimited: Bunny is throttling (429, in any retry-exhausted form).
	errorKindRateLimited
	// errorKindServer: Bunny-side failure (5xx).
	errorKindServer
	// errorKindInternal: an unexpected client error (non-auth, non-rate-limit 4xx).
	// These almost always mean hopper built a bad request: a likely bug.
	errorKindInternal
	// errorKindTooLarge: Bunny rejected the asset as too large (413). This is the
	// operator's payload, not a hopper bug, so it gets a distinct message.
	errorKindTooLarge
)

func errorKindFromStatus(status int) errorKind {
	switch {
	case status == http.StatusUnauthorized || status == http.StatusForbidden:
		return errorKindAuth
	case status == http.StatusRequestEntityTooLarge:
		return errorKindTooLarge
	case status >= 500:
		return errorKindServer
	default:
		return errorKindInternal
	}
}

// apiError is the single error type returned by all API operations. Error()
// renders a friendly, kind-based message for the user; the technical detail
// (method, path, status, wrapped cause) is exposed separately via
// ErrorVerbose() and surfaced only under --verbose by the presentation layer.
// method and path are derived from the request itself (HTTP verb and
// req.URL.Path).
type apiError struct {
	method string // HTTP verb, e.g. "PUT"
	path   string // req.URL.Path, e.g. "/zone/a/b.js"
	status int    // HTTP status code; 0 for transport-level (KindNetwork) errors
	kind   errorKind
	cause  error // wrapped technical cause, exposed via Unwrap (NOT via Error)
}

func newAPIError(kind errorKind, err error, req *http.Request, statusCode int) *apiError {
	return &apiError{
		method: req.Method,
		path:   req.URL.Path,
		status: statusCode,
		kind:   kind,
		cause:  err,
	}
}

func (e *apiError) Unwrap() error { return e.cause }

// ErrorVerbose returns the technical description (verb, path, status, cause)
// surfaced only under --verbose. It is the "re-run with --verbose for details"
// payload; the user-facing message stays in Error(). Any error type may
// implement this interface to contribute extra detail to failure reports.
func (e *apiError) ErrorVerbose() string {
	if e.status != 0 {
		return fmt.Sprintf("%s %s -> %d: %v", e.method, e.path, e.status, e.cause)
	}
	return fmt.Sprintf("%s %s -> %v", e.method, e.path, e.cause)
}

func (e *apiError) Error() string {
	switch e.kind {
	case errorKindAuth:
		return "The access key was rejected. Check your Bunny storage credentials."
	case errorKindRateLimited:
		return "Bunny is rate-limiting requests. Please try again shortly."
	case errorKindServer:
		return "Bunny had an internal server error. Please try again later."
	case errorKindNetwork:
		return "Couldn't reach Bunny. Check your network connection and endpoint."
	case errorKindTooLarge:
		return "The asset was rejected by Bunny as too large."
	case errorKindInternal:
		return "Unexpected response from Bunny. This is likely a bug in hopper. Please report it (re-run with --verbose for details)."
	default:
		return "An unknown Bunny error occurred (re-run with --verbose for details)."
	}
}
