package mysql

import (
	"context"
	"testing"

	"hyapp/internal/testutil/mysqlschema"
	"hyapp/pkg/appcode"
	domain "hyapp/services/lucky-gift-service/internal/domain/luckygift"
)

func v2BoostConfig(threshold, factor, recoverLine int64) domain.Config {
	return domain.Config{
		V2HighWaterBoostThresholdCoins: threshold,
		V2HighWaterBoostFactorPPM:      factor,
		V2HighWaterBoostRecoverCoins:   recoverLine,
	}
}

func TestAdvanceLuckyV2HighWaterBoostHysteresis(t *testing.T) {
	config := v2BoostConfig(2_000_000, 1_500_000, 1_000_000)
	pool := luckyPool{Balance: 1_500_000}

	// 阈值以下且未触发：不加权。
	if advanceLuckyV2HighWaterBoost(config, &pool) {
		t.Fatalf("balance below threshold must not boost")
	}
	// 越过阈值：进入加权。
	pool.Balance = 2_000_001
	if !advanceLuckyV2HighWaterBoost(config, &pool) {
		t.Fatalf("balance above threshold must boost")
	}
	// 恰好等于阈值不触发（严格大于语义），但已触发状态在恢复线之上必须保持。
	pool.Balance = 1_200_000
	if !advanceLuckyV2HighWaterBoost(config, &pool) {
		t.Fatalf("hysteresis must keep boosting between recover line and threshold")
	}
	// 恰好等于恢复线仍保持（严格小于才退出）。
	pool.Balance = 1_000_000
	if !advanceLuckyV2HighWaterBoost(config, &pool) {
		t.Fatalf("balance at recover line must keep boosting; exit is strictly below")
	}
	// 跌破恢复线：退出加权。
	pool.Balance = 999_999
	if advanceLuckyV2HighWaterBoost(config, &pool) {
		t.Fatalf("balance below recover line must stop boosting")
	}
	// 退出后回到中间区间不重新触发，必须再次越过阈值。
	pool.Balance = 1_800_000
	if advanceLuckyV2HighWaterBoost(config, &pool) {
		t.Fatalf("after exit the middle band must stay normal until threshold is crossed again")
	}
}

func TestAdvanceLuckyV2HighWaterBoostDisabledOrInvalidConfigClearsFlag(t *testing.T) {
	// 高水位余额 + 脏滞回标志：关闭或非法配置都必须清零标志并停止加权，避免规则回滚后残留放大。
	cases := []domain.Config{
		v2BoostConfig(0, 0, 0),
		v2BoostConfig(2_000_000, 1_000_000, 1_000_000), // 系数不足 100% 视为非法
		v2BoostConfig(2_000_000, 1_500_000, 0),         // 恢复线缺失
		v2BoostConfig(2_000_000, 1_500_000, 3_000_000), // 恢复线高于阈值
	}
	for index, config := range cases {
		pool := luckyPool{Balance: 5_000_000, V2BoostActive: true}
		if advanceLuckyV2HighWaterBoost(config, &pool) || pool.V2BoostActive {
			t.Fatalf("case %d: invalid/disabled config must clear the sticky flag", index)
		}
	}
}

