package lotteryengine

import (
	crand "crypto/rand"
	"errors"
	"math/big"
	"strings"
)

const (
	PrizeKindCoin  = "coin"
	PrizeKindGift  = "gift"
	PrizeKindProp  = "prop"
	PrizeKindDress = "dress"
)

var (
	ErrNoCandidate   = errors.New("lottery candidate is empty")
	ErrInvalidWeight = errors.New("lottery candidate weight is invalid")
)

// Candidate 是抽奖引擎唯一关心的奖档视图。
// 业务模块负责在进入引擎前完成资格、奖池和风控过滤；引擎只保证权重随机和 RTP 计值口径一致。
type Candidate struct {
	ID            string
	Weight        int64
	PrizeKind     string
	RTPValueCoins int64
}

// NormalizeCandidates 复制并清洗候选集，避免调用方的业务快照被引擎内部兜底权重改写。
// 全部候选权重都为 0 时退化成等权随机，是为了兼容旧配置或脏数据；负权重直接拒绝，防止配置错误改变概率方向。
func NormalizeCandidates(candidates []Candidate) ([]Candidate, error) {
	if len(candidates) == 0 {
		return nil, ErrNoCandidate
	}
	out := make([]Candidate, 0, len(candidates))
	var totalWeight int64
	for _, candidate := range candidates {
		if candidate.Weight < 0 {
			return nil, ErrInvalidWeight
		}
		candidate.PrizeKind = NormalizePrizeKind(candidate.PrizeKind)
		candidate.RTPValueCoins = NormalizeRTPValueCoins(candidate.PrizeKind, candidate.RTPValueCoins)
		if candidate.Weight > 0 {
			totalWeight += candidate.Weight
			out = append(out, candidate)
		}
	}
	if totalWeight > 0 {
		return out, nil
	}
	out = out[:0]
	for _, candidate := range candidates {
		candidate.PrizeKind = NormalizePrizeKind(candidate.PrizeKind)
		candidate.RTPValueCoins = NormalizeRTPValueCoins(candidate.PrizeKind, candidate.RTPValueCoins)
		candidate.Weight = 1
		out = append(out, candidate)
	}
	return out, nil
}

// SelectWeighted 从已经归一化的候选中按权重抽一档。
// 随机源使用 crypto/rand，避免高价值奖档被时间种子或并发顺序预测。
func SelectWeighted(candidates []Candidate) (Candidate, error) {
	normalized, err := NormalizeCandidates(candidates)
	if err != nil {
		return Candidate{}, err
	}
	var totalWeight int64
	for _, candidate := range normalized {
		totalWeight += candidate.Weight
	}
	if totalWeight <= 0 {
		return Candidate{}, ErrNoCandidate
	}
	index, err := secureIndex(totalWeight)
	if err != nil {
		return Candidate{}, err
	}
	for _, candidate := range normalized {
		if index < candidate.Weight {
			return candidate, nil
		}
		index -= candidate.Weight
	}
	return normalized[len(normalized)-1], nil
}

// NormalizePrizeKind 把后台和活动配置里的同义奖品类型收敛到引擎内部口径。
// prop 和 dress 都表示装扮/道具类权益，后续发放可以不同，但 RTP 口径必须一致归零。
func NormalizePrizeKind(kind string) string {
	switch strings.ToLower(strings.TrimSpace(kind)) {
	case PrizeKindCoin:
		return PrizeKindCoin
	case PrizeKindGift:
		return PrizeKindGift
	case PrizeKindProp, "item", "decoration", "resource":
		return PrizeKindProp
	case PrizeKindDress, "dress_up", "costume":
		return PrizeKindDress
	default:
		return strings.ToLower(strings.TrimSpace(kind))
	}
}

// NormalizeRTPValueCoins 是转盘和其它抽奖共用的 RTP 计值入口。
// 装扮/道具无论后台配置了多少展示价值，都不能进入 RTP 返奖分子；金币和礼物保留配置值，负数按 0 处理。
func NormalizeRTPValueCoins(kind string, value int64) int64 {
	if value < 0 {
		value = 0
	}
	switch NormalizePrizeKind(kind) {
	case PrizeKindProp, PrizeKindDress:
		return 0
	default:
		return value
	}
}

func secureIndex(totalWeight int64) (int64, error) {
	if totalWeight <= 0 {
		return 0, ErrNoCandidate
	}
	n, err := crand.Int(crand.Reader, big.NewInt(totalWeight))
	if err != nil {
		return 0, err
	}
	return n.Int64(), nil
}
