package luckygift

import (
	"fmt"
	"strings"

	"hyapp-admin-server/internal/middleware"
	"hyapp-admin-server/internal/modules/shared"
	"hyapp-admin-server/internal/response"
	luckygiftv1 "hyapp.local/api/proto/luckygift/v1"

	"github.com/gin-gonic/gin"
)

type experimentOverrideDTO struct {
	UserKey string `json:"user_key"`
	Arm     string `json:"arm"`
}

type experimentArmStatsDTO struct {
	Arm              string  `json:"arm"`
	RuleVersion      int64   `json:"rule_version"`
	StrategyVersion  string  `json:"strategy_version"`
	TotalDraws       int64   `json:"total_draws"`
	UniqueUsers      int64   `json:"unique_users"`
	TotalSpentCoins  int64   `json:"total_spent_coins"`
	TotalRewardCoins int64   `json:"total_reward_coins"`
	ActualRTPPercent float64 `json:"actual_rtp_percent"`
}

type experimentDTO struct {
	AppCode                 string                  `json:"app_code"`
	PoolID                  string                  `json:"pool_id"`
	ExperimentID            string                  `json:"experiment_id"`
	Name                    string                  `json:"name"`
	ControlRuleVersion      int64                   `json:"control_rule_version"`
	TreatmentRuleVersion    int64                   `json:"treatment_rule_version"`
	TreatmentTrafficPercent float64                 `json:"treatment_traffic_percent"`
	Status                  string                  `json:"status"`
	WinnerArm               string                  `json:"winner_arm"`
	WinnerRuleVersion       int64                   `json:"winner_rule_version"`
	FinalRuleVersion        int64                   `json:"final_rule_version"`
	CreatedByAdminID        int64                   `json:"created_by_admin_id"`
	StartedAtMS             int64                   `json:"started_at_ms"`
	EndedAtMS               int64                   `json:"ended_at_ms"`
	UpdatedAtMS             int64                   `json:"updated_at_ms"`
	Overrides               []experimentOverrideDTO `json:"overrides"`
	ArmStats                []experimentArmStatsDTO `json:"arm_stats"`
}

type createExperimentRequest struct {
	Name                    string                  `json:"name"`
	TreatmentTrafficPercent float64                 `json:"treatment_traffic_percent"`
	Overrides               []experimentOverrideDTO `json:"overrides"`
	Config                  configRequest           `json:"config"`
}

type updateExperimentRequest struct {
	// 指针区分「没传」与「显式传 0」：缺省时向 owner 传 -1 表示保持现有放量。
	TreatmentTrafficPercent *float64 `json:"treatment_traffic_percent"`
	Status                  string   `json:"status"`
}

type setExperimentOverridesRequest struct {
	Upserts []experimentOverrideDTO `json:"upserts"`
	Removes []string                `json:"removes"`
}

type endExperimentRequest struct {
	WinnerArm string `json:"winner_arm"`
}

