package membership

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

	"github.com/TaruLab/keluargamart-backend/internal/auth"
	"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"
	"github.com/shopspring/decimal"
)

// Handler binds the membership service to HTTP. Permission middleware is
// injected per route in cmd/server/main.go.
type Handler struct {
	svc *Service
}

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

func (h *Handler) ListMemberships(w http.ResponseWriter, r *http.Request) {
	q := r.URL.Query()
	f := Filter{}
	f.Page, _ = strconv.Atoi(q.Get("page"))
	f.PerPage, _ = strconv.Atoi(q.Get("per_page"))
	f.Search = q.Get("search")
	f.Tier = q.Get("tier")
	if v := q.Get("is_active"); v == "true" || v == "false" {
		b := v == "true"
		f.IsActive = &b
	}
	list, total, err := h.svc.List(r.Context(), f)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OKPaginated(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list, f.Page, f.PerPage, total)
}

// Me resolves the membership behind the authenticated user (User -> Customer ->
// Membership chain).
func (h *Handler) Me(w http.ResponseWriter, r *http.Request) {
	u := auth.UserFrom(r.Context())
	if u == nil {
		httpresponse.Error(w, http.StatusUnauthorized, middleware.RequestIDFrom(r.Context()), httpresponse.CodeAuthentication, "unauthenticated", nil)
		return
	}
	v, err := h.svc.ByUser(r.Context(), u.ID)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), v)
}

func (h *Handler) ByCustomer(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(chi.URLParam(r, "customerId"), 10, 64)
	if err != nil || id <= 0 {
		httpresponse.Error(w, http.StatusBadRequest, middleware.RequestIDFrom(r.Context()), httpresponse.CodeValidation, "invalid customerId", nil)
		return
	}
	v, err := h.svc.ByCustomer(r.Context(), id)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), v)
}

func (h *Handler) ByNumber(w http.ResponseWriter, r *http.Request) {
	v, err := h.svc.ByNumber(r.Context(), chi.URLParam(r, "number"))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), v)
}

func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
	v, err := h.svc.Get(r.Context(), pathID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), v)
}

func (h *Handler) TierHistory(w http.ResponseWriter, r *http.Request) {
	list, err := h.svc.TierHistory(r.Context(), pathID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list)
}

// TierConfigHistory serves the tier CONFIG audit trail — what the tier is worth
// and who changed it. Named distinctly from TierHistory above, which is the
// per-member assignment trail, because the two answer different questions.
func (h *Handler) TierConfigHistory(w http.ResponseWriter, r *http.Request) {
	list, err := h.svc.TierConfigHistory(r.Context(), pathID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list)
}

func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
	var in CreateInput
	if !decodeJSON(w, r, &in) {
		return
	}
	v, err := h.svc.Create(r.Context(), in, actorID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), v)
}

func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
	var in UpdateInput
	if !decodeJSON(w, r, &in) {
		return
	}
	v, err := h.svc.Update(r.Context(), pathID(r), in, actorID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), v)
}

// AccruePoints awards points for spend and auto-promotes the tier when a
// threshold is crossed (§20 membership default).
func (h *Handler) AccruePoints(w http.ResponseWriter, r *http.Request) {
	var in struct {
		AmountSpent float64 `json:"amount_spent"`
	}
	if !decodeJSON(w, r, &in) {
		return
	}
	amount := decimal.NewFromFloat(in.AmountSpent)
	v, err := h.svc.AccruePoints(r.Context(), pathID(r), amount)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), v)
}

// ListTiers returns the database-backed tier configurations (STEP 0, §13.1).
func (h *Handler) ListTiers(w http.ResponseWriter, r *http.Request) {
	list, err := h.svc.ListTiers(r.Context())
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), list)
}

// GetTier returns a single tier configuration by id.
func (h *Handler) GetTier(w http.ResponseWriter, r *http.Request) {
	id := pathID(r)
	c, err := h.svc.GetTier(r.Context(), id)
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), c)
}

// CreateTier creates a tier configuration and records its initial audit entry.
func (h *Handler) CreateTier(w http.ResponseWriter, r *http.Request) {
	var in CreateTierInput
	if !decodeJSON(w, r, &in) {
		return
	}
	c, err := h.svc.CreateTier(r.Context(), in, actorID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusCreated, middleware.RequestIDFrom(r.Context()), c)
}

// UpdateTier changes provided tier-config fields inside one transaction and
// appends a config-history audit entry.
func (h *Handler) UpdateTier(w http.ResponseWriter, r *http.Request) {
	var in UpdateTierInput
	if !decodeJSON(w, r, &in) {
		return
	}
	c, err := h.svc.UpdateTier(r.Context(), pathID(r), in, actorID(r))
	if err != nil {
		respondErr(w, r, err)
		return
	}
	httpresponse.OK(w, http.StatusOK, middleware.RequestIDFrom(r.Context()), c)
}

func pathID(r *http.Request) int64 {
	id, _ := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	return id
}

// decodeJSON answers 400 itself when the body would not decode and returns
// false, so a caller that gets false has already written the response.
func decodeJSON(w http.ResponseWriter, r *http.Request, dst any) bool {
	return httpresponse.DecodeJSON(w, r, middleware.RequestIDFrom(r.Context()), dst)
}

func actorID(r *http.Request) *int64 {
	u := auth.UserFrom(r.Context())
	if u == nil {
		return nil
	}
	return &u.ID
}

func respondErr(w http.ResponseWriter, r *http.Request, err error) {
	rid := middleware.RequestIDFrom(r.Context())
	switch {
	case errors.Is(err, ErrNotFound):
		httpresponse.Error(w, http.StatusNotFound, rid, httpresponse.CodeNotFound, "Not found", nil)
	case errors.Is(err, ErrAlreadyMember):
		httpresponse.Error(w, http.StatusConflict, rid, httpresponse.CodeConflict, "Customer is already a member", nil)
	case errors.Is(err, ErrTierInvalid), errors.Is(err, ErrCustomerInvalid),
		errors.Is(err, ErrReasonRequired), errors.Is(err, ErrJoinedDate),
		errors.Is(err, ErrTierCodeInvalid), errors.Is(err, ErrTierDiscountTypeInvalid),
		errors.Is(err, ErrTierDiscountValueInvalid):
		httpresponse.Error(w, http.StatusUnprocessableEntity, rid, httpresponse.CodeBusinessViolation, err.Error(), nil)
	case errors.Is(err, ErrTierNotFound):
		httpresponse.Error(w, http.StatusNotFound, rid, httpresponse.CodeNotFound, "Membership tier not found", nil)
	case err != nil:
		httpresponse.SafeError(w, rid, logger.FromContext(r.Context()), http.StatusBadRequest, httpresponse.CodeValidation, err)
	}
}
