package wheel

import (
	"context"
	"fmt"
	"math"
	"strings"
	"time"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/integration/activityclient"
	"hyapp-admin-server/internal/middleware"
	"hyapp-admin-server/internal/modules/shared"
	"hyapp-admin-server/internal/response"
	activityv1 "hyapp.local/api/proto/activity/v1"

	"github.com/gin-gonic/gin"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

const defaultWheelID = "default"

var wheelTierCounts = map[string]int{
	"classic":  8,
	"luxury":   8,
	"advanced": 12,
}

type Handler struct {
	activity       activityclient.Client
	requestTimeout time.Duration
	audit          shared.OperationLogger
}

func New(activity activityclient.Client, requestTimeout time.Duration, audit shared.OperationLogger) *Handler {
	if requestTimeout <= 0 {
		requestTimeout = 3 * time.Second
	}
	return &Handler{activity: activity, requestTimeout: requestTimeout, audit: audit}
}

type configRequest struct {
	WheelID               string    `json:"wheel_id"`
	Enabled               bool      `json:"enabled"`
	DrawPriceCoins        int64     `json:"draw_price_coins"`
	TargetRTPPercent      float64   `json:"target_rtp_percent"`
	PoolRatePercent       float64   `json:"pool_rate_percent"`
	SettlementWindowDraws int64     `json:"settlement_window_draws"`
	InitialPoolCoins      int64     `json:"initial_pool_coins"`
	PoolReserveCoins      int64     `json:"pool_reserve_coins"`
	MaxSingleRTPPayout    int64     `json:"max_single_rtp_payout"`
	EffectiveFromMS       int64     `json:"effective_from_ms"`
	Tiers                 []tierDTO `json:"tiers"`
}

type configDTO struct {
	configRequest
	AppCode          string `json:"app_code"`
	RuleVersion      int64  `json:"rule_version"`
	CreatedByAdminID int64  `json:"created_by_admin_id"`
	CreatedAtMS      int64  `json:"created_at_ms"`
}

type tierDTO 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"`
	RTPValueCoins      int64   `json:"rtp_value_coins"`
	ProbabilityPercent float64 `json:"probability_percent"`
	Enabled            bool    `json:"enabled"`
	MetadataJSON       string  `json:"metadata_json"`
}

type drawDTO struct {
	DrawID              string   `json:"draw_id"`
	DrawIDs             []string `json:"draw_ids"`
	CommandID           string   `json:"command_id"`
	WheelID             string   `json:"wheel_id"`
	RuleVersion         int64    `json:"rule_version"`
	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"`
	RTPValueCoins       int64    `json:"rtp_value_coins"`
	RewardStatus        string   `json:"reward_status"`
	RTPWindowIndex      int64    `json:"rtp_window_index"`
	ActualRTPPPM        int64    `json:"actual_rtp_ppm"`
	CreatedAtMS         int64    `json:"created_at_ms"`
	WalletTransactionID string   `json:"wallet_transaction_id"`
	CoinBalanceAfter    int64    `json:"coin_balance_after"`
	MetadataJSON        string   `json:"metadata_json"`
}

type drawSummaryDTO struct {
	WheelID            string `json:"wheel_id"`
	TotalDraws         int64  `json:"total_draws"`
	UniqueUsers        int64  `json:"unique_users"`
	TotalSpentCoins    int64  `json:"total_spent_coins"`
	TotalRTPValueCoins int64  `json:"total_rtp_value_coins"`
	ActualRTPPPM       int64  `json:"actual_rtp_ppm"`
	PendingDraws       int64  `json:"pending_draws"`
	GrantedDraws       int64  `json:"granted_draws"`
	FailedDraws        int64  `json:"failed_draws"`
}

func (h *Handler) GetConfig(c *gin.Context) {
	wheelID := normalizeWheelID(firstQuery(c, "wheel_id", "wheelId"))
	ctx, cancel := h.activityContext(c)
	defer cancel()
	resp, err := h.activity.GetWheelConfig(ctx, &activityv1.GetWheelConfigRequest{Meta: h.meta(c), WheelId: wheelID})
	if err != nil {
		if status.Code(err) == codes.NotFound {
			response.OK(c, defaultConfigDTO(wheelID))
			return
		}
		response.ServerError(c, "获取转盘配置失败")
		return
	}
	response.OK(c, configFromProto(resp.GetConfig()))
}