func (h *Handler) ListLuckyGiftExperiments(c *gin.Context) {
	if strings.TrimSpace(c.Query("app_code")) == "" {
		response.BadRequest(c, "app_code is required")
		return
	}
	ctx, cancel := h.luckyGiftContext(c)
	defer cancel()
	resp, err := h.luckyGift.ListLuckyGiftExperiments(ctx, &luckygiftv1.ListLuckyGiftExperimentsRequest{
		Meta:      h.meta(c),
		PoolId:    strings.TrimSpace(c.Query("pool_id")),
		Status:    strings.ToLower(strings.TrimSpace(c.Query("status"))),
		WithStats: parseOptionalBool(c.Query("with_stats")),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	items := make([]experimentDTO, 0, len(resp.GetExperiments()))
	for _, experiment := range resp.GetExperiments() {
		items = append(items, experimentFromProto(experiment))
	}
	response.OK(c, gin.H{"items": items})
}

func (h *Handler) CreateLuckyGiftExperiment(c *gin.Context) {
	appCode := strings.ToLower(strings.TrimSpace(c.Query("app_code")))
	if appCode == "" {
		response.BadRequest(c, "app_code is required")
		return
	}
	var req createExperimentRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "幸运礼物实验参数不正确")
		return
	}
	if req.Config.AppCode == "" {
		req.Config.AppCode = appCode
	}
	ctx, cancel := h.luckyGiftContext(c)
	defer cancel()
	resp, err := h.luckyGift.CreateLuckyGiftExperiment(ctx, &luckygiftv1.CreateLuckyGiftExperimentRequest{
		Meta: h.metaForApp(c, appCode),
		Name: strings.TrimSpace(req.Name),
		// treatment 配置复用 PUT /lucky-gifts/config 的解析归一逻辑（tier_id 生成、percent→ppm、
		// dynamic_v3 initial_pool_coins 清零），保证实验发布的新版本与常规发布口径一致。
		TreatmentConfig:     configToProto(req.Config),
		TreatmentTrafficPpm: percentToPPM(req.TreatmentTrafficPercent),
		OperatorAdminId:     int64(middleware.CurrentUserID(c)),
		Overrides:           experimentOverridesToProto(req.Overrides),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	experiment := experimentFromProto(resp.GetExperiment())
	shared.OperationLogWithResourceID(
		c, h.audit, "create-lucky-gift-experiment", "lucky_gift_experiments", experiment.ExperimentID, "success",
		fmt.Sprintf("app_code=%s pool_id=%s treatment_traffic_ppm=%d", appCode, experiment.PoolID, percentToPPM(req.TreatmentTrafficPercent)),
	)
	response.OK(c, gin.H{
		"experiment":       experiment,
		"treatment_config": configFromProto(resp.GetTreatmentConfig()),
	})
}

func (h *Handler) UpdateLuckyGiftExperiment(c *gin.Context) {
	appCode := strings.ToLower(strings.TrimSpace(c.Query("app_code")))
	if appCode == "" {
		response.BadRequest(c, "app_code is required")
		return
	}
	experimentID := strings.TrimSpace(c.Param("experimentId"))
	if experimentID == "" {
		response.BadRequest(c, "experiment_id is required")
		return
	}
	var req updateExperimentRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "幸运礼物实验参数不正确")
		return
	}
	trafficPPM := int64(-1)
	grpcReq := &luckygiftv1.UpdateLuckyGiftExperimentRequest{
		Meta:            h.metaForApp(c, appCode),
		ExperimentId:    experimentID,
		Status:          strings.ToLower(strings.TrimSpace(req.Status)),
		OperatorAdminId: int64(middleware.CurrentUserID(c)),
	}
	if req.TreatmentTrafficPercent != nil {
		trafficPPM = percentToPPM(*req.TreatmentTrafficPercent)
		grpcReq.TreatmentTrafficPpm = &trafficPPM
	}
	ctx, cancel := h.luckyGiftContext(c)
	defer cancel()
	resp, err := h.luckyGift.UpdateLuckyGiftExperiment(ctx, grpcReq)
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	shared.OperationLogWithResourceID(
		c, h.audit, "update-lucky-gift-experiment", "lucky_gift_experiments", experimentID, "success",
		fmt.Sprintf("app_code=%s treatment_traffic_ppm=%d status=%s", appCode, trafficPPM, strings.ToLower(strings.TrimSpace(req.Status))),
	)
	response.OK(c, gin.H{"experiment": experimentFromProto(resp.GetExperiment())})
}