func TestLuckyV2BoostedConfigScalesRewardAndMultiplierOnly(t *testing.T) {
	config := domain.Config{
		GiftPrice:                 100,
		V2HighWaterBoostFactorPPM: 1_500_000,
		Tiers: []domain.Tier{
			{TierID: "zero", MultiplierPPM: 0, RewardCoins: 0, Weight: 13_308, Enabled: true},
			{TierID: "1_2x", MultiplierPPM: 1_200_000, RewardCoins: 120, Weight: 66_664, Enabled: true},
			{TierID: "5x", MultiplierPPM: 5_000_000, RewardCoins: 500, Weight: 15_999, Enabled: true},
		},
	}
	boosted := luckyV2BoostedConfig(config)
	if boosted.Tiers[0].RewardCoins != 0 || boosted.Tiers[0].MultiplierPPM != 0 {
		t.Fatalf("zero tier must stay zero: %+v", boosted.Tiers[0])
	}
	if boosted.Tiers[1].RewardCoins != 180 || boosted.Tiers[1].MultiplierPPM != 1_800_000 {
		t.Fatalf("1.2x tier should scale to 1.8x/180: %+v", boosted.Tiers[1])
	}
	if boosted.Tiers[2].RewardCoins != 750 || boosted.Tiers[2].MultiplierPPM != 7_500_000 {
		t.Fatalf("5x tier should scale to 7.5x/750: %+v", boosted.Tiers[2])
	}
	// 概率权重与原配置保持一致：加权只放大返奖金额，不改变中奖概率。
	for index := range config.Tiers {
		if boosted.Tiers[index].Weight != config.Tiers[index].Weight {
			t.Fatalf("tier %d weight changed: %d -> %d", index, config.Tiers[index].Weight, boosted.Tiers[index].Weight)
		}
	}
	// 原配置不能被原地修改。
	if config.Tiers[1].RewardCoins != 120 {
		t.Fatalf("input config was mutated: %+v", config.Tiers[1])
	}
	// 未启用系数时原样返回。
	config.V2HighWaterBoostFactorPPM = 0
	if unchanged := luckyV2BoostedConfig(config); unchanged.Tiers[1].RewardCoins != 120 {
		t.Fatalf("disabled factor must return config unchanged: %+v", unchanged.Tiers[1])
	}
}