func (h *Handler) UpsertConfig(c *gin.Context) {
	var req configRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "转盘配置参数不正确")
		return
	}
	if req.WheelID == "" {
		req.WheelID = firstQuery(c, "wheel_id", "wheelId")
	}
	if err := validateConfigRequest(req); err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	ctx, cancel := h.activityContext(c)
	defer cancel()
	resp, err := h.activity.UpsertWheelConfig(ctx, &activityv1.UpsertWheelConfigRequest{
		Meta:            h.meta(c),
		Config:          configToProto(req),
		OperatorAdminId: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	item := configFromProto(resp.GetConfig())
	shared.OperationLogWithResourceID(c, h.audit, "upsert-wheel-config", "wheel_rule_versions", item.WheelID, "success", "")
	response.OK(c, item)
}

func (h *Handler) ListDraws(c *gin.Context) {
	options := shared.ListOptions(c)
	ctx, cancel := h.activityContext(c)
	defer cancel()
	resp, err := h.activity.ListWheelDraws(ctx, &activityv1.ListWheelDrawsRequest{
		Meta:     h.meta(c),
		WheelId:  normalizeWheelID(firstQuery(c, "wheel_id", "wheelId")),
		UserId:   parseOptionalInt64(firstQuery(c, "user_id", "userId")),
		Status:   strings.TrimSpace(options.Status),
		Page:     int32(options.Page),
		PageSize: int32(options.PageSize),
	})
	if err != nil {
		response.ServerError(c, "获取转盘抽奖记录失败")
		return
	}
	items := make([]drawDTO, 0, len(resp.GetDraws()))
	for _, draw := range resp.GetDraws() {
		items = append(items, drawFromProto(draw))
	}
	response.OK(c, response.Page{Items: items, Page: options.Page, PageSize: options.PageSize, Total: resp.GetTotal()})
}

func (h *Handler) GetDrawSummary(c *gin.Context) {
	ctx, cancel := h.activityContext(c)
	defer cancel()
	resp, err := h.activity.GetWheelDrawSummary(ctx, &activityv1.GetWheelDrawSummaryRequest{
		Meta:    h.meta(c),
		WheelId: normalizeWheelID(firstQuery(c, "wheel_id", "wheelId")),
		UserId:  parseOptionalInt64(firstQuery(c, "user_id", "userId")),
		Status:  strings.TrimSpace(c.Query("status")),
	})
	if err != nil {
		response.ServerError(c, "获取转盘抽奖汇总失败")
		return
	}
	response.OK(c, drawSummaryFromProto(resp.GetSummary()))
}

func (h *Handler) meta(c *gin.Context) *activityv1.RequestMeta {
	return &activityv1.RequestMeta{
		RequestId: middleware.CurrentRequestID(c),
		Caller:    "admin-server",
		AppCode:   appctx.FromContext(c.Request.Context()),
		SentAtMs:  time.Now().UTC().UnixMilli(),
	}
}

func (h *Handler) activityContext(c *gin.Context) (context.Context, context.CancelFunc) {
	return context.WithTimeout(c.Request.Context(), h.requestTimeout)
}

func validateConfigRequest(req configRequest) error {
	if normalizeWheelID(req.WheelID) == "" {
		return fmt.Errorf("wheel_id 不能为空")
	}
	expectedCount := expectedWheelTierCount(req.WheelID)
	// 转盘前端布局是固定格子数：classic=8、luxury=8、advanced=12。这里按池子强校验，
	// 避免运营导入资源组后少格或多格，导致 H5 动画序列和后端奖品配置无法一一对应。
	if len(req.Tiers) != expectedCount {
		return fmt.Errorf("%s 转盘必须配置 %d 个奖品档位", normalizeWheelID(req.WheelID), expectedCount)
	}
	var totalProbability float64
	for _, tier := range req.Tiers {
		rewardType := normalizeRewardType(tier.RewardType)
		if rewardType == "" {
			return fmt.Errorf("奖品类型不能为空")
		}
		totalProbability += tier.ProbabilityPercent
		switch rewardType {
		case "coin":
			if tier.RewardCoins <= 0 {
				return fmt.Errorf("金币档 reward_coins 必须大于 0")
			}
		case "gift":
			if strings.TrimSpace(tier.RewardID) == "" {
				return fmt.Errorf("礼物档 reward_id 不能为空")
			}
		case "prop", "dress":
			if strings.TrimSpace(tier.RewardID) == "" {
				return fmt.Errorf("道具或装扮档 reward_id 不能为空")
			}
		default:
			return fmt.Errorf("奖品类型只支持 coin、gift、prop、dress")
		}
		// 道具和装扮用于背包展示，不进入 RTP 支出；真实清零在 configToProto 里执行，
		// 这里保留运营输入兼容性，防止旧配置带值时直接无法打开保存。
		if tier.ProbabilityPercent < 0 {
			return fmt.Errorf("奖品概率不能为负数")
		}
	}
	if math.Abs(totalProbability-100) > 0.000001 {
		return fmt.Errorf("奖品档位概率合计必须等于 100")
	}
	return nil
}

func configToProto(req configRequest) *activityv1.WheelRuleConfig {
	tiers := make([]*activityv1.WheelPrizeTier, 0, len(req.Tiers))
	for index, tier := range req.Tiers {
		rewardType := normalizeRewardType(tier.RewardType)
		rtpValue := tier.RTPValueCoins
		rewardCoins := tier.RewardCoins
		if rewardType == "coin" && rtpValue <= 0 {
			rtpValue = rewardCoins
		}
		if rewardType == "prop" || rewardType == "dress" {
			rtpValue = 0
		}
		tiers = append(tiers, &activityv1.WheelPrizeTier{
			TierId:        generatedTierID(tier.TierID, rewardType, index),
			DisplayName:   strings.TrimSpace(tier.DisplayName),
			RewardType:    rewardType,
			RewardId:      strings.TrimSpace(tier.RewardID),
			RewardCount:   tier.RewardCount,
			RewardCoins:   rewardCoins,
			RtpValueCoins: rtpValue,
			WeightPpm:     percentToPPM(tier.ProbabilityPercent),
			Enabled:       tier.Enabled,
			MetadataJson:  normalizeMetadataJSON(tier.MetadataJSON),
		})
	}
	return &activityv1.WheelRuleConfig{
		WheelId:               normalizeWheelID(req.WheelID),
		Enabled:               req.Enabled,
		DrawPriceCoins:        req.DrawPriceCoins,
		TargetRtpPpm:          percentToPPM(req.TargetRTPPercent),
		PoolRatePpm:           percentToPPM(req.PoolRatePercent),
		SettlementWindowDraws: req.SettlementWindowDraws,
		InitialPoolCoins:      req.InitialPoolCoins,
		PoolReserveCoins:      req.PoolReserveCoins,
		MaxSingleRtpPayout:    req.MaxSingleRTPPayout,
		EffectiveFromMs:       req.EffectiveFromMS,
		Tiers:                 tiers,
	}
}

func configFromProto(config *activityv1.WheelRuleConfig) configDTO {
	if config == nil {
		return configDTO{}
	}
	tiers := make([]tierDTO, 0, len(config.GetTiers()))
	for _, tier := range config.GetTiers() {
		tiers = append(tiers, tierDTO{
			TierID:             tier.GetTierId(),
			DisplayName:        tier.GetDisplayName(),
			RewardType:         tier.GetRewardType(),
			RewardID:           tier.GetRewardId(),
			RewardCount:        tier.GetRewardCount(),
			RewardCoins:        tier.GetRewardCoins(),
			RTPValueCoins:      tier.GetRtpValueCoins(),
			ProbabilityPercent: ppmToPercent(tier.GetWeightPpm()),
			Enabled:            tier.GetEnabled(),
			MetadataJSON:       tier.GetMetadataJson(),
		})
	}
	return configDTO{
		AppCode:          config.GetAppCode(),
		RuleVersion:      config.GetRuleVersion(),
		CreatedByAdminID: config.GetCreatedByAdminId(),
		CreatedAtMS:      config.GetCreatedAtMs(),
		configRequest: configRequest{
			WheelID:               normalizeWheelID(config.GetWheelId()),
			Enabled:               config.GetEnabled(),
			DrawPriceCoins:        config.GetDrawPriceCoins(),
			TargetRTPPercent:      ppmToPercent(config.GetTargetRtpPpm()),
			PoolRatePercent:       ppmToPercent(config.GetPoolRatePpm()),
			SettlementWindowDraws: config.GetSettlementWindowDraws(),
			InitialPoolCoins:      config.GetInitialPoolCoins(),
			PoolReserveCoins:      config.GetPoolReserveCoins(),
			MaxSingleRTPPayout:    config.GetMaxSingleRtpPayout(),
			EffectiveFromMS:       config.GetEffectiveFromMs(),
			Tiers:                 tiers,
		},
	}
}

func defaultConfigDTO(wheelID string) configDTO {
	wheelID = normalizeWheelID(wheelID)
	// 没有保存过配置时只返回基础参数和空档位。奖品档位必须从资源组导入，
	// 不能用占位奖品冒充真实配置，否则运营会误以为当前池子已经可发布。
	return configDTO{configRequest: configRequest{
		WheelID:               wheelID,
		Enabled:               false,
		DrawPriceCoins:        10_000,
		TargetRTPPercent:      90,
		PoolRatePercent:       90,
		SettlementWindowDraws: 1000,
		InitialPoolCoins:      0,
		PoolReserveCoins:      0,
		MaxSingleRTPPayout:    0,
		Tiers:                 []tierDTO{},
	}}
}

func expectedWheelTierCount(wheelID string) int {
	if count, ok := wheelTierCounts[normalizeWheelID(wheelID)]; ok {
		return count
	}
	return wheelTierCounts["classic"]
}

func defaultProbabilityPercent(index int, count int) float64 {
	if count <= 0 {
		return 0
	}
	base := math.Floor(10000/float64(count)) / 100
	if index == count-1 {
		return 100 - base*float64(count-1)
	}
	return base
}

func drawFromProto(draw *activityv1.WheelDrawResult) drawDTO {
	if draw == nil {
		return drawDTO{}
	}
	return drawDTO{
		DrawID:              draw.GetDrawId(),
		DrawIDs:             draw.GetDrawIds(),
		CommandID:           draw.GetCommandId(),
		WheelID:             draw.GetWheelId(),
		RuleVersion:         draw.GetRuleVersion(),
		SelectedTierID:      draw.GetSelectedTierId(),
		RewardType:          draw.GetRewardType(),
		RewardID:            draw.GetRewardId(),
		RewardCount:         draw.GetRewardCount(),
		RewardCoins:         draw.GetRewardCoins(),
		RTPValueCoins:       draw.GetRtpValueCoins(),
		RewardStatus:        draw.GetRewardStatus(),
		RTPWindowIndex:      draw.GetRtpWindowIndex(),
		ActualRTPPPM:        draw.GetActualRtpPpm(),
		CreatedAtMS:         draw.GetCreatedAtMs(),
		WalletTransactionID: draw.GetWalletTransactionId(),
		CoinBalanceAfter:    draw.GetCoinBalanceAfter(),
		MetadataJSON:        draw.GetMetadataJson(),
	}
}

func drawSummaryFromProto(summary *activityv1.WheelDrawSummary) drawSummaryDTO {
	if summary == nil {
		return drawSummaryDTO{}
	}
	return drawSummaryDTO{
		WheelID:            summary.GetWheelId(),
		TotalDraws:         summary.GetTotalDraws(),
		UniqueUsers:        summary.GetUniqueUsers(),
		TotalSpentCoins:    summary.GetTotalSpentCoins(),
		TotalRTPValueCoins: summary.GetTotalRtpValueCoins(),
		ActualRTPPPM:       summary.GetActualRtpPpm(),
		PendingDraws:       summary.GetPendingDraws(),
		GrantedDraws:       summary.GetGrantedDraws(),
		FailedDraws:        summary.GetFailedDraws(),
	}
}

func normalizeRewardType(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "coin":
		return "coin"
	case "gift":
		return "gift"
	case "prop", "item", "decoration", "resource":
		return "prop"
	case "dress", "dress_up", "costume":
		return "dress"
	default:
		return strings.ToLower(strings.TrimSpace(value))
	}
}

