package service

import (
	"context"
	"fmt"
	"math"
	"math/bits"
	"strings"
	"time"

	"google.golang.org/protobuf/proto"
	luckygiftv1 "hyapp.local/api/proto/luckygift/v1"
	roomv1 "hyapp.local/api/proto/room/v1"
	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/room-service/internal/room/command"
)

const (
	luckyGiftStrategyFixedV2   = "fixed_v2"
	luckyGiftStrategyDynamicV3 = "dynamic_v3"
)

func (s *Service) executeLuckyGiftDraws(ctx context.Context, meta *roomv1.RequestMeta, cmd command.SendGift, roomMeta RoomMeta, roomLocked bool, targetBillings []giftTargetBilling) ([]*roomv1.LuckyGiftDrawResult, error) {
	if len(targetBillings) == 0 {
		return nil, nil
	}
	if len(targetBillings) == 1 {
		result, err := s.executeLuckyGiftDraw(ctx, meta, cmd, roomMeta, roomLocked, targetBillings[0])
		if err != nil {
			return nil, err
		}
		return []*roomv1.LuckyGiftDrawResult{result}, nil
	}

	return s.executeLuckyGiftDrawBatch(ctx, meta, cmd, roomMeta, roomLocked, targetBillings)
}

func (s *Service) executeLuckyGiftDraw(ctx context.Context, meta *roomv1.RequestMeta, cmd command.SendGift, roomMeta RoomMeta, roomLocked bool, targetBilling giftTargetBilling) (*roomv1.LuckyGiftDrawResult, error) {
	if targetBilling.Billing == nil {
		return nil, xerr.New(xerr.Unavailable, "wallet target debit response is empty")
	}
	// 抽奖必须按每个收礼目标独立落事实；command_id、target_user_id 和 coin_spent 都来自该目标的 wallet 子交易。
	luckyMeta, err := luckyGiftMetaForTarget(ctx, meta, cmd, roomMeta, roomLocked, targetBilling)
	if err != nil {
		return nil, err
	}
	drawResp, err := s.luckyGift.ExecuteLuckyGiftDraw(ctx, &luckygiftv1.ExecuteLuckyGiftDrawRequest{
		LuckyGift: luckyMeta,
	})
	if err != nil {
		return nil, err
	}
	if drawResp == nil || drawResp.GetResult() == nil {
		return nil, xerr.New(xerr.Unavailable, "lucky gift draw response is empty")
	}
	// 同步返回只承载抽奖表现事实；返奖后的余额由 activity 在中奖落库后发送钱包私有 IM，避免 HTTP 响应领先账务通知链路。
	return luckyGiftResultFromProto(drawResp.GetResult(), targetBilling.TargetUserID), nil
}

func (s *Service) executeLuckyGiftDrawBatch(ctx context.Context, meta *roomv1.RequestMeta, cmd command.SendGift, roomMeta RoomMeta, roomLocked bool, targetBillings []giftTargetBilling) ([]*roomv1.LuckyGiftDrawResult, error) {
	req := &luckygiftv1.BatchExecuteLuckyGiftDrawRequest{LuckyGifts: make([]*luckygiftv1.LuckyGiftMeta, 0, len(targetBillings))}
	for _, targetBilling := range targetBillings {
		if targetBilling.Billing == nil {
			return nil, xerr.New(xerr.Unavailable, "wallet target debit response is empty")
		}
		luckyMeta, err := luckyGiftMetaForTarget(ctx, meta, cmd, roomMeta, roomLocked, targetBilling)
		if err != nil {
			return nil, err
		}
		// 多人送礼只跨服务调用一次；lucky-gift-service 在一个事务内按这个顺序推进锁和抽奖事实。
		req.LuckyGifts = append(req.LuckyGifts, luckyMeta)
	}
	resp, err := s.luckyGift.BatchExecuteLuckyGiftDraw(ctx, req)
	if err != nil {
		return nil, err
	}
	if resp == nil || len(resp.GetResults()) != len(targetBillings) {
		return nil, xerr.New(xerr.Unavailable, "lucky gift batch draw response is incomplete")
	}
	results := make([]*roomv1.LuckyGiftDrawResult, 0, len(targetBillings))
	for index, result := range resp.GetResults() {
		if result == nil {
			return nil, xerr.New(xerr.Unavailable, "lucky gift batch draw result is empty")
		}
		results = append(results, luckyGiftResultFromProto(result, targetBillings[index].TargetUserID))
	}
	return results, nil
}

