package commerce

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

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

	qmiddleware "qycw/go-server/internal/middleware"
	"qycw/go-server/internal/response"
)

type UserOpsHandler struct {
	service *Service
	auth    *qmiddleware.Auth
	logger  *slog.Logger
}

func NewUserOpsHandler(service *Service, auth *qmiddleware.Auth, logger *slog.Logger) *UserOpsHandler {
	return &UserOpsHandler{service: service, auth: auth, logger: logger}
}

func (h *UserOpsHandler) MembershipRoutes() http.Handler {
	r := chi.NewRouter()
	r.Get("/levels", h.memberLevels)
	r.With(h.auth.RequireUser).Get("/me", h.myMembership)
	r.With(h.auth.RequireUser).Get("/points/logs", h.pointLogs)
	return r
}

func (h *UserOpsHandler) PointsRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireUser)
	r.Get("/center", h.pointsCenter)
	r.Post("/tasks/qq", h.completeTask("qq_community"))
	r.Post("/tasks/bilibili", h.completeTask("bilibili_follow"))
	r.Post("/tasks/checkin", h.checkin)
	r.Post("/withdraw", h.withdraw)
	return r
}

func (h *UserOpsHandler) PromotionRoutes() http.Handler {
	r := chi.NewRouter()
	r.Get("/aff-info", h.affInfo)
	r.Post("/aff-click", h.affClick)
	r.Group(func(r chi.Router) {
		r.Use(h.auth.RequireUser)
		r.Get("/me", h.promotionOverview)
		r.Patch("/invite-code", h.updateInviteCode)
		r.Get("/coupons", h.listCoupons)
		r.Post("/coupons", h.createCoupon)
		r.Get("/mall/items", h.mallItems)
		r.Post("/mall/claim", h.claimMall)
		r.Get("/mall/my-claims", h.myMallClaims)
		r.Post("/coupons/{coupon_id}/disable", h.disableCoupon)
		r.Get("/rewards", h.promotionRewards)
	})
	return r
}

func (h *UserOpsHandler) AgentRoutes() http.Handler {
	r := chi.NewRouter()
	r.Get("/aff-info", h.affInfo)
	r.Post("/aff-click", h.affClick)
	r.With(h.auth.RequireUser).Get("/me", h.agentOverview)
	r.Get("/tiers", h.agentTiers)
	r.With(h.auth.RequireUser).Post("/apply", h.applyAgent)
	r.With(h.auth.RequireUser).Get("/customers", h.agentCustomers)
	r.With(h.auth.RequireUser).Get("/settlements", h.agentSettlements)
	r.With(h.auth.RequireUser).Get("/invite-code", h.agentInviteCode)
	return r
}

func (h *UserOpsHandler) memberLevels(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.ListMemberLevels(r.Context())
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) myMembership(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	data, err := h.service.GetMyMembership(r.Context(), userID)
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) pointLogs(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ListMyPointLogs(r.Context(), userID, queryInt(r.URL.Query().Get("limit"), 20, 1, 100), queryInt(r.URL.Query().Get("offset"), 0, 0, 1000000))
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) pointsCenter(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	data, err := h.service.PointsCenter(r.Context(), userID)
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) completeTask(taskType string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		userID, ok := h.userID(w, r)
		if !ok {
			return
		}
		var req TaskAnswerRequest
		if !decodeUserOpsJSON(w, r, &req) {
			return
		}
		data, err := h.service.CompletePointTask(r.Context(), userID, taskType, req.Answer)
		h.writeResult(w, data, err)
	}
}

