package activity

import (
	"context"
	"fmt"
	"strings"
	"time"

	"bindbox-game/configs"
	"bindbox-game/internal/pkg/notify"
	"bindbox-game/internal/pkg/util/remark"
	"bindbox-game/internal/pkg/wechat"
	"bindbox-game/internal/repository/mysql/model"
	strat "bindbox-game/internal/service/activity/strategy"
	"bindbox-game/internal/service/sysconfig"
	usersvc "bindbox-game/internal/service/user"

	"go.uber.org/zap"
)

// ProcessOrderLottery 处理订单开奖（统原子化高性能幂等逻辑）
func (s *service) ProcessOrderLottery(ctx context.Context, orderID int64) error {
	s.logger.Info("开始原子化处理订单开奖", zap.Int64("order_id", orderID))

	// 1. Redis 分布式锁：强制同一个订单串行处理，防止并发竞态引起的超发/漏发
	lockKey := fmt.Sprintf("lock:lottery:order:%d", orderID)
	locked, err := s.redis.SetNX(ctx, lockKey, "1", 120*time.Second).Result()
	if err != nil {
		return fmt.Errorf("分布式锁获取异常: %w", err)
	}
	if !locked {
		s.logger.Info("订单开奖锁已存在，跳过本次处理", zap.Int64("order_id", orderID))
		return nil
	}
	defer s.redis.Del(ctx, lockKey)

	// 2. 批量预加载快照：将后续循环所需的查询合并为一次性加载
	order, err := s.readDB.Orders.WithContext(ctx).Where(s.readDB.Orders.ID.Eq(orderID)).First()
	if err != nil || order == nil {
		return fmt.Errorf("订单查询失败或不存在")
	}

	// 状态前校验：仅处理已支付且未取消的抽奖订单
	// SourceType: 2=Common Lottery (WeChat/Points mixed), 4=Game Pass (Pure)
	if order.Status != 2 || (order.SourceType != 2 && order.SourceType != 4) {
		return nil
	}

	rmk := remark.Parse(order.Remark)
	aid := rmk.ActivityID
	iss := rmk.IssueID
	dc := rmk.Count
	icID := rmk.ItemCardID
	if aid <= 0 || iss <= 0 {
		return fmt.Errorf("订单备注关键信息缺失")
	}

	orderNo := order.OrderNo
	userID := order.UserID

	// 2.1 批量加载配置
	act, _ := s.readDB.Activities.WithContext(ctx).Where(s.readDB.Activities.ID.Eq(aid)).First()
	actName := "活动"
	playType := "default"
	if act != nil {
		actName = act.Name
		playType = act.PlayType
	}

	rewardSettings, _ := s.readDB.ActivityRewardSettings.WithContext(ctx).Where(s.readDB.ActivityRewardSettings.IssueID.Eq(iss)).Find()
	rewardMap := make(map[int64]*model.ActivityRewardSettings)
	productIDs := make([]int64, 0, len(rewardSettings))
	for _, r := range rewardSettings {
		rewardMap[r.ID] = r
		if r.ProductID > 0 {
			productIDs = append(productIDs, r.ProductID)
		}
	}
	// 批量预加载商品名称
	productNameMap := make(map[int64]string)
	if len(productIDs) > 0 {
		products, _ := s.readDB.Products.WithContext(ctx).Where(s.readDB.Products.ID.In(productIDs...)).Find()
		for _, p := range products {
			productNameMap[p.ID] = p.Name
		}
	}

	// 2.2 批量加载已有记录
	existingLogs, _ := s.readDB.ActivityDrawLogs.WithContext(ctx).Where(s.readDB.ActivityDrawLogs.OrderID.Eq(orderID)).Order(s.readDB.ActivityDrawLogs.DrawIndex).Find()
	logMap := make(map[int64]*model.ActivityDrawLogs)
	for _, l := range existingLogs {
		logMap[l.DrawIndex] = l
	}

	existingInventory, _ := s.readDB.UserInventory.WithContext(ctx).Where(s.readDB.UserInventory.OrderID.Eq(orderID)).Find()
	invCountMap := make(map[int64]int64)
	invTotalCount := int64(len(existingInventory)) // 总发放数量（用于无限赏模式检测）
	for _, inv := range existingInventory {
		invCountMap[inv.RewardID]++
	}

	// 2.3 策略准备
	var sel strat.ActivityDrawStrategy
	if act != nil && act.PlayType == "ichiban" {
		sel = strat.NewIchiban(s.readDB, s.writeDB)
	} else {
		sel = strat.NewDefault(s.readDB, s.writeDB)
	}

	// 3. 核心循环：基于内存快照进行原子发放
	// 开始开奖循环
	for i := int64(0); i < dc; i++ {
		log, exists := logMap[i]
		if !exists {
			// 选品：如果日志不存在，则根据策略选出一个奖品 ID
			var rid int64
			var proof map[string]any
			var selErr error

			if act != nil && act.PlayType == "ichiban" {
				slot := rmk.GetSlotAtIndex(i)
				if slot < 0 {
					s.logger.Warn("一番赏格位无效，跳过", zap.Int64("draw_index", i), zap.Int64("slot", slot))
					continue
				}
				rid, proof, selErr = sel.SelectItemBySlot(ctx, aid, iss, slot)
			} else {
				// 使用预加载的数据进行选品，避免每次循环都查询数据库
				if defSel, ok := sel.(*strat.DefaultStrategy); ok {
					rid, proof, selErr = defSel.SelectItemFromCache(rewardSettings, act.CommitmentSeedMaster, iss, order.UserID)
				} else {
					rid, proof, selErr = sel.SelectItem(ctx, aid, iss, order.UserID)
				}
			}

			if selErr != nil || rid <= 0 {
				s.logger.Error("选品失败", zap.Int64("order_id", orderID), zap.Int64("draw_index", i), zap.Int64("rid", rid), zap.Error(selErr))
				continue
			}

			rw := rewardMap[rid]
			if rw == nil {
				s.logger.Warn("奖品配置不存在，跳过", zap.Int64("draw_index", i), zap.Int64("reward_id", rid))
				continue
			}

			// 写入开奖日志 (依靠数据库 (order_id, draw_index) 唯一键保证绝对幂等)
			log = &model.ActivityDrawLogs{
				UserID: order.UserID, IssueID: iss, OrderID: order.ID,
				RewardID: rid, IsWinner: 1, Level: rw.Level, CurrentLevel: 1, DrawIndex: i,
			}
			if errLog := s.writeDB.ActivityDrawLogs.WithContext(ctx).Create(log); errLog != nil {
				// 并发情况：回退并尝试复用已有记录
				log, _ = s.readDB.ActivityDrawLogs.WithContext(ctx).Where(s.readDB.ActivityDrawLogs.OrderID.Eq(orderID), s.readDB.ActivityDrawLogs.DrawIndex.Eq(i)).First()
				if log == nil {
					s.logger.Error("创建开奖日志失败且无法复用", zap.Int64("draw_index", i), zap.Error(errLog))
					continue
				}
			}
			_ = strat.SaveDrawReceipt(ctx, s.writeDB, log.ID, iss, order.UserID, proof)
			logMap[i] = log
		}
	}

	// 4. 批量发放奖励（单事务处理，大幅提升性能）
	// 收集所有需要发放的奖励项
	var batchItems []usersvc.BatchRewardItem
	var effectLogs []*model.ActivityDrawLogs // 需要处理道具卡效果的日志

	// 无限赏模式下使用总数检测（因为inventory.RewardID=0）
	// 如果已发放总数已达到开奖数量，说明已完成发放，跳过后续逻辑
	if invTotalCount >= dc {
		// s.logger.Info("奖励已全部发放，跳过重复发放", zap.Int64("order_id", orderID), zap.Int64("dc", dc), zap.Int64("invTotalCount", invTotalCount))
	} else {
		for i := int64(0); i < dc; i++ {
			log, ok := logMap[i]
			if !ok || log == nil {
				continue
			}

			// 统计该 RewardID 应得数量
			needed := int64(0)
			for j := int64(0); j <= i; j++ {
				if l, ok := logMap[j]; ok && l.RewardID == log.RewardID {
					needed++
				}
			}

			// 检查是否需要发放
			if invCountMap[log.RewardID] < needed {
				rw := rewardMap[log.RewardID]
				if rw != nil {
					var rewardIDRef *int64
					if act != nil && act.PlayType == "ichiban" {
						rewardIDRef = &log.RewardID
					}
					batchItems = append(batchItems, usersvc.BatchRewardItem{
						ProductID:  rw.ProductID,
						RewardID:   rewardIDRef,
						ActivityID: aid,
						Remark:     productNameMap[rw.ProductID],
					})
					invCountMap[log.RewardID]++ // 内存计数同步

					// 记录需要处理道具卡效果的日志
					if act != nil && act.AllowItemCards && icID > 0 {
						effectLogs = append(effectLogs, log)
					}
				}
			}
		}
	}

	// 批量发放奖励（单个事务）
	if len(batchItems) > 0 {
		_, errGrant := s.user.BatchGrantRewardsToOrder(ctx, order.UserID, order.ID, batchItems)
		if errGrant != nil {
			s.logger.Error("批量发放奖励失败", zap.Int64("order_id", orderID), zap.Int("count", len(batchItems)), zap.Error(errGrant))
		} else {
			// 处理道具卡效果 - 只对第一发生效（翻倍卡只翻倍一次）
			if len(effectLogs) > 0 {
				s.applyItemCardEffect(ctx, icID, aid, iss, effectLogs[0])
			}
		}
	}

	s.logger.Info("开奖完成", zap.Int64("order_id", orderID), zap.Int("completed", len(logMap)))

	// 5. 异步触发外部同步逻辑 (微信虚拟发货/通知)
	if order.IsConsumed == 0 {
		go s.TriggerVirtualShipping(context.Background(), orderID, orderNo, userID, aid, actName, playType)
	}

	return nil
}

