package rest

import (
	"net/http"
	"strings"

	validation "github.com/go-ozzo/ozzo-validation/v4"
	"github.com/pocketbase/pocketbase/tools/inflector"
)

// ApiError defines the properties for a basic api error response.
type ApiError struct {
	Code    int            `json:"code"`
	Message string         `json:"message"`
	Data    map[string]any `json:"data"`

	// stores unformatted error data (could be an internal error, text, etc.)
	rawData any
}

// Error makes it compatible with the `error` interface.
func (e *ApiError) Error() string {
	return e.Message
}

func (e *ApiError) RawData() any {
	return e.rawData
}

// NewNotFoundError creates and returns 404 `ApiError`.
func NewNotFoundError(message string, data any) *ApiError {
	if message == "" {
		message = "The requested resource wasn't found."
	}

	return NewApiError(http.StatusNotFound, message, data)
}

// NewBadRequestError creates and returns 400 `ApiError`.
func NewBadRequestError(message string, data any) *ApiError {
	if message == "" {
		message = "Something went wrong while processing your request."
	}

	return NewApiError(http.StatusBadRequest, message, data)
}

// NewForbiddenError creates and returns 403 `ApiError`.
func NewForbiddenError(message string, data any) *ApiError {
	if message == "" {
		message = "You are not allowed to perform this request."
	}

	return NewApiError(http.StatusForbidden, message, data)
}

// NewUnauthorizedError creates and returns 401 `ApiError`.
func NewUnauthorizedError(message string, data any) *ApiError {
	if message == "" {
		message = "Missing or invalid authentication token."
	}

	return NewApiError(http.StatusUnauthorized, message, data)
}

// NewApiError creates and returns new normalized `ApiError` instance.
func NewApiError(status int, message string, data any) *ApiError {
	message = inflector.Sentenize(message)

	formattedData := map[string]any{}

	if v, ok := data.(validation.Errors); ok {
		formattedData = resolveValidationErrors(v)
	}

	return &ApiError{
		rawData: data,
		Data:    formattedData,
		Code:    status,
		Message: strings.TrimSpace(message),
	}
}

func resolveValidationErrors(validationErrors validation.Errors) map[string]any {
	result := map[string]any{}

	// extract from each validation error its error code and message.
	for name, err := range validationErrors {
		// check for nested errors
		if nestedErrs, ok := err.(validation.Errors); ok {
			result[name] = resolveValidationErrors(nestedErrs)
			continue
		}

		errCode := "validation_invalid_value" // default
		if errObj, ok := err.(validation.ErrorObject); ok {
			errCode = errObj.Code()
		}

		result[name] = map[string]string{
			"code":    errCode,
			"message": inflector.Sentenize(err.Error()),
		}
	}

	return result
}