func (h *Handler) SetLuckyGiftExperimentOverrides(c *gin.Context) {
	appCode := strings.ToLower(strings.TrimSpace(c.Query("app_code")))
	if appCode == "" {
		response.BadRequest(c, "app_code is required")
		return
	}
	experimentID := strings.TrimSpace(c.Param("experimentId"))
	if experimentID == "" {
		response.BadRequest(c, "experiment_id is required")
		return
	}
	var req setExperimentOverridesRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "幸运礼物实验白名单参数不正确")
		return
	}
	removes := make([]string, 0, len(req.Removes))
	for _, userKey := range req.Removes {
		removes = append(removes, strings.TrimSpace(userKey))
	}
	ctx, cancel := h.luckyGiftContext(c)
	defer cancel()
	resp, err := h.luckyGift.SetLuckyGiftExperimentOverrides(ctx, &luckygiftv1.SetLuckyGiftExperimentOverridesRequest{
		Meta:            h.metaForApp(c, appCode),
		ExperimentId:    experimentID,
		Upserts:         experimentOverridesToProto(req.Upserts),
		Removes:         removes,
		OperatorAdminId: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	shared.OperationLogWithResourceID(
		c, h.audit, "set-lucky-gift-experiment-overrides", "lucky_gift_experiments", experimentID, "success",
		fmt.Sprintf("app_code=%s upserts=%d removes=%d", appCode, len(req.Upserts), len(removes)),
	)
	response.OK(c, gin.H{"experiment": experimentFromProto(resp.GetExperiment())})
}

func (h *Handler) EndLuckyGiftExperiment(c *gin.Context) {
	appCode := strings.ToLower(strings.TrimSpace(c.Query("app_code")))
	if appCode == "" {
		response.BadRequest(c, "app_code is required")
		return
	}
	experimentID := strings.TrimSpace(c.Param("experimentId"))
	if experimentID == "" {
		response.BadRequest(c, "experiment_id is required")
		return
	}
	var req endExperimentRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.BadRequest(c, "幸运礼物实验参数不正确")
		return
	}
	winnerArm := strings.ToLower(strings.TrimSpace(req.WinnerArm))
	ctx, cancel := h.luckyGiftContext(c)
	defer cancel()
	resp, err := h.luckyGift.EndLuckyGiftExperiment(ctx, &luckygiftv1.EndLuckyGiftExperimentRequest{
		Meta:            h.metaForApp(c, appCode),
		ExperimentId:    experimentID,
		WinnerArm:       winnerArm,
		OperatorAdminId: int64(middleware.CurrentUserID(c)),
	})
	if err != nil {
		response.BadRequest(c, err.Error())
		return
	}
	shared.OperationLogWithResourceID(
		c, h.audit, "end-lucky-gift-experiment", "lucky_gift_experiments", experimentID, "success",
		fmt.Sprintf("app_code=%s winner_arm=%s", appCode, winnerArm),
	)
	response.OK(c, gin.H{
		"experiment":   experimentFromProto(resp.GetExperiment()),
		"final_config": configFromProto(resp.GetFinalConfig()),
	})
}

func experimentOverridesToProto(overrides []experimentOverrideDTO) []*luckygiftv1.LuckyGiftExperimentOverride {
	out := make([]*luckygiftv1.LuckyGiftExperimentOverride, 0, len(overrides))
	for _, override := range overrides {
		out = append(out, &luckygiftv1.LuckyGiftExperimentOverride{
			UserKey: strings.TrimSpace(override.UserKey),
			Arm:     strings.ToLower(strings.TrimSpace(override.Arm)),
		})
	}
	return out
}

func experimentFromProto(experiment *luckygiftv1.LuckyGiftExperiment) experimentDTO {
	if experiment == nil {
		return experimentDTO{Overrides: []experimentOverrideDTO{}, ArmStats: []experimentArmStatsDTO{}}
	}
	overrides := make([]experimentOverrideDTO, 0, len(experiment.GetOverrides()))
	for _, override := range experiment.GetOverrides() {
		overrides = append(overrides, experimentOverrideDTO{
			UserKey: override.GetUserKey(),
			Arm:     override.GetArm(),
		})
	}
	armStats := make([]experimentArmStatsDTO, 0, len(experiment.GetArmStats()))
	for _, stats := range experiment.GetArmStats() {
		armStats = append(armStats, experimentArmStatsDTO{
			Arm:              stats.GetArm(),
			RuleVersion:      stats.GetRuleVersion(),
			StrategyVersion:  stats.GetStrategyVersion(),
			TotalDraws:       stats.GetTotalDraws(),
			UniqueUsers:      stats.GetUniqueUsers(),
			TotalSpentCoins:  stats.GetTotalSpentCoins(),
			TotalRewardCoins: stats.GetTotalRewardCoins(),
			ActualRTPPercent: ppmToPercent(stats.GetActualRtpPpm()),
		})
	}
	return experimentDTO{
		AppCode:                 experiment.GetAppCode(),
		PoolID:                  experiment.GetPoolId(),
		ExperimentID:            experiment.GetExperimentId(),
		Name:                    experiment.GetName(),
		ControlRuleVersion:      experiment.GetControlRuleVersion(),
		TreatmentRuleVersion:    experiment.GetTreatmentRuleVersion(),
		TreatmentTrafficPercent: ppmToPercent(experiment.GetTreatmentTrafficPpm()),
		Status:                  experiment.GetStatus(),
		WinnerArm:               experiment.GetWinnerArm(),
		WinnerRuleVersion:       experiment.GetWinnerRuleVersion(),
		FinalRuleVersion:        experiment.GetFinalRuleVersion(),
		CreatedByAdminID:        experiment.GetCreatedByAdminId(),
		StartedAtMS:             experiment.GetStartedAtMs(),
		EndedAtMS:               experiment.GetEndedAtMs(),
		UpdatedAtMS:             experiment.GetUpdatedAtMs(),
		Overrides:               overrides,
		ArmStats:                armStats,
	}
}