func normalizeWheelID(value string) string {
	value = strings.TrimSpace(value)
	if value == "" {
		return defaultWheelID
	}
	return value
}

func generatedTierID(raw string, rewardType string, index int) string {
	raw = strings.TrimSpace(raw)
	if raw != "" {
		return raw
	}
	// 同一奖池现在允许多个金币、多个礼物或多个装扮；缺省 ID 必须带序号，
	// 否则多档同类型会落到同一个 tier_id，抽奖结果无法稳定回显到正确格子。
	return fmt.Sprintf("%s-%02d", rewardType, index+1)
}

func normalizeMetadataJSON(value string) string {
	value = strings.TrimSpace(value)
	if value == "" {
		return "{}"
	}
	return value
}

func percentToPPM(value float64) int64 {
	return int64(math.Round(value * 10_000))
}

func ppmToPercent(value int64) float64 {
	return float64(value) / 10_000
}

func firstQuery(c *gin.Context, names ...string) string {
	for _, name := range names {
		if value := strings.TrimSpace(c.Query(name)); value != "" {
			return value
		}
	}
	return ""
}

func parseOptionalInt64(value string) int64 {
	value = strings.TrimSpace(value)
	if value == "" {
		return 0
	}
	var result int64
	for _, char := range value {
		if char < '0' || char > '9' {
			return 0
		}
		result = result*10 + int64(char-'0')
	}
	return result
}