func luckyGiftMetaForTarget(ctx context.Context, meta *roomv1.RequestMeta, cmd command.SendGift, roomMeta RoomMeta, roomLocked bool, targetBilling giftTargetBilling) (*luckygiftv1.LuckyGiftMeta, error) {
	paidAtMS := targetBilling.Billing.GetPaidAtMs()
	if paidAtMS <= 0 && normalizeLuckyGiftStrategyVersion(cmd.LuckyGiftStrategyVersion) == luckyGiftStrategyDynamicV3 {
		// dynamic_v3 所有时间窗口必须归属 wallet owner 的实际交易；缺字段通常表示 wallet 尚未滚动升级，
		// 已扣费 saga 保持可恢复并等待 owner 回执补齐，绝不能拿 room mutate/recovery 时钟继续开奖。
		return nil, xerr.New(xerr.Unavailable, "wallet paid_at_ms is required for dynamic lucky gift")
	}
	return &luckygiftv1.LuckyGiftMeta{
		Meta:         luckyGiftMetaFromRoom(ctx, meta),
		CommandId:    targetBilling.CommandID,
		UserId:       cmd.ActorUserID(),
		TargetUserId: targetBilling.TargetUserID,
		// DeviceID 已在首次请求进入 gift operation 时随 command.Base 持久化，恢复必须继续使用该值。
		// 空值原样传给 owner：dynamic_v3 会跳过设备日额度但保留其他风控；绝不用 session_id/command_id 伪造设备。
		DeviceId:  cmd.DeviceID,
		RoomId:    cmd.RoomID(),
		AnchorId:  luckyGiftAnchorID(roomMeta),
		GiftId:    cmd.GiftID,
		GiftCount: cmd.GiftCount,
		CoinSpent: targetBilling.Billing.GetCoinSpent(),
		// PaidAtMs 只来自当前目标的 wallet transaction；batch 每个 target 也保留自己的 owner 回执，
		// 不使用聚合回执、Room Cell mutate 时钟或 gift_operation.created_at_ms。
		PaidAtMs:        paidAtMS,
		PoolId:          cmd.PoolID,
		VisibleRegionId: roomMeta.VisibleRegionID,
		// 锁房状态与 presence 校验取自同一个串行 Room Cell 快照；lucky owner 只持久化展示事实，不能跨库回查房间。
		RoomLocked: roomLocked,
		// 幸运礼物使用原始送礼持久 RoomMeta 的房主身份；发送者、中奖人和异步回查结果都不能替代该快照。
		OwnerUserId: roomMeta.OwnerUserID,
		CountryId:   cmd.SenderCountryID,
		// 账号创建时间来自 gateway 的 user-service 快照并随首次命令持久化；owner 只用它判断
		// 支付时账号是否已满完整历史窗口。0 表示旧命令/缺失事实，补偿大奖资格必须 fail-close。
		UserRegisteredAtMs: cmd.SenderRegisteredAtMS,
		// 展示资料来自 gateway 本次送礼入口的用户快照，随抽奖事实持久化后，下游不需要在异步消费时反查当前资料。
		SenderName:                giftDisplayName(cmd.SenderDisplayProfile),
		SenderAvatar:              strings.TrimSpace(cmd.SenderDisplayProfile.Avatar),
		SenderDisplayUserId:       strings.TrimSpace(cmd.SenderDisplayProfile.DisplayUserID),
		SenderPrettyDisplayUserId: strings.TrimSpace(cmd.SenderDisplayProfile.PrettyDisplayUserID),
		// 充值分层和短时加权只消费 wallet 扣费回执里固化的 owner 快照；room 不查询充值流水，也不按当前时间重新计算。
		Recharge_7DCoins:  targetBilling.Billing.GetRechargeSevenDayCoins(),
		Recharge_30DCoins: targetBilling.Billing.GetRechargeThirtyDayCoins(),
		LastRechargedAtMs: targetBilling.Billing.GetLastRechargedAtMs(),
		// LuckyGiftMeta.gift_income_coins 是滚动兼容保留的旧字段名，但资金事实只允许读取
		// Host 政策结算出的主播基础收益；已退役的直接 POINT 回执字段即使来自旧重放也不能重新参与拆分。
		// Agency 额外收益不属于主播收益，绝不能混入幸运礼物公共池/平台利润的守恒拆分。
		GiftIncomeCoins: hostBaseIncomeFromBilling(targetBilling.Billing),
	}, nil
}

func hostBaseIncomeFromBilling(billing *walletv1.DebitGiftResponse) int64 {
	if billing == nil {
		return 0
	}
	if value := billing.GetHostBaseAmount(); value > 0 {
		return value
	}
	// 升级前已扣费 saga 只有 SALARY_DIAMOND 周期钻石字段，保留其恢复能力。
	return billing.GetHostPeriodDiamondAdded()
}

