package luckygift

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"
)

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 {
	PoolID                   string     `json:"pool_id"`
	Enabled                  bool       `json:"enabled"`
	TargetRTPPercent         float64    `json:"target_rtp_percent"`
	PoolRatePercent          float64    `json:"pool_rate_percent"`
	SettlementWindowWager    int64      `json:"settlement_window_wager"`
	ControlBandPercent       float64    `json:"control_band_percent"`
	GiftPriceReference       int64      `json:"gift_price_reference"`
	NoviceMaxEquivalentDraws int64      `json:"novice_max_equivalent_draws"`
	NormalMaxEquivalentDraws int64      `json:"normal_max_equivalent_draws"`
	MaxSinglePayout          int64      `json:"max_single_payout"`
	UserHourlyPayoutCap      int64      `json:"user_hourly_payout_cap"`
	UserDailyPayoutCap       int64      `json:"user_daily_payout_cap"`
	DeviceDailyPayoutCap     int64      `json:"device_daily_payout_cap"`
	RoomHourlyPayoutCap      int64      `json:"room_hourly_payout_cap"`
	AnchorDailyPayoutCap     int64      `json:"anchor_daily_payout_cap"`
	EffectiveFromMS          int64      `json:"effective_from_ms"`
	Stages                   []stageDTO `json:"stages"`
}

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 stageDTO struct {
	Stage string    `json:"stage"`
	Tiers []tierDTO `json:"tiers"`
}

type tierDTO struct {
	TierID             string  `json:"tier_id"`
	Multiplier         float64 `json:"multiplier"`
	ProbabilityPercent float64 `json:"probability_percent"`
	RewardSource       string  `json:"reward_source"`
	HighWaterOnly      bool    `json:"high_water_only"`
	BroadcastLevel     string  `json:"broadcast_level"`
	Enabled            bool    `json:"enabled"`
}

type drawDTO struct {
	DrawID                    string `json:"draw_id"`
	CommandID                 string `json:"command_id"`
	PoolID                    string `json:"pool_id"`
	GiftID                    string `json:"gift_id"`
	RuleVersion               int64  `json:"rule_version"`
	ExperiencePool            string `json:"experience_pool"`
	SelectedTierID            string `json:"selected_tier_id"`
	MultiplierPPM             int64  `json:"multiplier_ppm"`
	BaseRewardCoins           int64  `json:"base_reward_coins"`
	RoomAtmosphereRewardCoins int64  `json:"room_atmosphere_reward_coins"`
	ActivitySubsidyCoins      int64  `json:"activity_subsidy_coins"`
	EffectiveRewardCoins      int64  `json:"effective_reward_coins"`
	BudgetSourcesJSON         string `json:"budget_sources_json"`
	RewardStatus              string `json:"reward_status"`
	RTPWindowIndex            int64  `json:"rtp_window_index"`
	GiftRTPWindowIndex        int64  `json:"gift_rtp_window_index"`
	GlobalBaseRTPPPM          int64  `json:"global_base_rtp_ppm"`
	GiftBaseRTPPPM            int64  `json:"gift_base_rtp_ppm"`
	StageFeedback             bool   `json:"stage_feedback"`
	HighMultiplier            bool   `json:"high_multiplier"`
	CreatedAtMS               int64  `json:"created_at_ms"`
}

