package activityapi

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"

	activityv1 "hyapp.local/api/proto/activity/v1"
	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/idgen"
	"hyapp/services/gateway-service/internal/auth"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
)

type wheelDrawBody struct {
	Amount    int64  `json:"amount"`
	CommandID string `json:"command_id"`
	RoomID    string `json:"roomId"`
	Times     int32  `json:"times"`
}

type wheelConfigData struct {
	WheelID        string           `json:"wheel_id"`
	Enabled        bool             `json:"enabled"`
	DrawPriceCoins int64            `json:"draw_price_coins"`
	Rewards        []wheelPrizeData `json:"rewards"`
}

type wheelPrizeData struct {
	TierID       string `json:"tier_id"`
	DisplayName  string `json:"display_name"`
	RewardType   string `json:"reward_type"`
	RewardID     string `json:"reward_id"`
	RewardCount  int64  `json:"reward_count"`
	RewardCoins  int64  `json:"reward_coins"`
	Enabled      bool   `json:"enabled"`
	MetadataJSON string `json:"metadata_json"`
	PreviewURL   string `json:"preview_url"`
	AnimationURL string `json:"animation_url"`
	Value        int64  `json:"value"`
}

type wheelDrawData struct {
	DrawID              string           `json:"draw_id"`
	DrawIDs             []string         `json:"draw_ids"`
	CommandID           string           `json:"command_id"`
	WheelID             string           `json:"wheel_id"`
	SelectedTierID      string           `json:"selected_tier_id"`
	RewardType          string           `json:"reward_type"`
	RewardID            string           `json:"reward_id"`
	RewardCount         int64            `json:"reward_count"`
	RewardCoins         int64            `json:"reward_coins"`
	RewardStatus        string           `json:"reward_status"`
	WalletTransactionID string           `json:"wallet_transaction_id"`
	CoinBalanceAfter    int64            `json:"coin_balance_after"`
	MetadataJSON        string           `json:"metadata_json"`
	RewardValue         int64            `json:"rewardValue"`
	Rewards             []wheelPrizeData `json:"rewards"`
}

