package response

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	api "github.com/tech/sendico/pkg/api/http"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/mservice"
	"go.uber.org/zap"
)

// BaseResponse is a general structure for all API responses.
type BaseResponse struct {
	Status string `json:"status"` // "success" or "error"
	Data   any    `json:"data"`   // The actual data payload or the error details
}

// ErrorResponse provides more details about an error.
type ErrorResponse struct {
	Code    int    `json:"code"` // A unique identifier for the error type, useful for client handling
	Error   string `json:"error"`
	Source  string `json:"source"`
	Details string `json:"details"` // Additional details or hints about the error, if necessary
}

func errMessage(err error) string {
	if err != nil {
		return err.Error()
	}
	return ""
}

func logRequest(logger mlogger.Logger, r *http.Request, message string) {
	logger.Debug(
		message,
		zap.String("host", r.Host),
		zap.String("address", r.RemoteAddr),
		zap.String("method", r.Method),
		zap.String("request_uri", r.RequestURI),
		zap.String("proto", r.Proto),
		zap.String("user_agent", r.UserAgent()),
	)
}

func writeJSON(logger mlogger.Logger, w http.ResponseWriter, r *http.Request, code int, payload any) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
	w.WriteHeader(code)
	if err := json.NewEncoder(w).Encode(&payload); err != nil {
		logger.Warn("Failed to encode JSON response",
			zap.Error(err),
			zap.Any("response", payload),
			zap.String("host", r.Host),
			zap.String("address", r.RemoteAddr),
			zap.String("method", r.Method),
			zap.String("request_uri", r.RequestURI),
			zap.String("proto", r.Proto),
			zap.String("user_agent", r.UserAgent()))
	}
}

func errorf(
	logger mlogger.Logger,
	w http.ResponseWriter, r *http.Request,
	source mservice.Type, code int, message, details string,
) {
	logRequest(logger, r, message)

	errorMessage := BaseResponse{
		Status: api.MSError,
		Data: ErrorResponse{
			Code:    code,
			Details: details,
			Source:  source,
			Error:   message,
		},
	}

	writeJSON(logger, w, r, code, errorMessage)
}

func Accepted(logger mlogger.Logger, data any) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		resp := BaseResponse{
			Status: api.MSProcessed,
			Data:   data,
		}
		writeJSON(logger, w, r, http.StatusAccepted, resp)
	}
}

func Ok(logger mlogger.Logger, data any) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		resp := BaseResponse{
			Status: api.MSSuccess,
			Data:   data,
		}
		writeJSON(logger, w, r, http.StatusOK, resp)
	}
}

func Created(logger mlogger.Logger, data any) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		resp := BaseResponse{
			Status: api.MSSuccess,
			Data:   data,
		}
		writeJSON(logger, w, r, http.StatusCreated, resp)
	}
}

func Auto(logger mlogger.Logger, source mservice.Type, err error) http.HandlerFunc {
	if err == nil {
		return Success(logger)
	}
	if errors.Is(err, merrors.ErrAccessDenied) {
		return AccessDenied(logger, source, errMessage(err))
	}
	if errors.Is(err, merrors.ErrDataConflict) {
		return DataConflict(logger, source, errMessage(err))
	}
	if errors.Is(err, merrors.ErrInvalidArg) {
		return BadRequest(logger, source, "invalid_argument", errMessage(err))
	}
	if errors.Is(err, merrors.ErrNoData) {
		return NotFound(logger, source, errMessage(err))
	}
	if errors.Is(err, merrors.ErrUnauthorized) {
		return Unauthorized(logger, source, errMessage(err))
	}
	return Internal(logger, source, err)
}

func Internal(logger mlogger.Logger, source mservice.Type, err error) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		errorf(logger, w, r, source, http.StatusInternalServerError, "internal_error", errMessage(err))
	}
}

func NotImplemented(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		errorf(logger, w, r, source, http.StatusNotImplemented, "not_implemented", hint)
	}
}

func BadRequest(logger mlogger.Logger, source mservice.Type, err, hint string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		errorf(logger, w, r, source, http.StatusBadRequest, err, hint)
	}
}

func Gone(logger mlogger.Logger, source mservice.Type, err, hint string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		errorf(logger, w, r, source, http.StatusGone, err, hint)
	}
}

func BadQueryParam(logger mlogger.Logger, source mservice.Type, param string, err error) http.HandlerFunc {
	return BadRequest(logger, source, "invalid_query_parameter", fmt.Sprintf("Failed to parse '%s': %v", param, err))
}

func BadReference(logger mlogger.Logger, source mservice.Type, refName, refVal string, err error) http.HandlerFunc {
	return BadRequest(logger, source, "broken_reference",
		fmt.Sprintf("broken object reference: %s = %s, error: %v", refName, refVal, err))
}

func BadPayload(logger mlogger.Logger, source mservice.Type, err error) http.HandlerFunc {
	return BadRequest(logger, source, "broken_payload",
		fmt.Sprintf("broken '%s' object payload, error: %v", source, err))
}

func DataConflict(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		errorf(logger, w, r, source, http.StatusConflict, "data_conflict", hint)
	}
}

func Error(logger mlogger.Logger, source mservice.Type, code int, errType, hint string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		errorf(logger, w, r, source, code, errType, hint)
	}
}

func AccessDenied(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
	return Error(logger, source, http.StatusForbidden, "access_denied", hint)
}

func Forbidden(logger mlogger.Logger, source mservice.Type, errType, hint string) http.HandlerFunc {
	return Error(logger, source, http.StatusForbidden, errType, hint)
}

func TooManyRequests(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
	return Error(logger, source, http.StatusTooManyRequests, "too_many_requests", hint)
}

func LicenseRequired(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		errorf(logger, w, r, source, http.StatusPaymentRequired, "license_required", hint)
	}
}

func Unauthorized(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		errorf(logger, w, r, source, http.StatusUnauthorized, "unauthorized", hint)
	}
}

func NotFound(logger mlogger.Logger, source mservice.Type, hint string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		errorf(logger, w, r, source, http.StatusNotFound, "not_found", hint)
	}
}