func (h *UserOpsHandler) checkin(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	if r.Body != nil && r.ContentLength != 0 {
		var ignored map[string]any
		if !decodeUserOpsJSON(w, r, &ignored) {
			return
		}
	}
	data, err := h.service.CompletePointTask(r.Context(), userID, "daily_checkin", "")
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) withdraw(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	var req WithdrawPointsRequest
	if !decodeUserOpsJSON(w, r, &req) {
		return
	}
	data, err := h.service.WithdrawPoints(r.Context(), userID, req.Points)
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) promotionOverview(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	data, err := h.service.PromotionOverview(r.Context(), userID)
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) updateInviteCode(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	var req InviteCodeUpdateRequest
	if !decodeUserOpsJSON(w, r, &req) {
		return
	}
	data, err := h.service.UpdateInviteCode(r.Context(), userID, req.InviteCode)
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) listCoupons(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ListPromotionCoupons(r.Context(), userID, queryInt(r.URL.Query().Get("limit"), 50, 1, 100), queryInt(r.URL.Query().Get("offset"), 0, 0, 1000000))
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) createCoupon(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	var req PromotionCouponCreateRequest
	if !decodeUserOpsJSON(w, r, &req) {
		return
	}
	data, err := h.service.CreatePromotionCoupon(r.Context(), userID, req)
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) mallItems(w http.ResponseWriter, r *http.Request) {
	userID, _ := h.userID(w, r)
	query := map[string]string{}
	for _, key := range []string{"scope", "q", "type", "sort_by", "min_points", "max_points", "is_hot"} {
		query[key] = r.URL.Query().Get(key)
	}
	data, err := h.service.ListMallItems(r.Context(), userID, query, queryInt(r.URL.Query().Get("limit"), 50, 1, 100), queryInt(r.URL.Query().Get("offset"), 0, 0, 1000000))
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) claimMall(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	var req MallClaimRequest
	if !decodeUserOpsJSON(w, r, &req) {
		return
	}
	data, err := h.service.ClaimMallCoupon(r.Context(), userID, req.MallItemID)
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) myMallClaims(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ListMyMallClaims(r.Context(), userID, r.URL.Query().Get("status"))
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) disableCoupon(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	couponID, ok := parseID(w, chi.URLParam(r, "coupon_id"))
	if !ok {
		return
	}
	data, err := h.service.DisablePromotionCoupon(r.Context(), userID, couponID)
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) promotionRewards(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	data, err := h.service.PromotionRewards(r.Context(), userID, queryInt(r.URL.Query().Get("limit"), 20, 1, 100), queryInt(r.URL.Query().Get("offset"), 0, 0, 1000000))
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) agentOverview(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	data, err := h.service.AgentOverview(r.Context(), userID)
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) agentTiers(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.ListAgentTiers(r.Context())
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) applyAgent(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	var req AgentApplyRequest
	if !decodeUserOpsJSON(w, r, &req) {
		return
	}
	data, err := h.service.ApplyAgent(r.Context(), userID, req)
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) agentCustomers(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ListAgentCustomers(r.Context(), userID, queryInt(r.URL.Query().Get("limit"), 20, 1, 100), queryInt(r.URL.Query().Get("offset"), 0, 0, 1000000))
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) agentSettlements(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ListAgentSettlements(r.Context(), userID, queryInt(r.URL.Query().Get("limit"), 20, 1, 100), queryInt(r.URL.Query().Get("offset"), 0, 0, 1000000))
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) agentInviteCode(w http.ResponseWriter, r *http.Request) {
	userID, ok := h.userID(w, r)
	if !ok {
		return
	}
	data, err := h.service.PromotionOverview(r.Context(), userID)
	if err == nil {
		data = map[string]any{"invite_code": data["invite_code"], "invite_link": data["invite_link"], "register_link": data["register_link"]}
	}
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) affInfo(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AffiliateInfo(r.Context(), r.URL.Query().Get("code"))
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) affClick(w http.ResponseWriter, r *http.Request) {
	var req AffiliateClickRequest
	if !decodeUserOpsJSON(w, r, &req) {
		return
	}
	data, err := h.service.RecordAffiliateClick(r.Context(), req, r)
	h.writeResult(w, data, err)
}

func (h *UserOpsHandler) userID(w http.ResponseWriter, r *http.Request) (int64, bool) {
	principal, ok := qmiddleware.PrincipalFromContext(r.Context())
	if !ok || principal.ID <= 0 {
		response.Detail(w, http.StatusUnauthorized, "Could not validate credentials")
		return 0, false
	}
	return principal.ID, true
}

func (h *UserOpsHandler) writeResult(w http.ResponseWriter, data any, err error) {
	if err != nil {
		h.writeError(w, err)
		return
	}
	response.OK(w, data)
}

func (h *UserOpsHandler) writeError(w http.ResponseWriter, err error) {
	var httpErr HTTPError
	if errors.As(err, &httpErr) {
		response.Detail(w, httpErr.Status, httpErr.Detail)
		return
	}
	if h.logger != nil {
		h.logger.Error("user ops handler error", "error", err)
	}
	response.Detail(w, http.StatusInternalServerError, msgServiceError)
}

func decodeUserOpsJSON(w http.ResponseWriter, r *http.Request, out any) bool {
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(out); err != nil {
		response.Detail(w, http.StatusBadRequest, msgInvalidParams)
		return false
	}
	return true
}