// TriggerVirtualShipping 触发虚拟发货同步到微信
func (s *service) TriggerVirtualShipping(ctx context.Context, orderID int64, orderNo string, userID int64, aid int64, actName string, playType string) {
	drawLogs, _ := s.readDB.ActivityDrawLogs.WithContext(ctx).Where(s.readDB.ActivityDrawLogs.OrderID.Eq(orderID)).Find()
	if len(drawLogs) == 0 {
		return
	}
	// 批量获取 reward 信息
	rewardIDs := make([]int64, 0, len(drawLogs))
	for _, lg := range drawLogs {
		rewardIDs = append(rewardIDs, lg.RewardID)
	}
	rewards, _ := s.readDB.ActivityRewardSettings.WithContext(ctx).Where(s.readDB.ActivityRewardSettings.ID.In(rewardIDs...)).Find()
	rewardMap := make(map[int64]*model.ActivityRewardSettings)
	productIDs := make([]int64, 0, len(rewards))
	for _, r := range rewards {
		rewardMap[r.ID] = r
		if r.ProductID > 0 {
			productIDs = append(productIDs, r.ProductID)
		}
	}
	// 批量获取商品信息，通过 ProductID 关联查询名称
	productMap := make(map[int64]*model.Products)
	if len(productIDs) > 0 {
		products, _ := s.readDB.Products.WithContext(ctx).Where(s.readDB.Products.ID.In(productIDs...)).Find()
		for _, p := range products {
			productMap[p.ID] = p
		}
	}
	// 构建奖品名称列表，使用商品名称
	var rewardNames []string
	for _, lg := range drawLogs {
		if rw, ok := rewardMap[lg.RewardID]; ok && rw != nil {
			name := ""
			// 使用商品名称
			if rw.ProductID > 0 {
				if p, ok := productMap[rw.ProductID]; ok && p != nil && p.Name != "" {
					name = p.Name
				}
			}
			if name != "" {
				rewardNames = append(rewardNames, name)
			}
		}
	}
	itemsDesc := actName + " " + orderNo + " 盲盒赏品: " + strings.Join(rewardNames, ", ")
	itemsDesc = utf8SafeTruncate(itemsDesc, 110) // 微信限制 128 字节，我们保守一点截断到 110
	tx, _ := s.readDB.PaymentTransactions.WithContext(ctx).Where(s.readDB.PaymentTransactions.OrderNo.Eq(orderNo)).First()
	if tx == nil || tx.TransactionID == "" {
		return
	}
	u, _ := s.readDB.Users.WithContext(ctx).Where(s.readDB.Users.ID.Eq(userID)).First()
	payerOpenid := ""
	if u != nil {
		payerOpenid = u.Openid
	}
	var cfg *wechat.WechatConfig
	if dc := sysconfig.GetDynamicConfig(); dc != nil {
		wc := dc.GetWechat(ctx)
		cfg = &wechat.WechatConfig{AppID: wc.AppID, AppSecret: wc.AppSecret}
	} else {
		c := configs.Get()
		cfg = &wechat.WechatConfig{AppID: c.Wechat.AppID, AppSecret: c.Wechat.AppSecret}
	}
	errUpload := wechat.UploadVirtualShippingForBackground(ctx, cfg, tx.TransactionID, orderNo, payerOpenid, itemsDesc)

	// 如果发货成功，或者微信提示已经发过货了(10060003)，则标记本地订单为已履约
	if errUpload == nil || strings.Contains(errUpload.Error(), "10060003") {
		_, _ = s.writeDB.Orders.WithContext(ctx).Where(s.readDB.Orders.ID.Eq(orderID)).Update(s.readDB.Orders.IsConsumed, 1)
		if errUpload != nil {
			s.logger.Info("[虚拟发货] 微信反馈已处理，更新本地标记", zap.String("order_no", orderNo))
		}
	} else if errUpload != nil {
		s.logger.Error("[虚拟发货] 上传失败", zap.Error(errUpload), zap.String("order_no", orderNo))
	}

	// 发送开奖通知 - 仅一番赏，使用动态配置（system_configs 表）
	if playType == "ichiban" {
		dc := sysconfig.GetDynamicConfig()
		if dc != nil {
			wxCfg := dc.GetWechat(ctx)
			notifyCfg := &notify.WechatNotifyConfig{
				AppID:                   wxCfg.AppID,
				AppSecret:               wxCfg.AppSecret,
				LotteryResultTemplateID: wxCfg.LotteryResultTemplateID,
			}
			if err := notify.SendLotteryResultNotification(ctx, notifyCfg, payerOpenid, actName, rewardNames, orderNo, time.Now()); err != nil {
				s.logger.Error("[虚拟发货] 发送开奖通知失败", zap.Error(err), zap.String("order_no", orderNo))
			}
		}
	}
}

