package roomapi

import (
	"context"
	"net/http"
	"strconv"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/services/gateway-service/internal/auth"
	"hyapp/services/gateway-service/internal/transport/http/giftlimit"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
)

const giftCapacityReleaseTimeout = time.Second

type giftRateLimitedData struct {
	RetryAfterMS int64 `json:"retry_after_ms"`
}

// acquireGiftCapacity 在任何用户资料或 room-service RPC 前占用容量。
// 返回的 lease 必须 defer 释放；令牌代表本次请求已经给下游造成的成本，不因后续业务失败退还。
func (h *Handler) acquireGiftCapacity(writer http.ResponseWriter, request *http.Request, roomID string, totalGiftUnits int) (giftlimit.Lease, bool) {
	if !h.giftCapacityEnabled {
		return giftlimit.Lease{}, true
	}
	if h.giftCapacityLimiter == nil {
		// 生产配置要求保护默认开启；后端缺失时 fail-closed，不能在 Redis/装配故障时静默放开洪峰。
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return giftlimit.Lease{}, false
	}
	if totalGiftUnits < 1 {
		totalGiftUnits = 1
	}

	decision, err := h.giftCapacityLimiter.Acquire(request.Context(), giftlimit.Input{
		AppCode:   appcode.FromContext(request.Context()),
		UserID:    auth.UserIDFromContext(request.Context()),
		RoomID:    roomID,
		RequestID: httpkit.ResponseRequestID(request),
		Weight:    totalGiftUnits,
	})
	if err != nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return giftlimit.Lease{}, false
	}
	if decision.Allowed {
		return decision.Lease, true
	}

	retryAfter := decision.RetryAfter
	if retryAfter < time.Millisecond {
		retryAfter = time.Millisecond
	}
	retryAfterMS := retryAfter.Milliseconds()
	// Retry-After 是标准 HTTP 秒级提示；data.retry_after_ms 保留连击送礼所需的毫秒精度。
	writer.Header().Set("Retry-After", strconv.FormatInt((retryAfterMS+999)/1000, 10))
	httpkit.WriteEnvelope(writer, http.StatusTooManyRequests, httpkit.ResponseEnvelope{
		Code:      httpkit.CodeRateLimited,
		Message:   "rate limited",
		RequestID: httpkit.ResponseRequestID(request),
		Data:      giftRateLimitedData{RetryAfterMS: retryAfterMS},
	})
	return giftlimit.Lease{}, false
}

// releaseGiftCapacity 使用与客户端断连解耦的短 context；Redis 暂时失败时租约 TTL 仍会兜底回收。
func (h *Handler) releaseGiftCapacity(lease giftlimit.Lease) {
	if h.giftCapacityLimiter == nil {
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), giftCapacityReleaseTimeout)
	defer cancel()
	_ = h.giftCapacityLimiter.Release(ctx, lease)
}