func normalizeLuckyGiftStrategyVersion(value string) string {
	value = strings.ToLower(strings.TrimSpace(value))
	if value == "" {
		// 旧 lucky-gift 版本没有返回 strategy_version；它发布时只有 fixed_v2，故仅该空值兼容成 fixed_v2。
		return luckyGiftStrategyFixedV2
	}
	return value
}

func (s *Service) shouldDrawLuckyGift(poolID string, giftTypeCode string) bool {
	if s.luckyGift == nil {
		return false
	}
	switch strings.TrimSpace(giftTypeCode) {
	case "lucky", "super_lucky":
		return true
	default:
		return strings.TrimSpace(poolID) != ""
	}
}

func (s *Service) withLuckyGiftSendLock(ctx context.Context, enabled bool, userID int64, fn func() error) error {
	if !enabled || s == nil || s.luckyGiftSendLocker == nil {
		return fn()
	}
	release, err := s.luckyGiftSendLocker.AcquireLuckyGiftSendLock(ctx, appcode.FromContext(ctx), userID, s.luckyGiftSendLockTTL)
	if err != nil {
		return err
	}
	if release != nil {
		defer func() {
			releaseCtx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()
			release(releaseCtx)
		}()
	}
	return fn()
}

func luckyGiftMetaFromRoom(ctx context.Context, meta *roomv1.RequestMeta) *luckygiftv1.RequestMeta {
	return &luckygiftv1.RequestMeta{
		RequestId:     meta.GetRequestId(),
		Caller:        "room-service",
		GatewayNodeId: meta.GetGatewayNodeId(),
		SentAtMs:      time.Now().UTC().UnixMilli(),
		AppCode:       appcode.FromContext(ctx),
	}
}

func luckyGiftAnchorID(roomMeta RoomMeta) string {
	if roomMeta.OwnerUserID <= 0 {
		return roomMeta.RoomID
	}
	return fmt.Sprintf("%d", roomMeta.OwnerUserID)
}

func luckyGiftResultFromProto(result *luckygiftv1.LuckyGiftDrawResult, targetUserID int64) *roomv1.LuckyGiftDrawResult {
	if result == nil {
		return nil
	}
	mapped := &roomv1.LuckyGiftDrawResult{
		Enabled:              true,
		DrawId:               result.GetDrawId(),
		CommandId:            result.GetCommandId(),
		PoolId:               result.GetPoolId(),
		GiftId:               result.GetGiftId(),
		RuleVersion:          result.GetRuleVersion(),
		ExperiencePool:       result.GetExperiencePool(),
		SelectedTierId:       result.GetSelectedTierId(),
		MultiplierPpm:        result.GetMultiplierPpm(),
		BaseRewardCoins:      result.GetBaseRewardCoins(),
		EffectiveRewardCoins: result.GetEffectiveRewardCoins(),
		RewardStatus:         result.GetRewardStatus(),
		StageFeedback:        result.GetStageFeedback(),
		HighMultiplier:       result.GetHighMultiplier(),
		CreatedAtMs:          result.GetCreatedAtMs(),
		WalletTransactionId:  result.GetWalletTransactionId(),
		// 这里故意不透出 activity 返回的返奖后余额。SendGift HTTP 只承诺扣费后的真实余额，中奖返奖后的余额必须等钱包落账后由 WalletBalanceChanged 私有 IM 覆盖。
		CoinBalanceAfter: 0,
		TargetUserId:     targetUserID,
	}
	for _, hit := range result.GetHits() {
		if hit == nil || hit.GetRewardCoins() <= 0 {
			continue
		}
		mapped.Hits = append(mapped.Hits, &roomv1.SendGiftLuckyHit{
			GiftIndex: hit.GetGiftIndex(), DrawId: hit.GetDrawId(), TargetUserId: targetUserID,
			SelectedTierId: hit.GetSelectedTierId(), MultiplierPpm: hit.GetMultiplierPpm(), RewardCoins: hit.GetRewardCoins(),
		})
	}
	return mapped
}

