package mysql

import (
	"context"
	"database/sql"
	"errors"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	domain "hyapp/services/activity-service/internal/domain/luckygift"
)

type luckyRuleQueryer interface {
	QueryContext(context.Context, string, ...any) (*sql.Rows, error)
	QueryRowContext(context.Context, string, ...any) *sql.Row
}

// GetLuckyGiftRuleConfig 读取指定奖池最新 v2 规则版本；旧抽奖运行表不会参与后台配置读取。
func (r *Repository) GetLuckyGiftRuleConfig(ctx context.Context, poolID string) (domain.RuleConfig, bool, error) {
	if r == nil || r.db == nil {
		return domain.RuleConfig{}, false, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	return r.getLuckyGiftRuleConfig(ctx, r.db, appcode.FromContext(ctx), luckyPoolID(poolID), false)
}

// PublishLuckyGiftRuleConfig 只新增不可变版本，不覆盖历史配置；后续 v2 抽奖引擎按版本读取快照。
func (r *Repository) PublishLuckyGiftRuleConfig(ctx context.Context, config domain.RuleConfig, nowMS int64) (domain.RuleConfig, error) {
	if r == nil || r.db == nil {
		return domain.RuleConfig{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return domain.RuleConfig{}, err
	}
	defer func() { _ = tx.Rollback() }()

	appCode := appcode.FromContext(ctx)
	config.AppCode = appCode
	config.PoolID = luckyPoolID(config.PoolID)
	config.RuleVersion = 1
	config.CreatedAtMS = nowMS
	if config.EffectiveFromMS <= 0 {
		config.EffectiveFromMS = nowMS
	}
	var currentVersion int64
	err = tx.QueryRowContext(ctx, `
		SELECT rule_version
		FROM lucky_gift_rule_versions
		WHERE app_code = ? AND pool_id = ?
		ORDER BY rule_version DESC
		LIMIT 1
		FOR UPDATE`,
		appCode, config.PoolID,
	).Scan(&currentVersion)
	if err != nil && !errors.Is(err, sql.ErrNoRows) {
		return domain.RuleConfig{}, err
	}
	if err == nil {
		config.RuleVersion = currentVersion + 1
	}
	if _, err := tx.ExecContext(ctx, `
		INSERT INTO lucky_gift_rule_versions (
			app_code, pool_id, rule_version, enabled, target_rtp_ppm, pool_rate_ppm,
			settlement_window_wager, control_band_ppm, gift_price_reference,
			novice_max_equivalent_draws, normal_max_equivalent_draws,
			max_single_payout, user_hourly_payout_cap, user_daily_payout_cap, device_daily_payout_cap,
			room_hourly_payout_cap, anchor_daily_payout_cap, effective_from_ms, created_by_admin_id, created_at_ms
		) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
		config.AppCode, config.PoolID, config.RuleVersion, config.Enabled, config.TargetRTPPPM, config.PoolRatePPM,
		config.SettlementWindowWager, config.ControlBandPPM, config.GiftPriceReference,
		config.NoviceMaxEquivalentDraws, config.NormalMaxEquivalentDraws,
		config.MaxSinglePayout, config.UserHourlyPayoutCap, config.UserDailyPayoutCap, config.DeviceDailyPayoutCap,
		config.RoomHourlyPayoutCap, config.AnchorDailyPayoutCap,
		config.EffectiveFromMS, config.CreatedByAdminID, config.CreatedAtMS,
	); err != nil {
		return domain.RuleConfig{}, err
	}
	for _, stage := range config.Stages {
		for _, tier := range stage.Tiers {
			if _, err := tx.ExecContext(ctx, `
				INSERT INTO lucky_gift_stage_tiers (
					app_code, pool_id, rule_version, stage, tier_id, multiplier_ppm, base_weight_ppm,
					reward_source, high_water_only, broadcast_level, enabled
				) VALUES (?,?,?,?,?,?,?,?,?,?,?)`,
				config.AppCode, config.PoolID, config.RuleVersion, tier.Stage, tier.TierID, tier.MultiplierPPM, tier.BaseWeightPPM,
				tier.RewardSource, tier.HighWaterOnly, tier.BroadcastLevel, tier.Enabled,
			); err != nil {
				return domain.RuleConfig{}, err
			}
		}
	}
	if err := tx.Commit(); err != nil {
		return domain.RuleConfig{}, err
	}
	return config, nil
}

// ListLuckyGiftRuleConfigs 返回每个奖池的最新 v2 规则版本，用于后台奖池列表。
func (r *Repository) ListLuckyGiftRuleConfigs(ctx context.Context) ([]domain.RuleConfig, error) {
	if r == nil || r.db == nil {
		return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	appCode := appcode.FromContext(ctx)
	rows, err := r.db.QueryContext(ctx, `
		SELECT v.app_code, v.pool_id, v.rule_version, v.enabled, v.target_rtp_ppm, v.pool_rate_ppm,
		       v.settlement_window_wager, v.control_band_ppm, v.gift_price_reference,
		       v.novice_max_equivalent_draws, v.normal_max_equivalent_draws,
		       v.max_single_payout, v.user_hourly_payout_cap, v.user_daily_payout_cap, v.device_daily_payout_cap,
		       v.room_hourly_payout_cap, v.anchor_daily_payout_cap,
		       v.effective_from_ms, v.created_by_admin_id, v.created_at_ms
		FROM lucky_gift_rule_versions v
		INNER JOIN (
			SELECT app_code, pool_id, MAX(rule_version) AS rule_version
			FROM lucky_gift_rule_versions
			WHERE app_code = ?
			GROUP BY app_code, pool_id
		) latest ON latest.app_code = v.app_code AND latest.pool_id = v.pool_id AND latest.rule_version = v.rule_version
		ORDER BY v.created_at_ms DESC, v.pool_id ASC`,
		appCode,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	configs := make([]domain.RuleConfig, 0)
	for rows.Next() {
		config, err := scanLuckyGiftRuleConfig(rows)
		if err != nil {
			return nil, err
		}
		stages, err := r.getLuckyGiftRuleStages(ctx, r.db, config.AppCode, config.PoolID, config.RuleVersion)
		if err != nil {
			return nil, err
		}
		config.Stages = stages
		configs = append(configs, config)
	}
	return configs, rows.Err()
}

func (r *Repository) getLuckyGiftRuleConfig(ctx context.Context, q luckyRuleQueryer, appCode string, poolID string, forUpdate bool) (domain.RuleConfig, bool, error) {
	if poolID == "" {
		return domain.RuleConfig{}, false, nil
	}
	lockSQL := ""
	if forUpdate {
		lockSQL = " FOR UPDATE"
	}
	row := q.QueryRowContext(ctx, `
		SELECT app_code, pool_id, rule_version, enabled, target_rtp_ppm, pool_rate_ppm,
		       settlement_window_wager, control_band_ppm, gift_price_reference,
		       novice_max_equivalent_draws, normal_max_equivalent_draws,
		       max_single_payout, user_hourly_payout_cap, user_daily_payout_cap, device_daily_payout_cap,
		       room_hourly_payout_cap, anchor_daily_payout_cap,
		       effective_from_ms, created_by_admin_id, created_at_ms
		FROM lucky_gift_rule_versions
		WHERE app_code = ? AND pool_id = ?
		ORDER BY rule_version DESC
		LIMIT 1`+lockSQL,
		appCode, poolID,
	)
	config, err := scanLuckyGiftRuleConfig(row)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return domain.RuleConfig{}, false, nil
		}
		return domain.RuleConfig{}, false, err
	}
	stages, err := r.getLuckyGiftRuleStages(ctx, q, appCode, poolID, config.RuleVersion)
	if err != nil {
		return domain.RuleConfig{}, false, err
	}
	config.Stages = stages
	return config, true, nil
}

func (r *Repository) getLuckyGiftRuleStages(ctx context.Context, q luckyRuleQueryer, appCode string, poolID string, ruleVersion int64) ([]domain.RuleStage, error) {
	rows, err := q.QueryContext(ctx, `
		SELECT stage, tier_id, multiplier_ppm, base_weight_ppm, reward_source, high_water_only, broadcast_level, enabled
		FROM lucky_gift_stage_tiers
		WHERE app_code = ? AND pool_id = ? AND rule_version = ?
		ORDER BY FIELD(stage, 'novice', 'normal', 'advanced'), multiplier_ppm ASC, tier_id ASC`,
		appCode, poolID, ruleVersion,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	stageIndex := map[string]int{}
	stages := make([]domain.RuleStage, 0, 3)
	for rows.Next() {
		var tier domain.RuleTier
		if err := rows.Scan(&tier.Stage, &tier.TierID, &tier.MultiplierPPM, &tier.BaseWeightPPM, &tier.RewardSource, &tier.HighWaterOnly, &tier.BroadcastLevel, &tier.Enabled); err != nil {
			return nil, err
		}
		index, exists := stageIndex[tier.Stage]
		if !exists {
			index = len(stages)
			stageIndex[tier.Stage] = index
			stages = append(stages, domain.RuleStage{Stage: tier.Stage})
		}
		stages[index].Tiers = append(stages[index].Tiers, tier)
	}
	return stages, rows.Err()
}

func scanLuckyGiftRuleConfig(scanner luckyScanner) (domain.RuleConfig, error) {
	var config domain.RuleConfig
	if err := scanner.Scan(
		&config.AppCode,
		&config.PoolID,
		&config.RuleVersion,
		&config.Enabled,
		&config.TargetRTPPPM,
		&config.PoolRatePPM,
		&config.SettlementWindowWager,
		&config.ControlBandPPM,
		&config.GiftPriceReference,
		&config.NoviceMaxEquivalentDraws,
		&config.NormalMaxEquivalentDraws,
		&config.MaxSinglePayout,
		&config.UserHourlyPayoutCap,
		&config.UserDailyPayoutCap,
		&config.DeviceDailyPayoutCap,
		&config.RoomHourlyPayoutCap,
		&config.AnchorDailyPayoutCap,
		&config.EffectiveFromMS,
		&config.CreatedByAdminID,
		&config.CreatedAtMS,
	); err != nil {
		return domain.RuleConfig{}, err
	}
	return config, nil
}
