package commerce

import (
	"bytes"
	"encoding/json"
	"errors"
	"io"
	"log/slog"
	"net/http"
	"strings"

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

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

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

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

func (h *RechargeHandler) Routes() http.Handler {
	r := chi.NewRouter()
	r.Post("/notify", h.notify)
	r.Get("/notify", h.notify)
	r.Post("/notify/{plugin_id}", h.notify)
	r.Get("/notify/{plugin_id}", h.notify)
	r.Group(func(r chi.Router) {
		r.Use(h.auth.RequireUser)
		r.Get("/payment-options", h.paymentOptions)
		r.Post("/", h.create)
		r.Get("/pending", h.pending)
		r.Get("/status/{out_trade_no}", h.status)
		r.Get("/orders", h.orders)
	})
	return r
}

func (h *RechargeHandler) paymentOptions(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.RechargePaymentOptions(r.Context())
	h.writeResult(w, data, err)
}

func (h *RechargeHandler) create(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	var req RechargeCreateRequest
	if !decodeRechargeJSON(w, r, &req) {
		return
	}
	data, err := h.service.CreateRecharge(r.Context(), userID, req, clientIP(r))
	h.writeResult(w, data, err)
}

func (h *RechargeHandler) pending(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	data, err := h.service.PendingRecharge(r.Context(), userID)
	h.writeResult(w, data, err)
}

func (h *RechargeHandler) status(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	data, err := h.service.RechargeStatus(r.Context(), userID, chi.URLParam(r, "out_trade_no"))
	h.writeResult(w, data, err)
}

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

func (h *RechargeHandler) notify(w http.ResponseWriter, r *http.Request) {
	raw, _ := io.ReadAll(r.Body)
	_ = r.Body.Close()
	params := queryStringMap(r)
	if len(raw) > 0 {
		r.Body = io.NopCloser(bytes.NewReader(raw))
		if strings.Contains(strings.ToLower(r.Header.Get("Content-Type")), "application/json") {
			var body map[string]any
			if err := json.NewDecoder(bytes.NewReader(raw)).Decode(&body); err == nil {
				for key, value := range body {
					params[key] = toString(value)
				}
			}
		} else if err := r.ParseForm(); err == nil {
			for key, values := range r.PostForm {
				if len(values) > 0 {
					params[key] = values[0]
				}
			}
		} else {
			response.Detail(w, http.StatusBadRequest, msgInvalidParams)
			return
		}
	}
	if len(params) == 0 && len(raw) > 0 {
		var body map[string]any
		if err := json.NewDecoder(bytes.NewReader(raw)).Decode(&body); err == nil {
			for key, value := range body {
				params[key] = toString(value)
			}
		}
	}
	data, err := h.service.HandleRechargeNotify(r.Context(), RechargeNotifyRequest{
		PluginID: chi.URLParam(r, "plugin_id"),
		Method:   r.Method,
		Headers:  headerMap(r),
		Params:   params,
		Body:     string(raw),
	})
	if err != nil {
		h.writeError(w, err)
		return
	}
	if ok, _ := data["ok"].(bool); ok {
		_, _ = w.Write([]byte("success"))
		return
	}
	response.OK(w, data)
}

func queryStringMap(r *http.Request) map[string]string {
	out := map[string]string{}
	for key, values := range r.URL.Query() {
		if len(values) > 0 {
			out[key] = values[0]
		}
	}
	return out
}

func headerMap(r *http.Request) map[string]string {
	out := map[string]string{}
	for key, values := range r.Header {
		if len(values) > 0 {
			out[key] = values[0]
		}
	}
	return out
}

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

func (h *RechargeHandler) 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("recharge handler error", "error", err)
	}
	response.Detail(w, http.StatusInternalServerError, msgServiceError)
}

func decodeRechargeJSON(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
}

func clientIP(r *http.Request) string {
	if value := r.Header.Get("X-Forwarded-For"); value != "" {
		return value
	}
	if value := r.Header.Get("X-Real-IP"); value != "" {
		return value
	}
	return r.RemoteAddr
}