func aggregateLuckyGiftResults(commandID string, results []*roomv1.LuckyGiftDrawResult) *roomv1.LuckyGiftDrawResult {
	if len(results) == 0 {
		return nil
	}
	if len(results) == 1 {
		return results[0]
	}
	aggregate := proto.Clone(results[0]).(*roomv1.LuckyGiftDrawResult)
	aggregate.CommandId = commandID
	aggregate.SelectedTierId = "batch"
	aggregate.TargetUserId = 0
	aggregate.WalletTransactionId = ""
	aggregate.MultiplierPpm = 0
	aggregate.BaseRewardCoins = 0
	aggregate.EffectiveRewardCoins = 0
	aggregate.RewardStatus = "granted"
	aggregate.StageFeedback = false
	aggregate.HighMultiplier = false
	aggregate.CoinBalanceAfter = 0
	aggregate.Hits = nil
	for _, result := range results {
		if result == nil {
			continue
		}
		aggregate.RuleVersion = result.GetRuleVersion()
		aggregate.ExperiencePool = result.GetExperiencePool()
		aggregate.CreatedAtMs = result.GetCreatedAtMs()
		aggregate.MultiplierPpm += result.GetMultiplierPpm()
		aggregate.BaseRewardCoins += result.GetBaseRewardCoins()
		aggregate.EffectiveRewardCoins += result.GetEffectiveRewardCoins()
		aggregate.StageFeedback = aggregate.StageFeedback || result.GetStageFeedback()
		aggregate.HighMultiplier = aggregate.HighMultiplier || result.GetHighMultiplier()
		aggregate.Hits = append(aggregate.Hits, result.GetHits()...)
		switch result.GetRewardStatus() {
		case "pending":
			aggregate.RewardStatus = "pending"
		case "failed":
			if aggregate.RewardStatus != "pending" {
				aggregate.RewardStatus = "failed"
			}
		case "granted":
		default:
			if aggregate.RewardStatus == "" {
				aggregate.RewardStatus = result.GetRewardStatus()
			}
		}
	}
	return aggregate
}

func syntheticLuckyGiftResult(cmd command.SendGift, targetBillings []giftTargetBilling, now time.Time) *roomv1.LuckyGiftDrawResult {
	if len(targetBillings) == 0 || targetBillings[0].Billing == nil {
		return nil
	}
	coinSpent := targetBillings[0].Billing.GetCoinSpent()
	multiplier := firstPositiveInt64(cmd.SyntheticLuckyMultiplierPPM, 5000000)
	// 未显式指定中奖额时必须按同一 multiplier 推导，避免 20x 文案却展示 5x 金额；计算饱和防止异常配置溢出。
	defaultRewardCoins := scaledRobotRewardCoins(coinSpent, multiplier)
	rewardCoins := firstPositiveInt64(cmd.SyntheticLuckyRewardCoins, defaultRewardCoins)
	drawID := fmt.Sprintf("robot_lucky_%s_%d", cmd.ID(), now.UnixMilli())
	return &roomv1.LuckyGiftDrawResult{
		Enabled:              true,
		DrawId:               drawID,
		CommandId:            cmd.ID(),
		PoolId:               cmd.PoolID,
		GiftId:               cmd.GiftID,
		MultiplierPpm:        multiplier,
		BaseRewardCoins:      rewardCoins,
		EffectiveRewardCoins: rewardCoins,
		RewardStatus:         "granted",
		StageFeedback:        true,
		HighMultiplier:       multiplier >= 5000000,
		CreatedAtMs:          now.UnixMilli(),
		TargetUserId:         cmd.TargetUserID,
		Hits: []*roomv1.SendGiftLuckyHit{{
			GiftIndex: 1, DrawId: drawID, TargetUserId: cmd.TargetUserID, MultiplierPpm: multiplier, RewardCoins: rewardCoins,
		}},
	}
}

func scaledRobotRewardCoins(coinSpent int64, multiplierPPM int64) int64 {
	if coinSpent <= 0 || multiplierPPM <= 0 {
		return 0
	}
	// bits.Mul64 + Div64 完成无符号 128 位乘除；高 64 位已不小于除数时，商超出 uint64，直接饱和。
	high, low := bits.Mul64(uint64(coinSpent), uint64(multiplierPPM))
	if high >= 1_000_000 {
		return math.MaxInt64
	}
	reward, _ := bits.Div64(high, low, 1_000_000)
	if reward > math.MaxInt64 {
		return math.MaxInt64
	}
	return int64(reward)
}

func giftFinalCoinBalanceAfter(billing *walletv1.DebitGiftResponse) int64 {
	if billing == nil {
		return 0
	}
	// SendGift 的 HTTP 余额只表示本次送礼扣费完成后的 COIN 余额；lucky/super_lucky 返奖属于后续入账事实，由钱包余额 IM 更新客户端。
	return billing.GetBalanceAfter()
}

func firstPositiveInt64(values ...int64) int64 {
	for _, value := range values {
		if value > 0 {
			return value
		}
	}
	return 0
}
