package gin

import (
	"errors"
	"github.com/UpmeshLTD/urus-aio/infrastructure/server/endpoint"
	"net/http"

	endperrs "github.com/UpmeshLTD/urus-aio/infrastructure/server/endpoint/errors"
	"github.com/gin-gonic/gin"
)

// RequestDecoderFunc is a function that decodes incoming context data to endpoint request.
type RequestDecoderFunc func(c *gin.Context) (interface{}, error)

// ResponseEncoderFunc is a function that transforms an endpoint response to respective data format in out-going gin context.
type ResponseEncoderFunc func(c *gin.Context, response interface{}) error

// ErrorResponse is the response object when there is an error detected by the transport layer.
type ErrorResponse struct {
	Message string      `json:"message"`
	Error   interface{} `json:"error,omitempty"`
}

// NewHandler returns a Gin HandlerFunc corresponding to a given endpoint and the request-response decoder-encoder
// pair.
func NewHandler(e endpoint.Endpoint, decoderFunc RequestDecoderFunc, encoderFunc ResponseEncoderFunc) gin.HandlerFunc {
	return func(c *gin.Context) {
		request, err := decoderFunc(c)
		if err != nil {
			handleError(c, err)
			return
		}

		// We need to use the context in the request rather than Gin context (c). Gin context does not work well
		// with other components where standard context value injection is used - Gin use Set/Get with string key.
		response, err := e.Invoke(c.Request.Context(), request)
		if err != nil {
			handleError(c, err)
			return
		}

		// encoderFunc will take care of serialization.
		err = encoderFunc(c, response)
		if err != nil {
			handleError(c, err)
			return
		}
	}
}

// handleError handles and error by generating respective HTTP status code and ErrorResponse to gin transport layer.
func handleError(c *gin.Context, err error) {
	response := &ErrorResponse{
		Message: err.Error(),
	}

	details := endperrs.Details(err)
	if details != nil {
		response.Error = details.Error()
	}

	switch {
	case errors.Is(err, endperrs.ErrNotFound):
		c.JSON(http.StatusNotFound, response)
	case errors.Is(err, endperrs.ErrInvalidArgument):
		c.JSON(http.StatusBadRequest, response)
	case errors.Is(err, endperrs.ErrUnimplemented):
		c.JSON(http.StatusNotImplemented, response)
	default:
		c.JSON(http.StatusInternalServerError, response)
	}
}
