package apierror

import (
	"errors"
	"fmt"
)

type Error struct {
	Status  int
	Code    string
	Message string
	Cause   error
}

func (e *Error) Error() string {
	if e.Cause != nil {
		return fmt.Sprintf("%s: %v", e.Message, e.Cause)
	}
	return e.Message
}

func (e *Error) Unwrap() error { return e.Cause }

// From returns err as *Error if it (or anything in its chain) is one;
// otherwise wraps err in an Internal.
func From(err error) *Error {
	if err == nil {
		return nil
	}
	var apiErr *Error
	if errors.As(err, &apiErr) {
		return apiErr
	}
	return Internal(err)
}

func NotFound(what string) *Error {
	return &Error{Status: 404, Code: what + "_not_found", Message: what + " not found"}
}

func BadRequest(code, message string) *Error {
	return &Error{Status: 400, Code: code, Message: message}
}

func Conflict(code, message string) *Error {
	return &Error{Status: 409, Code: code, Message: message}
}

func Forbidden(code, message string) *Error {
	return &Error{Status: 403, Code: code, Message: message}
}

func Unauthorized(code, message string) *Error {
	return &Error{Status: 401, Code: code, Message: message}
}

func Internal(cause error) *Error {
	return &Error{Status: 500, Code: "server_error", Message: "internal server error", Cause: cause}
}

// InternalMsg returns a 500 Error with a custom user-facing message
// and a cause for logging. Use when the call site has a meaningful
// message that should appear on the wire (e.g. "lookup failed") rather
// than the generic "internal server error" from Internal.
func InternalMsg(message string, cause error) *Error {
	return &Error{Status: 500, Code: "server_error", Message: message, Cause: cause}
}

var (
	ErrNotFound     = &Error{Status: 404, Code: "not_found", Message: "not found"}
	ErrForbidden    = &Error{Status: 403, Code: "forbidden", Message: "forbidden"}
	ErrUnauthorized = &Error{Status: 401, Code: "unauthorized", Message: "authentication required"}
)