func (s *service) applyItemCardEffect(ctx context.Context, icID int64, aid int64, iss int64, log *model.ActivityDrawLogs) {
	uic, _ := s.readDB.UserItemCards.WithContext(ctx).Where(s.readDB.UserItemCards.ID.Eq(icID), s.readDB.UserItemCards.UserID.Eq(log.UserID), s.readDB.UserItemCards.Status.Eq(1)).First()
	if uic == nil {
		return
	}
	ic, _ := s.readDB.SystemItemCards.WithContext(ctx).Where(s.readDB.SystemItemCards.ID.Eq(uic.CardID), s.readDB.SystemItemCards.Status.Eq(1)).First()
	now := time.Now()
	if ic != nil && !uic.ValidStart.After(now) && !uic.ValidEnd.Before(now) {
		scopeOK := (ic.ScopeType == 1) || (ic.ScopeType == 3 && ic.ActivityID == aid) || (ic.ScopeType == 4 && ic.IssueID == iss)
		if scopeOK {
			effectApplied := false
			if ic.EffectType == 1 && ic.RewardMultiplierX1000 >= 2000 {
				rw, _ := s.readDB.ActivityRewardSettings.WithContext(ctx).Where(s.readDB.ActivityRewardSettings.ID.Eq(log.RewardID)).First()
				if rw != nil {
					// 获取商品名称
					prodName := ""
					if prod, _ := s.readDB.Products.WithContext(ctx).Where(s.readDB.Products.ID.Eq(rw.ProductID)).First(); prod != nil {
						prodName = prod.Name
					}
					// 尝试发放额外奖励
					_, err := s.user.GrantRewardToOrder(ctx, log.UserID, usersvc.GrantRewardToOrderRequest{OrderID: log.OrderID, ProductID: rw.ProductID, Quantity: 1, ActivityID: &aid, RewardID: &log.RewardID, Remark: prodName + "(倍数)"})
					if err != nil {
						s.logger.Error("道具卡-Lottery: 双倍奖励发放失败", zap.Int64("order_id", log.OrderID), zap.Error(err))
					} else {
						effectApplied = true
						s.logger.Info("道具卡-Lottery: 应用双倍奖励成功", zap.Int64("order_id", log.OrderID))
					}
				}
			} else {
				// Other effects? If none implemented, do not void.
				s.logger.Warn("道具卡-Lottery: 未知效果类型或参数无效，不消耗卡片", zap.Int32("effect_type", ic.EffectType))
			}

			// Only void if effect was successfully applied
			if effectApplied {
				_, err := s.writeDB.UserItemCards.WithContext(ctx).Where(s.readDB.UserItemCards.ID.Eq(icID)).Updates(map[string]any{
					"status":           2,
					"used_draw_log_id": log.ID,
					"used_activity_id": aid,
					"used_issue_id":    iss,
					"used_at":          now,
				})
				if err != nil {
					s.logger.Error("道具卡-Lottery: 核销卡片失败", zap.Int64("icID", icID), zap.Error(err))
				}
			}
		} else {
			s.logger.Debug("道具卡-Lottery: 范围校验失败")
		}
	} else {
		s.logger.Debug("道具卡-Lottery: 卡片状态无效")
	}
}

