package cart

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

	"github.com/TaruLab/keluargamart-backend/internal/auth"
	"github.com/TaruLab/keluargamart-backend/internal/pricing"
	"github.com/TaruLab/keluargamart-backend/internal/sales"
	"github.com/TaruLab/keluargamart-backend/pkg/httpresponse"
	"github.com/TaruLab/keluargamart-backend/pkg/logger"
	"github.com/TaruLab/keluargamart-backend/pkg/middleware"
	"github.com/go-chi/chi/v5"
)

type Handler struct {
	svc *Service
}

func NewHandler(svc *Service) *Handler {
	return &Handler{svc: svc}
}

// GetCart returns the current user's cart.
func (h *Handler) GetCart(w http.ResponseWriter, r *http.Request) {
	userID := userIDFrom(r)
	cart, err := h.svc.GetCart(r.Context(), userID)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), cart)
}

// AddItem adds a product to the cart by SKU.
func (h *Handler) AddItem(w http.ResponseWriter, r *http.Request) {
	userID := userIDFrom(r)
	var in AddItemInput
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()), httpresponse.CodeValidation, "invalid request body", nil)
		return
	}

	cart, err := h.svc.AddItem(r.Context(), userID, in.SKU, in.Quantity)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), cart)
}

// UpdateQuantity updates the quantity of a cart item by product ID (from path).
func (h *Handler) UpdateQuantity(w http.ResponseWriter, r *http.Request) {
	userID := userIDFrom(r)
	productID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	if err != nil {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()), httpresponse.CodeValidation, "invalid product id", nil)
		return
	}

	var in UpdateQuantityInput
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()), httpresponse.CodeValidation, "invalid request body", nil)
		return
	}

	cart, err := h.svc.UpdateQuantity(r.Context(), userID, productID, in.Quantity)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), cart)
}

// RemoveItem removes a product from the cart by product ID (from path).
func (h *Handler) RemoveItem(w http.ResponseWriter, r *http.Request) {
	userID := userIDFrom(r)
	productID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	if err != nil {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()), httpresponse.CodeValidation, "invalid product id", nil)
		return
	}

	cart, err := h.svc.RemoveItem(r.Context(), userID, productID)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), cart)
}

// ClearCart removes all items from the cart.
func (h *Handler) ClearCart(w http.ResponseWriter, r *http.Request) {
	userID := userIDFrom(r)
	err := h.svc.ClearCart(r.Context(), userID)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// Checkout converts the cart into a confirmed sales order.
func (h *Handler) Checkout(w http.ResponseWriter, r *http.Request) {
	user := UserFrom(r)
	if user == nil {
		httpresponse.Error(w, http.StatusUnauthorized, middleware.RequestIDFrom(r.Context()), httpresponse.CodeAuthentication, "Authentication required", nil)
		return
	}
	var in CheckoutInput
	if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()), httpresponse.CodeValidation, "invalid request body", nil)
		return
	}
	in.IdempotencyKey = r.Header.Get("Idempotency-Key")
	order, err := h.svc.Checkout(r.Context(), user.ID, user.Name, user.Email, in)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), map[string]any{
		"sales_order": order,
	})
}

// --- helpers

func UserFrom(r *http.Request) *auth.User {
	return auth.UserFrom(r.Context())
}

func userIDFrom(r *http.Request) int64 {
	if u := UserFrom(r); u != nil {
		return u.ID
	}
	return 0
}