// getWheelConfig 返回 H5 渲染所需的当前奖池配置；只暴露资源图字段，不暴露后台 RTP 控制窗口。
func (h *Handler) getWheelConfig(writer http.ResponseWriter, request *http.Request) {
	if h.wheel == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	resp, err := h.wheel.GetWheelConfig(request.Context(), &activityv1.GetWheelConfigRequest{
		Meta:    httpkit.ActivityMeta(request),
		WheelId: wheelIDFromRequest(request),
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	httpkit.WriteOK(writer, request, wheelConfigFromProto(resp.GetConfig()))
}

// drawWheel 先完成钱包扣费，再把扣费事实传给 activity-service 抽奖；任何一步失败都直接返回错误，不伪造中奖。
func (h *Handler) drawWheel(writer http.ResponseWriter, request *http.Request) {
	if h.wheel == nil || h.walletClient == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	var body wheelDrawBody
	if err := httpkit.DecodeJSON(request.Body, &body); err != nil && !errors.Is(err, io.EOF) {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidJSON, "invalid request body")
		return
	}
	wheelID := wheelIDFromRequest(request)
	times := body.Times
	if times <= 0 {
		times = 1
	}
	configResp, err := h.wheel.GetWheelConfig(request.Context(), &activityv1.GetWheelConfigRequest{
		Meta:    httpkit.ActivityMeta(request),
		WheelId: wheelID,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	config := configResp.GetConfig()
	if config == nil || !config.GetEnabled() {
		httpkit.WriteError(writer, request, http.StatusConflict, httpkit.CodeInvalidArgument, "wheel disabled")
		return
	}
	expectedAmount := config.GetDrawPriceCoins() * int64(times)
	if body.Amount > 0 && body.Amount != expectedAmount {
		httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "draw amount mismatch")
		return
	}
	commandID := strings.TrimSpace(body.CommandID)
	if commandID == "" {
		commandID = idgen.New("wheel")
	}
	debitResp, err := h.walletClient.DebitWheelDraw(request.Context(), &walletv1.DebitWheelDrawRequest{
		CommandId: commandID,
		AppCode:   appcode.FromContext(request.Context()),
		UserId:    auth.UserIDFromContext(request.Context()),
		WheelId:   wheelID,
		DrawCount: times,
		Amount:    expectedAmount,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	drawResp, err := h.wheel.ExecuteWheelDraw(request.Context(), &activityv1.ExecuteWheelDrawRequest{
		Wheel: &activityv1.WheelDrawMeta{
			Meta:            httpkit.ActivityMeta(request),
			CommandId:       commandID,
			WheelId:         wheelID,
			UserId:          auth.UserIDFromContext(request.Context()),
			DeviceId:        wheelDeviceID(request),
			DrawCount:       times,
			CoinSpent:       debitResp.GetCoinSpent(),
			PaidAtMs:        time.Now().UTC().UnixMilli(),
			VisibleRegionId: 0,
		},
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	data := wheelDrawFromProto(drawResp.GetResult())
	data.WalletTransactionID = debitResp.GetTransactionId()
	data.CoinBalanceAfter = debitResp.GetCoinBalanceAfter()
	httpkit.WriteOK(writer, request, data)
}

// listWheelHistory 返回当前用户的转盘抽奖记录；读侧复用 activity-service 明细，避免 H5 直接拼后台接口。
func (h *Handler) listWheelHistory(writer http.ResponseWriter, request *http.Request) {
	if h.wheel == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	resp, err := h.wheel.ListWheelDraws(request.Context(), &activityv1.ListWheelDrawsRequest{
		Meta:     httpkit.ActivityMeta(request),
		WheelId:  wheelIDFromRequest(request),
		UserId:   auth.UserIDFromContext(request.Context()),
		Page:     int32(queryInt(request, "pageNo", 1)),
		PageSize: int32(queryInt(request, "pageSize", 20)),
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	records := make([]map[string]any, 0, len(resp.GetDraws()))
	for _, item := range resp.GetDraws() {
		draw := wheelDrawFromProto(item)
		records = append(records, map[string]any{
			"createdTime": item.GetCreatedAtMs(),
			"drawTimes":   len(item.GetDrawIds()),
			"rewardValue": draw.RewardValue,
			"rewards":     draw.Rewards,
		})
	}
	httpkit.WriteOK(writer, request, map[string]any{"records": records, "total": resp.GetTotal()})
}

// listWheelHints 返回最近中奖提示；昵称不在 activity-service 明细里，这里用用户 ID 占位，后续可接 user profile 批量补齐。
func (h *Handler) listWheelHints(writer http.ResponseWriter, request *http.Request) {
	if h.wheel == nil {
		httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
		return
	}
	resp, err := h.wheel.ListWheelDraws(request.Context(), &activityv1.ListWheelDrawsRequest{
		Meta:     httpkit.ActivityMeta(request),
		WheelId:  wheelIDFromRequest(request),
		Status:   "granted",
		Page:     1,
		PageSize: 20,
	})
	if err != nil {
		httpkit.WriteRPCError(writer, request, err)
		return
	}
	values := make([]map[string]any, 0, len(resp.GetDraws()))
	for _, item := range resp.GetDraws() {
		reward := wheelPrizeFromDraw(item)
		values = append(values, map[string]any{
			"avatar":   reward.PreviewURL,
			"nickname": fmt.Sprintf("Winner %d", len(values)+1),
			"value":    reward.DisplayName,
		})
	}
	httpkit.WriteOK(writer, request, map[string]any{"values": values})
}

func wheelIDFromRequest(request *http.Request) string {
	value := strings.TrimSpace(request.PathValue("wheel_id"))
	if value == "" {
		return "classic"
	}
	return value
}

func wheelDeviceID(request *http.Request) string {
	for _, value := range []string{
		httpkit.FirstQueryOrHeader(request, "device_id", "X-Device-ID", "X-Hyapp-Device-ID"),
		auth.SessionIDFromContext(request.Context()),
	} {
		if strings.TrimSpace(value) != "" {
			return value
		}
	}
	return "h5"
}

func wheelConfigFromProto(config *activityv1.WheelRuleConfig) wheelConfigData {
	if config == nil {
		return wheelConfigData{Rewards: []wheelPrizeData{}}
	}
	rewards := make([]wheelPrizeData, 0, len(config.GetTiers()))
	for _, tier := range config.GetTiers() {
		rewards = append(rewards, wheelPrizeFromTier(tier))
	}
	return wheelConfigData{
		WheelID:        config.GetWheelId(),
		Enabled:        config.GetEnabled(),
		DrawPriceCoins: config.GetDrawPriceCoins(),
		Rewards:        rewards,
	}
}

func wheelPrizeFromTier(tier *activityv1.WheelPrizeTier) wheelPrizeData {
	if tier == nil {
		return wheelPrizeData{}
	}
	previewURL, animationURL := wheelAssetURLs(tier.GetRewardType(), tier.GetMetadataJson())
	return wheelPrizeData{
		TierID:       tier.GetTierId(),
		DisplayName:  tier.GetDisplayName(),
		RewardType:   tier.GetRewardType(),
		RewardID:     tier.GetRewardId(),
		RewardCount:  tier.GetRewardCount(),
		RewardCoins:  tier.GetRewardCoins(),
		Enabled:      tier.GetEnabled(),
		MetadataJSON: tier.GetMetadataJson(),
		PreviewURL:   previewURL,
		AnimationURL: animationURL,
		Value:        wheelPrizeDisplayValue(tier.GetRewardCoins(), tier.GetRtpValueCoins(), tier.GetRewardCount(), tier.GetMetadataJson()),
	}
}

func wheelDrawFromProto(item *activityv1.WheelDrawResult) wheelDrawData {
	if item == nil {
		return wheelDrawData{Rewards: []wheelPrizeData{}}
	}
	reward := wheelPrizeFromDraw(item)
	rewards := make([]wheelPrizeData, 0, len(item.GetRewards()))
	for _, detail := range item.GetRewards() {
		rewards = append(rewards, wheelPrizeFromDrawReward(detail))
	}
	if len(rewards) == 0 {
		rewards = []wheelPrizeData{reward}
	}
	rewards = mergeWheelPrizeData(rewards)
	return wheelDrawData{
		DrawID:              item.GetDrawId(),
		DrawIDs:             item.GetDrawIds(),
		CommandID:           item.GetCommandId(),
		WheelID:             item.GetWheelId(),
		SelectedTierID:      item.GetSelectedTierId(),
		RewardType:          item.GetRewardType(),
		RewardID:            item.GetRewardId(),
		RewardCount:         item.GetRewardCount(),
		RewardCoins:         item.GetRewardCoins(),
		RewardStatus:        item.GetRewardStatus(),
		WalletTransactionID: item.GetWalletTransactionId(),
		CoinBalanceAfter:    item.GetCoinBalanceAfter(),
		MetadataJSON:        item.GetMetadataJson(),
		RewardValue:         wheelPrizeTotalValue(rewards),
		Rewards:             rewards,
	}
}

func wheelPrizeFromDraw(item *activityv1.WheelDrawResult) wheelPrizeData {
	if item == nil {
		return wheelPrizeData{}
	}
	previewURL, animationURL := wheelAssetURLs(item.GetRewardType(), item.GetMetadataJson())
	return wheelPrizeData{
		TierID:       item.GetSelectedTierId(),
		DisplayName:  item.GetSelectedTierId(),
		RewardType:   item.GetRewardType(),
		RewardID:     item.GetRewardId(),
		RewardCount:  item.GetRewardCount(),
		RewardCoins:  item.GetRewardCoins(),
		MetadataJSON: item.GetMetadataJson(),
		PreviewURL:   previewURL,
		AnimationURL: animationURL,
		Value:        wheelPrizeDisplayValue(item.GetRewardCoins(), item.GetRtpValueCoins(), item.GetRewardCount(), item.GetMetadataJson()),
	}
}

func wheelPrizeFromDrawReward(item *activityv1.WheelDrawReward) wheelPrizeData {
	if item == nil {
		return wheelPrizeData{}
	}
	previewURL, animationURL := wheelAssetURLs(item.GetRewardType(), item.GetMetadataJson())
	return wheelPrizeData{
		TierID:       item.GetSelectedTierId(),
		DisplayName:  item.GetSelectedTierId(),
		RewardType:   item.GetRewardType(),
		RewardID:     item.GetRewardId(),
		RewardCount:  item.GetRewardCount(),
		RewardCoins:  item.GetRewardCoins(),
		MetadataJSON: item.GetMetadataJson(),
		PreviewURL:   previewURL,
		AnimationURL: animationURL,
		Value:        wheelPrizeDisplayValue(item.GetRewardCoins(), item.GetRtpValueCoins(), item.GetRewardCount(), item.GetMetadataJson()),
	}
}

func mergeWheelPrizeData(items []wheelPrizeData) []wheelPrizeData {
	merged := make([]wheelPrizeData, 0, len(items))
	byKey := make(map[string]int, len(items))
	for _, item := range items {
		key := wheelPrizeMergeKey(item)
		if index, ok := byKey[key]; ok {
			merged[index].RewardCount += item.RewardCount
			continue
		}
		byKey[key] = len(merged)
		merged = append(merged, item)
	}
	return merged
}

func wheelPrizeTotalValue(items []wheelPrizeData) int64 {
	var total int64
	for _, item := range items {
		count := item.RewardCount
		if count <= 0 {
			count = 1
		}
		total += item.Value * count
	}
	return total
}

func wheelPrizeMergeKey(item wheelPrizeData) string {
	return strings.Join([]string{
		item.RewardType,
		item.RewardID,
		item.MetadataJSON,
		item.PreviewURL,
		item.AnimationURL,
		fmt.Sprintf("%d", item.Value),
	}, "|")
}

func wheelPrizeDisplayValue(rewardCoins int64, internalValueCoins int64, rewardCount int64, metadataJSON string) int64 {
	// H5 只需要一个展示值，不能把后台 RTP 字段原样暴露出去；金币奖品展示金币数，礼物可展示内部估值。
	// 道具和装扮不计入 RTP 时内部估值会是 0，所以优先读取 metadata 里资源原价，避免页面只能展示 reward_count=1。
	if rewardCoins > 0 {
		return rewardCoins
	}
	if displayValue := wheelMetadataDisplayValue(metadataJSON); displayValue > 0 {
		return displayValue
	}
	if internalValueCoins > 0 {
		return internalValueCoins
	}
	if rewardCount > 0 {
		return rewardCount
	}
	return 1
}

func wheelMetadataDisplayValue(metadataJSON string) int64 {
	var metadata map[string]any
	if err := json.NewDecoder(strings.NewReader(strings.TrimSpace(metadataJSON))).Decode(&metadata); err != nil && err != io.EOF {
		return 0
	}
	return firstMetadataInt64(metadata, "display_value", "displayValue", "coin_price", "coinPrice", "price", "value")
}

func wheelAssetURLs(rewardType string, metadataJSON string) (string, string) {
	var metadata map[string]any
	if err := json.NewDecoder(strings.NewReader(strings.TrimSpace(metadataJSON))).Decode(&metadata); err != nil && err != io.EOF {
		return "", ""
	}
	preview := firstMetadataString(metadata, "preview_url", "previewUrl", "asset_url", "assetUrl")
	if strings.EqualFold(strings.TrimSpace(rewardType), "coin") {
		return preview, ""
	}
	animation := firstMetadataString(metadata, "animation_url", "animationUrl")
	return preview, animation
}

func firstMetadataString(metadata map[string]any, keys ...string) string {
	for _, key := range keys {
		if value, ok := metadata[key].(string); ok && strings.TrimSpace(value) != "" {
			return value
		}
	}
	return ""
}

func firstMetadataInt64(metadata map[string]any, keys ...string) int64 {
	for _, key := range keys {
		value, ok := metadata[key]
		if !ok {
			continue
		}
		switch typed := value.(type) {
		case float64:
			if typed > 0 {
				return int64(typed)
			}
		case string:
			parsed, err := strconv.ParseInt(strings.TrimSpace(typed), 10, 64)
			if err == nil && parsed > 0 {
				return parsed
			}
		}
	}
	return 0
}