func parseInt64(s string) int64 {
	var n int64
	for i := 0; i < len(s); i++ {
		if s[i] < '0' || s[i] > '9' {
			break
		}
		n = n*10 + int64(s[i]-'0')
	}
	return n
}

// utf8SafeTruncate 按字节长度截断，并过滤掉无效/损坏的 UTF-8 字符
func utf8SafeTruncate(s string, n int) string {
	r := []rune(s)
	var res []rune
	var byteLen int
	for _, val := range r {
		// 过滤掉无效字符标识 \ufffd，防止微信 API 报错
		if val == '\uFFFD' {
			continue
		}
		l := len(string(val))
		if byteLen+l > n {
			break
		}
		res = append(res, val)
		byteLen += l
	}
	return string(res)
}

// ClearIchibanPositionsByOrderID 清理订单对应的一番赏占位
func (s *service) ClearIchibanPositionsByOrderID(ctx context.Context, orderID int64) error {
	result, err := s.writeDB.IssuePositionClaims.WithContext(ctx).Where(
		s.writeDB.IssuePositionClaims.OrderID.Eq(orderID),
	).Delete()
	if err != nil {
		s.logger.Error("清理一番赏占位失败", zap.Int64("order_id", orderID), zap.Error(err))
		return err
	}
	s.logger.Info("清理一番赏占位成功", zap.Int64("order_id", orderID), zap.Int64("rows", result.RowsAffected))
	return nil
}
