package usage

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

	"github.com/wangjia/pangolin/server/internal/apierr"
	"github.com/wangjia/pangolin/server/internal/codes"
)

// userIDFromContext extracts the authenticated user ID set by the auth
// middleware, or 0 if absent.
//
// 关键:必须读 codes.CtxKeyUserID —— auth.RequireAuth 正是把 user id 存在这个键
// 下。此前本包自定义了同名常量 ctxKey("user_id"),但 Go 的 context key 按
// 「类型+值」比较,命名类型不同 → 取不到 → /v1/usage 恒 401(统计页全 0)。
func userIDFromContext(r *http.Request) int64 {
	id, _ := r.Context().Value(codes.CtxKeyUserID).(int64)
	return id
}

// UsageHandler serves GET /v1/usage?days=N.
type UsageHandler struct {
	svc *Service
}

// NewUsageHandler creates a UsageHandler.
func NewUsageHandler(svc *Service) *UsageHandler { return &UsageHandler{svc: svc} }

type usageResponse struct {
	Points []UsagePoint `json:"points"`
}

// ServeHTTP implements http.Handler.
func (h *UsageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	userID := userIDFromContext(r)
	if userID == 0 {
		apierr.WriteJSON(w, http.StatusUnauthorized, &apierr.Error{
			Code:      "UNAUTHORIZED",
			MessageZH: "请先登录",
			MessageEn: "Authentication required",
		})
		return
	}

	days := 7
	if v := r.URL.Query().Get("days"); v != "" {
		n, err := strconv.Atoi(v)
		if err != nil || n < 1 || n > 90 {
			apierr.WriteJSON(w, http.StatusBadRequest, apierr.ErrBadRequest)
			return
		}
		days = n
	}

	points, apiErr := h.svc.UsageCurve(r.Context(), userID, days)
	if apiErr != nil {
		apierr.WriteJSON(w, http.StatusInternalServerError, apiErr)
		return
	}

	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	_ = json.NewEncoder(w).Encode(usageResponse{Points: points})
}

// AdsUnlockHandler serves POST /v1/ads/unlock.
type AdsUnlockHandler struct {
	svc *Service
}

// NewAdsUnlockHandler creates an AdsUnlockHandler.
func NewAdsUnlockHandler(svc *Service) *AdsUnlockHandler { return &AdsUnlockHandler{svc: svc} }

type adsUnlockRequest struct {
	DeviceID string `json:"device_id"`
	AdToken  string `json:"ad_token"`
}

// ServeHTTP implements http.Handler.  On success it returns 204 No Content
// (matching the OpenAPI contract), including the idempotent already-unlocked
// case.
func (h *AdsUnlockHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
		return
	}
	userID := userIDFromContext(r)
	if userID == 0 {
		apierr.WriteJSON(w, http.StatusUnauthorized, &apierr.Error{
			Code:      "UNAUTHORIZED",
			MessageZH: "请先登录",
			MessageEn: "Authentication required",
		})
		return
	}

	var req adsUnlockRequest
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 16*1024)).Decode(&req); err != nil {
		apierr.WriteJSON(w, http.StatusBadRequest, apierr.ErrBadRequest)
		return
	}

	_, apiErr := h.svc.UnlockAd(r.Context(), userID, req.DeviceID, req.AdToken)
	if apiErr != nil {
		apierr.WriteJSON(w, adsErrorStatus(apiErr.Code), apiErr)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// adsErrorStatus maps an ads-unlock error code to an HTTP status.
func adsErrorStatus(code string) int {
	switch code {
	case "AD_VERIFY_FAILED":
		return http.StatusForbidden
	case "AD_TOKEN_REPLAY":
		return http.StatusConflict
	case "BAD_REQUEST":
		return http.StatusBadRequest
	case "INTERNAL_ERROR":
		return http.StatusInternalServerError
	default:
		return http.StatusBadRequest
	}
}
