package luckygift

import (
	"hash/fnv"
	"strconv"
	"strings"
)

const (
	// 实验组常量。control 钉住实验开始时的最新已启用版本，treatment 钉住随实验原子发布的新版本。
	ExperimentArmControl   = "control"
	ExperimentArmTreatment = "treatment"

	// paused 时全部流量（含白名单 override）回落 control，作为不销毁分组事实的紧急停闸。
	ExperimentStatusRunning   = "running"
	ExperimentStatusPaused    = "paused"
	ExperimentStatusCompleted = "completed"

	// ExperimentTrafficPPMScale 是 treatment 放量比例的满量程；与全仓 ppm 口径一致。
	ExperimentTrafficPPMScale int64 = 1_000_000

	// MaxExperimentOverrides 是单实验白名单的累计上限（单次请求与库内总量都受它约束）。
	// 白名单是放量前的验证工具，不是花名册式定向投放。
	MaxExperimentOverrides = 500
)

// Experiment 是单奖池 AB 实验事实。两组各自钉住一个不可变 rule_version，
// 运行中"最新版本生效"的默认语义被实验钉住的版本替代；同一奖池最多一个未完成实验。
type Experiment 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"`
	// TreatmentTrafficPPM ∈ [0, 1000000]；0 表示只有白名单 override 进入 treatment。
	TreatmentTrafficPPM int64  `json:"treatment_traffic_ppm"`
	BucketSalt          string `json:"bucket_salt"`
	Status              string `json:"status"`
	WinnerArm           string `json:"winner_arm"`
	WinnerRuleVersion   int64  `json:"winner_rule_version"`
	// FinalRuleVersion 是结束实验时把获胜配置重发布成的新最新版本；全量流量由它承接。
	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        []ExperimentOverride `json:"overrides,omitempty"`
	ArmStats         []ExperimentArmStats `json:"arm_stats,omitempty"`
}

// ExperimentOverride 把单个用户强制固定到某个实验组，多用于放量前的白名单验证。
type ExperimentOverride struct {
	UserKey string `json:"user_key"`
	Arm     string `json:"arm"`
}

// ExperimentArmStats 是实验开始以来单组的抽奖聚合，按 rule_version 从抽奖事实现算。
type ExperimentArmStats 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"`
	ActualRTPPPM     int64  `json:"actual_rtp_ppm"`
}

type CreateExperimentCommand struct {
	Name                string
	TreatmentConfig     RuleConfig
	TreatmentTrafficPPM int64
	OperatorAdminID     int64
	Overrides           []ExperimentOverride
}

type UpdateExperimentCommand struct {
	ExperimentID string
	// TreatmentTrafficPPM < 0 表示不修改流量。
	TreatmentTrafficPPM int64
	// Status 为空表示不修改状态；仅允许 running/paused 之间切换。
	Status          string
	OperatorAdminID int64
}

type SetExperimentOverridesCommand struct {
	ExperimentID    string
	Upserts         []ExperimentOverride
	Removes         []string
	OperatorAdminID int64
}

type EndExperimentCommand struct {
	ExperimentID    string
	WinnerArm       string
	OperatorAdminID int64
}

type ExperimentQuery struct {
	AppCode   string
	PoolID    string
	Status    string
	WithStats bool
}

// ExperimentUserKey 归一实验分桶与白名单使用的用户标识：
// 内部用户为十进制 user_id，外部用户为 "ext:" + external_user_id。
func ExperimentUserKey(userID int64) string {
	return strconv.FormatInt(userID, 10)
}

func ExperimentExternalUserKey(externalUserID string) string {
	return "ext:" + strings.TrimSpace(externalUserID)
}

// ExperimentBucketPPM 把用户确定性地映射到 [0, 1000000) 的分桶坐标。
// 对同一 salt+userKey 必须稳定：实验状态不变时 Check 与 Draw 解析到同一组。
// 放量/白名单/暂停操作仍可能让两次解析落入不同组，因此两代策略都必须兼容旧客户端缺少的可选事实；
// dynamic_v3 的 paid_at_ms 始终来自扣费后的 wallet 回执，不能依赖 Check 阶段尚不存在的支付事实。
func ExperimentBucketPPM(salt string, userKey string) int64 {
	hasher := fnv.New64a()
	_, _ = hasher.Write([]byte(salt))
	_, _ = hasher.Write([]byte{':'})
	_, _ = hasher.Write([]byte(userKey))
	return int64(hasher.Sum64() % uint64(ExperimentTrafficPPMScale))
}

// ResolveExperimentArm 返回用户所属实验组。
// 优先级：paused 全量 control > 白名单 override > 哈希放量。
// 放量比较使用 bucket < traffic：调大流量时既有 treatment 用户全部保留，只有新用户迁入，保证单调放量。
func ResolveExperimentArm(experiment Experiment, userKey string, override string) string {
	if experiment.Status != ExperimentStatusRunning {
		return ExperimentArmControl
	}
	switch override {
	case ExperimentArmControl, ExperimentArmTreatment:
		return override
	}
	if ExperimentBucketPPM(experiment.BucketSalt, userKey) < experiment.TreatmentTrafficPPM {
		return ExperimentArmTreatment
	}
	return ExperimentArmControl
}

// ExperimentArmRuleVersion 返回某组钉住的配置版本。
func ExperimentArmRuleVersion(experiment Experiment, arm string) int64 {
	if arm == ExperimentArmTreatment {
		return experiment.TreatmentRuleVersion
	}
	return experiment.ControlRuleVersion
}

func ValidExperimentArm(arm string) bool {
	return arm == ExperimentArmControl || arm == ExperimentArmTreatment
}
