package api

import (
	"errors"
	"log/slog"
	"net/http"

	"github.com/go-chi/chi/v5/middleware"
)

// HandlerFunc is a handler function that returns an error.
// Use with Wrap() to automatically handle error responses.
//
// Example:
//
//	func GetUser(w http.ResponseWriter, r *http.Request) error {
//	    user, err := svc.Get(ctx, id)
//	    if err != nil {
//	        return api.NotFoundf("user %s not found", id)
//	    }
//	    api.WriteSuccess(w, r, user)
//	    return nil
//	}
type HandlerFunc func(w http.ResponseWriter, r *http.Request) error

// Wrap converts a HandlerFunc to http.HandlerFunc, automatically handling
// error responses. HTTPErrors are written with their status code and details;
// other errors are logged and returned as 500 Internal Server Error.
//
// Example:
//
//	r.Get("/users/{id}", api.Wrap(handlers.GetUser))
//
//	func (h *Handlers) GetUser(w http.ResponseWriter, r *http.Request) error {
//	    id := chi.URLParam(r, "id")
//	    user, err := h.userSvc.Get(r.Context(), id)
//	    if err != nil {
//	        if errors.Is(err, ErrUserNotFound) {
//	            return api.NotFoundf("user %s not found", id)
//	        }
//	        return err // Will be logged and returned as 500
//	    }
//	    api.WriteSuccess(w, r, user)
//	    return nil
//	}
func Wrap(h HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if err := h(w, r); err != nil {
			writeErrorFromErr(w, r, err)
		}
	}
}

// WrapWithLogger is like Wrap but accepts a logger for internal error logging.
// Use this when you want to log internal errors that aren't HTTPErrors.
//
// Example:
//
//	r.Get("/users/{id}", api.WrapWithLogger(handlers.GetUser, logger))
func WrapWithLogger(h HandlerFunc, logger *slog.Logger) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if err := h(w, r); err != nil {
			writeErrorFromErrWithLogger(w, r, err, logger)
		}
	}
}

// writeErrorFromErr writes an error response based on the error type.
// HTTPErrors are written with their status code; other errors become 500.
func writeErrorFromErr(w http.ResponseWriter, r *http.Request, err error) {
	writeErrorFromErrWithLogger(w, r, err, nil)
}

// writeErrorFromErrWithLogger writes an error response and optionally logs internal errors.
func writeErrorFromErrWithLogger(w http.ResponseWriter, r *http.Request, err error, logger *slog.Logger) {
	var httpErr *HTTPError
	if errors.As(err, &httpErr) {
		// Write the HTTPError directly
		WriteError(w, r, httpErr.Status, httpErr.Code, httpErr.Message, httpErr.Details)
		return
	}

	// For non-HTTP errors, log and return generic 500
	if logger != nil {
		reqID := middleware.GetReqID(r.Context())
		logger.Error("internal error",
			"error", err,
			"request_id", reqID,
			"method", r.Method,
			"path", r.URL.Path,
		)
	}
	WriteInternalError(w, r, "internal error")
}

// -----------------------------------------------------------------------------
// Middleware Helpers
// -----------------------------------------------------------------------------

// MiddlewareFunc is a middleware that returns an error.
// Use with WrapMiddleware() to automatically handle error responses.
type MiddlewareFunc func(next http.Handler) func(w http.ResponseWriter, r *http.Request) error

// WrapMiddleware converts a MiddlewareFunc to a standard http middleware.
//
// Example:
//
//	func AuthMiddleware(next http.Handler) func(w http.ResponseWriter, r *http.Request) error {
//	    return func(w http.ResponseWriter, r *http.Request) error {
//	        token := r.Header.Get("Authorization")
//	        if token == "" {
//	            return api.Unauthorized("missing authorization header")
//	        }
//	        user, err := validateToken(token)
//	        if err != nil {
//	            return api.Unauthorized("invalid token")
//	        }
//	        ctx := context.WithValue(r.Context(), userKey, user)
//	        next.ServeHTTP(w, r.WithContext(ctx))
//	        return nil
//	    }
//	}
//
//	r.Use(api.WrapMiddleware(AuthMiddleware))
func WrapMiddleware(m MiddlewareFunc) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if err := m(next)(w, r); err != nil {
				writeErrorFromErr(w, r, err)
			}
		})
	}
}