type drawSummaryDTO struct {
	PoolID                    string `json:"pool_id"`
	TotalDraws                int64  `json:"total_draws"`
	UniqueUsers               int64  `json:"unique_users"`
	UniqueRooms               int64  `json:"unique_rooms"`
	TotalSpentCoins           int64  `json:"total_spent_coins"`
	TotalRewardCoins          int64  `json:"total_reward_coins"`
	BaseRewardCoins           int64  `json:"base_reward_coins"`
	RoomAtmosphereRewardCoins int64  `json:"room_atmosphere_reward_coins"`
	ActivitySubsidyCoins      int64  `json:"activity_subsidy_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) GetLuckyGiftConfig(c *gin.Context) {
	ctx, cancel := h.activityContext(c)
	defer cancel()
	resp, err := h.activity.GetLuckyGiftConfig(ctx, &activityv1.GetLuckyGiftConfigRequest{
		Meta:   h.meta(c),
		PoolId: strings.TrimSpace(c.Query("pool_id")),
	})
	if err != nil {
		response.ServerError(c, "获取幸运礼物配置失败")
		return
	}
	response.OK(c, configFromProto(resp.GetConfig()))
}

func (h *Handler) UpsertLuckyGiftConfig(c *gin.Context) {
	var req configRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "幸运礼物配置参数不正确")
		return
	}
	if req.PoolID == "" {
		req.PoolID = strings.TrimSpace(c.Query("pool_id"))
	}
	ctx, cancel := h.activityContext(c)
	defer cancel()
	resp, err := h.activity.UpsertLuckyGiftConfig(ctx, &activityv1.UpsertLuckyGiftConfigRequest{
		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-lucky-gift-config", "lucky_gift_rule_versions", item.PoolID, "success", "")
	response.OK(c, item)
}

func (h *Handler) ListLuckyGiftConfigs(c *gin.Context) {
	ctx, cancel := h.activityContext(c)
	defer cancel()
	resp, err := h.activity.ListLuckyGiftConfigs(ctx, &activityv1.ListLuckyGiftConfigsRequest{
		Meta: h.meta(c),
	})
	if err != nil {
		response.ServerError(c, "获取幸运礼物奖池列表失败")
		return
	}
	items := make([]configDTO, 0, len(resp.GetConfigs()))
	for _, config := range resp.GetConfigs() {
		items = append(items, configFromProto(config))
	}
	response.OK(c, items)
}

func (h *Handler) ListLuckyGiftDraws(c *gin.Context) {
	options := shared.ListOptions(c)
	ctx, cancel := h.activityContext(c)
	defer cancel()
	resp, err := h.activity.ListLuckyGiftDraws(ctx, &activityv1.ListLuckyGiftDrawsRequest{
		Meta:     h.meta(c),
		PoolId:   strings.TrimSpace(c.Query("pool_id")),
		GiftId:   strings.TrimSpace(c.Query("gift_id")),
		UserId:   parseOptionalInt64(c.Query("user_id")),
		RoomId:   strings.TrimSpace(c.Query("room_id")),
		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) GetLuckyGiftDrawSummary(c *gin.Context) {
	ctx, cancel := h.activityContext(c)
	defer cancel()
	resp, err := h.activity.GetLuckyGiftDrawSummary(ctx, &activityv1.GetLuckyGiftDrawSummaryRequest{
		Meta:   h.meta(c),
		PoolId: strings.TrimSpace(c.Query("pool_id")),
		GiftId: strings.TrimSpace(c.Query("gift_id")),
		UserId: parseOptionalInt64(c.Query("user_id")),
		RoomId: strings.TrimSpace(c.Query("room_id")),
		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 configToProto(req configRequest) *activityv1.LuckyGiftRuleConfig {
	stages := make([]*activityv1.LuckyGiftRuleStage, 0, len(req.Stages))
	for _, stage := range req.Stages {
		stageName := strings.TrimSpace(stage.Stage)
		tierIDCounts := make(map[string]int, len(stage.Tiers))
		tiers := make([]*activityv1.LuckyGiftRuleTier, 0, len(stage.Tiers))
		for _, tier := range stage.Tiers {
			multiplierPPM := multiplierToPPM(tier.Multiplier)
			tiers = append(tiers, &activityv1.LuckyGiftRuleTier{
				Stage: stageName,
				// 奖档 ID 是 activity-service 持久化主键的一部分，不能让运营手工维护；admin-server 按阶段和倍率生成稳定 ID，
				// 同一阶段同倍率出现多行时追加序号，保证进入 activity-service 前已经满足唯一性。
				TierId:         generatedLuckyGiftTierID(stageName, multiplierPPM, tierIDCounts),
				MultiplierPpm:  multiplierPPM,
				BaseWeightPpm:  percentToPPM(tier.ProbabilityPercent),
				RewardSource:   strings.TrimSpace(tier.RewardSource),
				HighWaterOnly:  tier.HighWaterOnly,
				BroadcastLevel: strings.TrimSpace(tier.BroadcastLevel),
				Enabled:        tier.Enabled,
			})
		}
		stages = append(stages, &activityv1.LuckyGiftRuleStage{
			Stage: stageName,
			Tiers: tiers,
		})
	}
	return &activityv1.LuckyGiftRuleConfig{
		PoolId:                   strings.TrimSpace(req.PoolID),
		Enabled:                  req.Enabled,
		TargetRtpPpm:             percentToPPM(req.TargetRTPPercent),
		PoolRatePpm:              percentToPPM(req.PoolRatePercent),
		SettlementWindowWager:    req.SettlementWindowWager,
		ControlBandPpm:           percentToPPM(req.ControlBandPercent),
		GiftPriceReference:       req.GiftPriceReference,
		NoviceMaxEquivalentDraws: req.NoviceMaxEquivalentDraws,
		NormalMaxEquivalentDraws: req.NormalMaxEquivalentDraws,
		MaxSinglePayout:          req.MaxSinglePayout,
		UserHourlyPayoutCap:      req.UserHourlyPayoutCap,
		UserDailyPayoutCap:       req.UserDailyPayoutCap,
		DeviceDailyPayoutCap:     req.DeviceDailyPayoutCap,
		RoomHourlyPayoutCap:      req.RoomHourlyPayoutCap,
		AnchorDailyPayoutCap:     req.AnchorDailyPayoutCap,
		EffectiveFromMs:          req.EffectiveFromMS,
		Stages:                   stages,
	}
}

func configFromProto(config *activityv1.LuckyGiftRuleConfig) configDTO {
	if config == nil {
		return configDTO{}
	}
	stages := make([]stageDTO, 0, len(config.GetStages()))
	for _, stage := range config.GetStages() {
		tiers := make([]tierDTO, 0, len(stage.GetTiers()))
		for _, tier := range stage.GetTiers() {
			tiers = append(tiers, tierDTO{
				TierID:             tier.GetTierId(),
				Multiplier:         ppmToMultiplier(tier.GetMultiplierPpm()),
				ProbabilityPercent: ppmToPercent(tier.GetBaseWeightPpm()),
				RewardSource:       tier.GetRewardSource(),
				HighWaterOnly:      tier.GetHighWaterOnly(),
				BroadcastLevel:     tier.GetBroadcastLevel(),
				Enabled:            tier.GetEnabled(),
			})
		}
		stages = append(stages, stageDTO{
			Stage: stage.GetStage(),
			Tiers: tiers,
		})
	}
	poolID := strings.TrimSpace(config.GetPoolId())
	return configDTO{
		AppCode:          config.GetAppCode(),
		RuleVersion:      config.GetRuleVersion(),
		CreatedByAdminID: config.GetCreatedByAdminId(),
		CreatedAtMS:      config.GetCreatedAtMs(),
		configRequest: configRequest{
			PoolID:                   poolID,
			Enabled:                  config.GetEnabled(),
			TargetRTPPercent:         ppmToPercent(config.GetTargetRtpPpm()),
			PoolRatePercent:          ppmToPercent(config.GetPoolRatePpm()),
			SettlementWindowWager:    config.GetSettlementWindowWager(),
			ControlBandPercent:       ppmToPercent(config.GetControlBandPpm()),
			GiftPriceReference:       config.GetGiftPriceReference(),
			NoviceMaxEquivalentDraws: config.GetNoviceMaxEquivalentDraws(),
			NormalMaxEquivalentDraws: config.GetNormalMaxEquivalentDraws(),
			MaxSinglePayout:          config.GetMaxSinglePayout(),
			UserHourlyPayoutCap:      config.GetUserHourlyPayoutCap(),
			UserDailyPayoutCap:       config.GetUserDailyPayoutCap(),
			DeviceDailyPayoutCap:     config.GetDeviceDailyPayoutCap(),
			RoomHourlyPayoutCap:      config.GetRoomHourlyPayoutCap(),
			AnchorDailyPayoutCap:     config.GetAnchorDailyPayoutCap(),
			EffectiveFromMS:          config.GetEffectiveFromMs(),
			Stages:                   stages,
		},
	}
}

func generatedLuckyGiftTierID(stage string, multiplierPPM int64, counts map[string]int) string {
	baseID := luckyGiftTierIDBase(stage, multiplierPPM)
	counts[baseID]++
	if counts[baseID] == 1 {
		return baseID
	}
	return fmt.Sprintf("%s_%d", baseID, counts[baseID])
}

func luckyGiftTierIDBase(stage string, multiplierPPM int64) string {
	stage = strings.TrimSpace(stage)
	if multiplierPPM <= 0 {
		return stage + "_none"
	}
	whole := multiplierPPM / 1_000_000
	fraction := multiplierPPM % 1_000_000
	if fraction == 0 {
		return fmt.Sprintf("%s_%dx", stage, whole)
	}
	fractionText := strings.TrimRight(fmt.Sprintf("%06d", fraction), "0")
	return fmt.Sprintf("%s_%d_%sx", stage, whole, fractionText)
}

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

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

func multiplierToPPM(value float64) int64 {
	return int64(math.Round(value * 1_000_000))
}

func ppmToMultiplier(value int64) float64 {
	return float64(value) / 1_000_000
}

func drawFromProto(draw *activityv1.LuckyGiftDrawResult) drawDTO {
	if draw == nil {
		return drawDTO{}
	}
	return drawDTO{
		DrawID:                    draw.GetDrawId(),
		CommandID:                 draw.GetCommandId(),
		PoolID:                    draw.GetPoolId(),
		GiftID:                    draw.GetGiftId(),
		RuleVersion:               draw.GetRuleVersion(),
		ExperiencePool:            draw.GetExperiencePool(),
		SelectedTierID:            draw.GetSelectedTierId(),
		MultiplierPPM:             draw.GetMultiplierPpm(),
		BaseRewardCoins:           draw.GetBaseRewardCoins(),
		RoomAtmosphereRewardCoins: draw.GetRoomAtmosphereRewardCoins(),
		ActivitySubsidyCoins:      draw.GetActivitySubsidyCoins(),
		EffectiveRewardCoins:      draw.GetEffectiveRewardCoins(),
		BudgetSourcesJSON:         draw.GetBudgetSourcesJson(),
		RewardStatus:              draw.GetRewardStatus(),
		RTPWindowIndex:            draw.GetRtpWindowIndex(),
		GiftRTPWindowIndex:        draw.GetGiftRtpWindowIndex(),
		GlobalBaseRTPPPM:          draw.GetGlobalBaseRtpPpm(),
		GiftBaseRTPPPM:            draw.GetGiftBaseRtpPpm(),
		StageFeedback:             draw.GetStageFeedback(),
		HighMultiplier:            draw.GetHighMultiplier(),
		CreatedAtMS:               draw.GetCreatedAtMs(),
	}
}

func drawSummaryFromProto(summary *activityv1.LuckyGiftDrawSummary) drawSummaryDTO {
	if summary == nil {
		return drawSummaryDTO{}
	}
	return drawSummaryDTO{
		PoolID:                    summary.GetPoolId(),
		TotalDraws:                summary.GetTotalDraws(),
		UniqueUsers:               summary.GetUniqueUsers(),
		UniqueRooms:               summary.GetUniqueRooms(),
		TotalSpentCoins:           summary.GetTotalSpentCoins(),
		TotalRewardCoins:          summary.GetTotalRewardCoins(),
		BaseRewardCoins:           summary.GetBaseRewardCoins(),
		RoomAtmosphereRewardCoins: summary.GetRoomAtmosphereRewardCoins(),
		ActivitySubsidyCoins:      summary.GetActivitySubsidyCoins(),
		ActualRTPPPM:              summary.GetActualRtpPpm(),
		PendingDraws:              summary.GetPendingDraws(),
		GrantedDraws:              summary.GetGrantedDraws(),
		FailedDraws:               summary.GetFailedDraws(),
	}
}

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
}