func respondErr(w http.ResponseWriter, r *http.Request, err error) {
	reqID := middleware.RequestIDFrom(r.Context())
	log := logger.FromContext(r.Context())

	switch {
	case errors.Is(err, ErrCheckoutConflict):
		httpresponse.Error(w, http.StatusConflict, reqID, httpresponse.CodeBusinessViolation, err.Error(), nil)
	case errors.Is(err, pricing.ErrBelowMinPurchase), errors.Is(err, pricing.ErrQuantityInvalid),
		errors.Is(err, pricing.ErrProductNotFound), errors.Is(err, pricing.ErrProductInactive),
		errors.Is(err, pricing.ErrUnitNotFound), errors.Is(err, pricing.ErrUnitNotForProduct),
		errors.Is(err, pricing.ErrUnitInactive), errors.Is(err, sales.ErrCheckoutValidation):
		httpresponse.Error(w, http.StatusBadRequest, reqID, httpresponse.CodeValidation, err.Error(), nil)
	case errors.Is(err, pricing.ErrPriceNotConfigured):
		httpresponse.Error(w, http.StatusConflict, reqID, httpresponse.CodeBusinessViolation, "Harga produk belum tersedia. Hubungi toko.", nil)
	case errors.Is(err, ErrNotFound) || errors.Is(err, sales.ErrNotFound):
		httpresponse.Error(w, http.StatusNotFound, reqID, httpresponse.CodeNotFound, "cart not found", nil)
	case errors.Is(err, ErrItemNotFound):
		httpresponse.Error(w, http.StatusNotFound, reqID, httpresponse.CodeNotFound, "cart item not found", nil)
	case errors.Is(err, ErrProductNotFound):
		httpresponse.Error(w, http.StatusNotFound, reqID, httpresponse.CodeNotFound, "product not found", nil)
	case errors.Is(err, ErrInvalidQuantity):
		httpresponse.Error(w, http.StatusBadRequest, reqID, httpresponse.CodeValidation, err.Error(), nil)
	case errors.Is(err, ErrEmptyCart) || errors.Is(err, ErrNotConfigured):
		httpresponse.Error(w, http.StatusConflict, reqID, httpresponse.CodeBusinessViolation, err.Error(), nil)
	// Checkout runs the sales order placer, so sales-domain sentinel errors
	// surface here. Mirror internal/sales respondErr mappings exactly:
	// voucher redemption failures are business violations (409), missing
	// items/customer are validations (400) — never an opaque 500.
	case errors.Is(err, sales.ErrVoucherNotUsable):
		httpresponse.Error(w, http.StatusConflict, reqID, httpresponse.CodeBusinessViolation, err.Error(), nil)
	case errors.Is(err, sales.ErrCannotModify) || errors.Is(err, sales.ErrCannotCancel) ||
		errors.Is(err, sales.ErrInvalidStatus) || errors.Is(err, sales.ErrCancelled) ||
		errors.Is(err, sales.ErrInsufficientStock) || errors.Is(err, sales.ErrPaidCannotCancel) ||
		errors.Is(err, sales.ErrUnpaidOrderFulfillment):
		httpresponse.Error(w, http.StatusConflict, reqID, httpresponse.CodeBusinessViolation, err.Error(), nil)
	// A transfer-order buyer hitting fulfilment gating through the storefront
	// must see the rule, not a 500.
	case errors.Is(err, sales.ErrFulfillmentForbidden):
		httpresponse.Error(w, http.StatusForbidden, reqID, httpresponse.CodeForbidden, err.Error(), nil)
	case errors.Is(err, sales.ErrGatewayDisabled):
		// Mirror sales: same status and safe message, never the configured keys.
		httpresponse.Error(w, http.StatusServiceUnavailable, reqID, httpresponse.CodeInternal, "Payment gateway is unavailable", nil)
	case errors.Is(err, sales.ErrPaymentNotAllowed):
		httpresponse.Error(w, http.StatusBadRequest, reqID, httpresponse.CodeValidation, err.Error(), nil)
	case errors.Is(err, sales.ErrItemRequired) || errors.Is(err, sales.ErrCustomerRequired):
		httpresponse.Error(w, http.StatusBadRequest, reqID, httpresponse.CodeValidation, err.Error(), nil)
	default:
		httpresponse.Internal(w, reqID, log, err)
	}
}
