package luckygift

import "testing"

// 分桶必须纯确定：room saga 在 Check 固化 strategy_version 且恢复不重新 Check，
// 同一用户在 Check 与 Draw 的两次解析必须得到同一组。
func TestExperimentBucketPPMIsDeterministic(t *testing.T) {
	for _, userKey := range []string{"1", "42", "9007199254740993", "ext:abc-123"} {
		first := ExperimentBucketPPM("salt-a", userKey)
		for i := 0; i < 10; i++ {
			if again := ExperimentBucketPPM("salt-a", userKey); again != first {
				t.Fatalf("bucket for %q drifted: %d != %d", userKey, again, first)
			}
		}
		if first < 0 || first >= ExperimentTrafficPPMScale {
			t.Fatalf("bucket for %q out of range: %d", userKey, first)
		}
	}
	if ExperimentBucketPPM("salt-a", "1") == ExperimentBucketPPM("salt-b", "1") &&
		ExperimentBucketPPM("salt-a", "2") == ExperimentBucketPPM("salt-b", "2") &&
		ExperimentBucketPPM("salt-a", "3") == ExperimentBucketPPM("salt-b", "3") {
		t.Fatal("different salts should reshuffle buckets")
	}
}

// 放量必须单调：调大流量时既有 treatment 用户全部保留，不允许来回倒组打断用户体验状态。
func TestResolveExperimentArmMonotonicRamp(t *testing.T) {
	low := Experiment{Status: ExperimentStatusRunning, BucketSalt: "s", TreatmentTrafficPPM: 50_000}
	high := low
	high.TreatmentTrafficPPM = 300_000
	treatmentAtLow := 0
	for userID := int64(1); userID <= 2_000; userID++ {
		key := ExperimentUserKey(userID)
		lowArm := ResolveExperimentArm(low, key, "")
		highArm := ResolveExperimentArm(high, key, "")
		if lowArm == ExperimentArmTreatment {
			treatmentAtLow++
			if highArm != ExperimentArmTreatment {
				t.Fatalf("user %d left treatment when traffic increased", userID)
			}
		}
	}
	if treatmentAtLow == 0 {
		t.Fatal("5% ramp assigned nobody to treatment in a 2000-user sample")
	}
}

// 分桶应大致均匀：50% 流量在 10000 用户样本上偏差不应超过 ±5 个百分点。
func TestExperimentBucketDistribution(t *testing.T) {
	experiment := Experiment{Status: ExperimentStatusRunning, BucketSalt: "distribution", TreatmentTrafficPPM: 500_000}
	treatment := 0
	const sample = 10_000
	for userID := int64(1); userID <= sample; userID++ {
		if ResolveExperimentArm(experiment, ExperimentUserKey(userID), "") == ExperimentArmTreatment {
			treatment++
		}
	}
	if treatment < sample*45/100 || treatment > sample*55/100 {
		t.Fatalf("treatment share %d/%d deviates from 50%% beyond tolerance", treatment, sample)
	}
}

func TestResolveExperimentArmPrecedence(t *testing.T) {
	experiment := Experiment{Status: ExperimentStatusRunning, BucketSalt: "s", TreatmentTrafficPPM: 0}
	if arm := ResolveExperimentArm(experiment, "7", ExperimentArmTreatment); arm != ExperimentArmTreatment {
		t.Fatalf("override treatment ignored, got %s", arm)
	}
	full := experiment
	full.TreatmentTrafficPPM = ExperimentTrafficPPMScale
	if arm := ResolveExperimentArm(full, "7", ExperimentArmControl); arm != ExperimentArmControl {
		t.Fatalf("override control ignored at 100%% traffic, got %s", arm)
	}
	if arm := ResolveExperimentArm(full, "7", ""); arm != ExperimentArmTreatment {
		t.Fatalf("100%% traffic should send everyone to treatment, got %s", arm)
	}
	// paused 是紧急停闸：包括白名单在内的全部流量回落 control。
	paused := full
	paused.Status = ExperimentStatusPaused
	if arm := ResolveExperimentArm(paused, "7", ExperimentArmTreatment); arm != ExperimentArmControl {
		t.Fatalf("paused experiment must send everyone to control, got %s", arm)
	}
}