// 真库端到端：发布带高水位加权的 fixed_v2 规则，抽奖路径必须按水位推进滞回并放大返奖，
// 滞回标志随 lucky_pools 行持久化，跨请求可见。
func TestExecuteLuckyGiftDrawAppliesV2HighWaterBoostWithHysteresis(t *testing.T) {
	caller := mysqlschema.CallerFile(t, 1)
	schema := mysqlschema.New(t, mysqlschema.Config{
		EnvVar:         "LUCKY_GIFT_SERVICE_MYSQL_TEST_DSN",
		InitDBPath:     mysqlschema.InitDBPath(t, caller, "..", "..", "..", "deploy", "mysql", "initdb", "001_lucky_gift_service.sql"),
		DatabasePrefix: "hy_lucky_gift_v2boost",
	})

	repo := &Repository{db: schema.DB}
	ctx := appcode.WithContext(context.Background(), "lalu")
	// 零档权重 0 + 单一 2x 档权重 100%：抽奖结果确定为 2x，测试只观察加权对金额的影响。
	rule := domain.RuleConfig{
		PoolID:                         "lucky",
		StrategyVersion:                domain.StrategyFixedV2,
		Enabled:                        true,
		TargetRTPPPM:                   950_000,
		PoolRatePPM:                    980_000,
		SettlementWindowWager:          1_000_000,
		ControlBandPPM:                 10_000,
		GiftPriceReference:             100,
		NoviceMaxEquivalentDraws:       2_000,
		NormalMaxEquivalentDraws:       20_000,
		V2HighWaterBoostThresholdCoins: 500_000,
		V2HighWaterBoostFactorPPM:      1_500_000,
		V2HighWaterBoostRecoverCoins:   400_000,
		Stages: []domain.RuleStage{
			{Stage: domain.StageNovice, Tiers: []domain.RuleTier{
				{Stage: domain.StageNovice, TierID: "novice_none", MultiplierPPM: 0, BaseWeightPPM: 0, Enabled: true},
				{Stage: domain.StageNovice, TierID: "novice_2x", MultiplierPPM: 2_000_000, BaseWeightPPM: 1_000_000, Enabled: true},
			}},
		},
	}
	published, err := repo.PublishLuckyGiftRuleConfig(ctx, rule, 1_700_000_000_000)
	if err != nil {
		t.Fatalf("publish v2 boost rule: %v", err)
	}
	loaded, exists, err := repo.GetLuckyGiftRuleConfig(ctx, "lucky")
	if err != nil || !exists {
		t.Fatalf("read back rule: exists=%v err=%v", exists, err)
	}
	if loaded.V2HighWaterBoostThresholdCoins != 500_000 || loaded.V2HighWaterBoostFactorPPM != 1_500_000 || loaded.V2HighWaterBoostRecoverCoins != 400_000 {
		t.Fatalf("v2 boost triple did not round-trip: %+v", loaded)
	}
	if published.RuleVersion != loaded.RuleVersion {
		t.Fatalf("latest rule version mismatch: published=%d loaded=%d", published.RuleVersion, loaded.RuleVersion)
	}

	setBalance := func(balance int64) {
		t.Helper()
		if _, err := schema.DB.Exec(`
			UPDATE lucky_pools SET balance = ?
			WHERE app_code='lalu' AND scope_type=? AND scope_id='lucky'`, balance, luckyBasePoolScopeType); err != nil {
			t.Fatalf("set pool balance: %v", err)
		}
	}
	boostFlag := func() bool {
		t.Helper()
		var active bool
		if err := schema.DB.QueryRow(`
			SELECT v2_boost_active FROM lucky_pools
			WHERE app_code='lalu' AND scope_type=? AND scope_id='lucky'`, luckyBasePoolScopeType).Scan(&active); err != nil {
			t.Fatalf("read boost flag: %v", err)
		}
		return active
	}
	draw := func(commandID string) domain.DrawResult {
		t.Helper()
		result, err := repo.ExecuteLuckyGiftDraw(ctx, domain.DrawCommand{
			CommandID: commandID, PoolID: "lucky", UserID: 9001, RoomID: "room-1",
			GiftID: "lucky", GiftCount: 1, CoinSpent: 100, PaidAtMS: 1_700_000_000_000,
		}, 1_700_000_000_000)
		if err != nil {
			t.Fatalf("draw %s: %v", commandID, err)
		}
		return result
	}

	// 首抽物化奖池；默认初始水位 382,500 在阈值之上，先压回阈值下方再验证基线。
	first := draw("cmd-materialize")
	if first.SelectedTierID != "novice_2x" {
		t.Fatalf("deterministic table must select novice_2x, got %+v", first)
	}

	// 基线：水位低于阈值，2x 原价返奖。
	setBalance(100_000)
	if result := draw("cmd-normal"); result.EffectiveRewardCoins != 200 || result.MultiplierPPM != 2_000_000 {
		t.Fatalf("below threshold draw = %d coins %d ppm, want 200/2000000", result.EffectiveRewardCoins, result.MultiplierPPM)
	}
	if boostFlag() {
		t.Fatalf("boost flag must stay clear below threshold")
	}

	// 越过阈值：返奖放大 150%（2x -> 3x），滞回标志落库。
	setBalance(600_000)
	if result := draw("cmd-boosted"); result.EffectiveRewardCoins != 300 || result.MultiplierPPM != 3_000_000 {
		t.Fatalf("boosted draw = %d coins %d ppm, want 300/3000000", result.EffectiveRewardCoins, result.MultiplierPPM)
	}
	if !boostFlag() {
		t.Fatalf("boost flag must persist after crossing threshold")
	}

	// 阈值与恢复线之间：滞回保持加权。
	setBalance(450_000)
	if result := draw("cmd-hysteresis"); result.EffectiveRewardCoins != 300 {
		t.Fatalf("hysteresis draw = %d coins, want 300", result.EffectiveRewardCoins)
	}
	if !boostFlag() {
		t.Fatalf("boost flag must survive between recover line and threshold")
	}

	// 跌破恢复线：退出加权并清零标志。
	setBalance(300_000)
	if result := draw("cmd-recovered"); result.EffectiveRewardCoins != 200 || result.MultiplierPPM != 2_000_000 {
		t.Fatalf("recovered draw = %d coins %d ppm, want 200/2000000", result.EffectiveRewardCoins, result.MultiplierPPM)
	}
	if boostFlag() {
		t.Fatalf("boost flag must clear after falling below recover line")
	}

	// 批量路径共享同一套滞回与放大逻辑：高水位下 3 连抽每抽都是 3x。
	setBalance(600_000)
	batch, err := repo.ExecuteLuckyGiftDraw(ctx, domain.DrawCommand{
		CommandID: "cmd-batch", PoolID: "lucky", UserID: 9001, RoomID: "room-1",
		GiftID: "lucky", GiftCount: 3, CoinSpent: 300, PaidAtMS: 1_700_000_000_000,
	}, 1_700_000_000_000)
	if err != nil {
		t.Fatalf("batch draw: %v", err)
	}
	if batch.EffectiveRewardCoins != 900 {
		t.Fatalf("boosted batch total = %d coins, want 900 (3 draws x 100 x 3x)", batch.EffectiveRewardCoins)
	}
	if !boostFlag() {
		t.Fatalf("boost flag must persist after the batch")
	}
}
