// Package orderLogic provides order processing logic for handling various types of orders
// including subscription purchases, renewals, traffic resets, and balance recharges.
package orderLogic

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/perfect-panel/server/internal/logic/admin/group"
	commonLogic "github.com/perfect-panel/server/internal/logic/common"
	"github.com/perfect-panel/server/internal/model/log"
	"github.com/perfect-panel/server/pkg/constant"
	"github.com/perfect-panel/server/pkg/logger"

	"github.com/google/uuid"
	"github.com/hibiken/asynq"
	"github.com/perfect-panel/server/internal/model/order"
	"github.com/perfect-panel/server/internal/model/redemption"
	"github.com/perfect-panel/server/internal/model/subscribe"
	"github.com/perfect-panel/server/internal/model/user"
	"github.com/perfect-panel/server/internal/svc"
	"github.com/perfect-panel/server/internal/types"
	internaltypes "github.com/perfect-panel/server/internal/types"
	"github.com/perfect-panel/server/pkg/tool"
	"github.com/perfect-panel/server/pkg/uuidx"
	queueTypes "github.com/perfect-panel/server/queue/types"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

// Order type constants define the different types of orders that can be processed
const (
	OrderTypeSubscribe    = 1 // New subscription purchase
	OrderTypeRenewal      = 2 // Subscription renewal
	OrderTypeResetTraffic = 3 // Traffic quota reset
	OrderTypeRecharge     = 4 // Balance recharge
	OrderTypeRedemption   = 5 // Redemption code activation
)

// Order status constants define the lifecycle states of an order
const (
	OrderStatusPending  = 1 // Order created but not paid
	OrderStatusPaid     = 2 // Order paid and ready for processing
	OrderStatusClose    = 3 // Order closed/cancelled
	OrderStatusFailed   = 4 // Order processing failed
	OrderStatusClaimed  = 6 // Internal transient claim while a worker processes the order
	OrderStatusFinished = 5 // Order successfully completed
)

// Predefined error variables for common error conditions
var (
	ErrInvalidOrderStatus = fmt.Errorf("invalid order status")
	ErrInvalidOrderType   = fmt.Errorf("invalid order type")
)

// ActivateOrderLogic handles the activation and processing of paid orders
type ActivateOrderLogic struct {
	svc *svc.ServiceContext // Service context containing dependencies
}

// NewActivateOrderLogic creates a new instance of ActivateOrderLogic
func NewActivateOrderLogic(svc *svc.ServiceContext) *ActivateOrderLogic {
	return &ActivateOrderLogic{
		svc: svc,
	}
}

// ProcessTask is the main entry point for processing order activation tasks.
// It handles the complete workflow of activating a paid order including validation,
// processing based on order type, and finalization.
func (l *ActivateOrderLogic) ProcessTask(ctx context.Context, task *asynq.Task) error {
	commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "activation_task_received",
		"[SubscriptionFlow] activation task received",
		logger.Field("payload", string(task.Payload())),
	)

	payload, err := l.parsePayload(ctx, task.Payload())
	if err != nil {
		logger.WithContext(ctx).Error("[ActivateOrderLogic] 解析 payload 失败，跳过任务",
			logger.Field("error", err.Error()))
		return nil // payload 解析失败不重试，因为重试也会失败
	}

	commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "activation_order_lookup",
		"[SubscriptionFlow] activation task is loading order",
		logger.Field("order_no", payload.OrderNo),
		logger.Field("iap_expire_at", payload.IAPExpireAt),
	)

	orderInfo, err := l.claimAndGetOrder(ctx, payload.OrderNo)
	if err != nil {
		if errors.Is(err, ErrInvalidOrderStatus) {
			if strings.Contains(err.Error(), "stuck in claimed") {
				logger.WithContext(ctx).Error("[ActivateOrderLogic] 订单卡在 claimed，将重试",
					logger.Field("order_no", payload.OrderNo))
				return err // 返回错误触发 asynq 重试
			}
			logger.WithContext(ctx).Info("[ActivateOrderLogic] 订单状态不是已支付，跳过",
				logger.Field("order_no", payload.OrderNo))
			return nil
		}
		// 数据库查询失败，应该重试
		logger.WithContext(ctx).Error("[ActivateOrderLogic] 查询订单失败，将重试",
			logger.Field("order_no", payload.OrderNo),
			logger.Field("error", err.Error()))
		return err
	}

	// Idempotency: if order is already finished, skip processing
	if orderInfo == nil {
		return nil
	}

	commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "activation_order_claimed",
		"[SubscriptionFlow] activation worker claimed paid order",
		commonLogic.OrderTraceFields(orderInfo)...,
	)

	if err = l.processOrderByType(ctx, orderInfo, payload.IAPExpireAt); err != nil {
		if releaseErr := l.releaseClaim(ctx, orderInfo.OrderNo); releaseErr != nil {
			logger.WithContext(ctx).Error("[ActivateOrderLogic] releaseClaim also failed, stuck recovery will handle",
				logger.Field("order_no", orderInfo.OrderNo),
				logger.Field("release_error", releaseErr.Error()),
			)
		}
		logger.WithContext(ctx).Error("[ActivateOrderLogic] 处理订单失败，将重试",
			logger.Field("order_no", orderInfo.OrderNo),
			logger.Field("order_type", orderInfo.Type),
			logger.Field("error", err.Error()))
		return err // 返回 err 允许 asynq 重试
	}

	if err = l.reconcilePostOrderSubscriptions(ctx, orderInfo); err != nil {
		if releaseErr := l.releaseClaim(ctx, orderInfo.OrderNo); releaseErr != nil {
			logger.WithContext(ctx).Error("[ActivateOrderLogic] releaseClaim also failed, stuck recovery will handle",
				logger.Field("order_no", orderInfo.OrderNo),
				logger.Field("release_error", releaseErr.Error()),
			)
		}
		logger.WithContext(ctx).Error("[ActivateOrderLogic] 订单订阅兜底合并失败，将重试",
			logger.Field("order_no", orderInfo.OrderNo),
			logger.Field("order_type", orderInfo.Type),
			logger.Field("error", err.Error()))
		return err
	}

	l.finalizeCouponAndOrder(ctx, orderInfo)

	commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "activation_finished",
		"[SubscriptionFlow] order activation completed",
		commonLogic.OrderTraceFields(orderInfo)...,
	)
	return nil
}

// parsePayload unMarshals the task payload into a structured format
func (l *ActivateOrderLogic) parsePayload(ctx context.Context, payload []byte) (*queueTypes.ForthwithActivateOrderPayload, error) {
	var p queueTypes.ForthwithActivateOrderPayload
	if err := json.Unmarshal(payload, &p); err != nil {
		logger.WithContext(ctx).Error("[ActivateOrderLogic] Unmarshal payload failed",
			logger.Field("error", err.Error()),
			logger.Field("payload", string(payload)),
		)
		return nil, err
	}
	return &p, nil
}

// claimAndGetOrder retrieves an order by order number and atomically claims paid orders.
// Returns error if order is not found or not in paid status
func (l *ActivateOrderLogic) claimAndGetOrder(ctx context.Context, orderNo string) (*order.Order, error) {
	var orderInfo order.Order
	err := l.svc.DB.WithContext(ctx).Model(&order.Order{}).Where("order_no = ?", orderNo).First(&orderInfo).Error
	if err != nil {
		logger.WithContext(ctx).Error("Find order failed",
			logger.Field("error", err.Error()),
			logger.Field("order_no", orderNo),
		)
		return nil, err
	}

	// Idempotency check: if order is already finished, return success
	if orderInfo.Status == OrderStatusFinished {
		logger.WithContext(ctx).Info("Order already finished, skip processing",
			logger.Field("order_no", orderInfo.OrderNo),
		)
		return nil, nil
	}

	// Detect stuck claimed order — return retryable error so asynq re-tries
	if orderInfo.Status == OrderStatusClaimed {
		logger.WithContext(ctx).Error("Order stuck in claimed status",
			logger.Field("order_no", orderInfo.OrderNo),
			logger.Field("status", orderInfo.Status),
		)
		return nil, fmt.Errorf("order %s stuck in claimed status: %w", orderNo, ErrInvalidOrderStatus)
	}

	if orderInfo.Status != OrderStatusPaid {
		logger.WithContext(ctx).Error("Order status error",
			logger.Field("order_no", orderInfo.OrderNo),
			logger.Field("status", orderInfo.Status),
		)
		return nil, ErrInvalidOrderStatus
	}

	result := l.svc.DB.WithContext(ctx).
		Model(&order.Order{}).
		Where("order_no = ? AND status = ?", orderNo, OrderStatusPaid).
		Update("status", OrderStatusClaimed)
	if result.Error != nil {
		return nil, result.Error
	}
	if result.RowsAffected == 0 {
		logger.WithContext(ctx).Info("Order already claimed by another worker, skip processing",
			logger.Field("order_no", orderNo),
		)
		return nil, nil
	}
	orderInfo.Status = OrderStatusClaimed
	return &orderInfo, nil
}

func (l *ActivateOrderLogic) releaseClaim(ctx context.Context, orderNo string) error {
	if err := l.svc.DB.WithContext(ctx).
		Model(&order.Order{}).
		Where("order_no = ? AND status = ?", orderNo, OrderStatusClaimed).
		Update("status", OrderStatusPaid).Error; err != nil {
		logger.WithContext(ctx).Error("Release order claim failed",
			logger.Field("error", err.Error()),
			logger.Field("order_no", orderNo),
		)
		return fmt.Errorf("release claim failed for order %s: %w", orderNo, err)
	}
	return nil
}

// processOrderByType routes order processing based on the order type
func (l *ActivateOrderLogic) processOrderByType(ctx context.Context, orderInfo *order.Order, iapExpireAt int64) error {
	switch orderInfo.Type {
	case OrderTypeSubscribe:
		return l.NewPurchase(ctx, orderInfo)
	case OrderTypeRenewal:
		return l.Renewal(ctx, orderInfo, iapExpireAt)
	case OrderTypeResetTraffic:
		return l.ResetTraffic(ctx, orderInfo)
	case OrderTypeRecharge:
		return l.Recharge(ctx, orderInfo)
	case OrderTypeRedemption:
		return l.RedemptionActivate(ctx, orderInfo)
	default:
		logger.WithContext(ctx).Error("Order type is invalid", logger.Field("type", orderInfo.Type))
		return ErrInvalidOrderType
	}
}

func (l *ActivateOrderLogic) reconcilePostOrderSubscriptions(ctx context.Context, orderInfo *order.Order) error {
	if !shouldReconcilePostOrderSubscriptions(orderInfo) {
		return nil
	}

	effectiveUserID := orderInfo.UserId
	if orderInfo.SubscriptionUserId > 0 {
		effectiveUserID = orderInfo.SubscriptionUserId
	}
	if effectiveUserID == 0 || orderInfo.Id == 0 {
		return nil
	}

	var (
		survivor             user.Subscribe
		survivorBefore       user.Subscribe
		losers               []user.Subscribe
		mergedIDs            []int64
		subscribeIDsToClear  = make(map[int64]struct{})
		missingSurvivor      bool
		ownerMismatchSkipped bool
		identitySourceID     int64
	)

	err := l.svc.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Model(&user.Subscribe{}).
			Where("order_id = ?", orderInfo.Id).
			First(&survivor).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				missingSurvivor = true
				return nil
			}
			return err
		}
		survivorBefore = survivor

		var ownerSubs []user.Subscribe
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Model(&user.Subscribe{}).
			Where("user_id = ?", effectiveUserID).
			Order("id ASC").
			Find(&ownerSubs).Error; err != nil {
			return err
		}

		if survivor.UserId != effectiveUserID {
			if len(ownerSubs) == 0 {
				ownerMismatchSkipped = true
				return nil
			}

			if err := tx.Model(&user.Subscribe{}).
				Where("id = ?", survivor.Id).
				Update("user_id", effectiveUserID).Error; err != nil {
				return err
			}
			survivor.UserId = effectiveUserID

			if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
				Model(&user.Subscribe{}).
				Where("user_id = ?", effectiveUserID).
				Order("id ASC").
				Find(&ownerSubs).Error; err != nil {
				return err
			}
		}

		if len(ownerSubs) <= 1 {
			return nil
		}

		now := time.Now()
		accumulatedExpire := now
		for i := range ownerSubs {
			item := ownerSubs[i]
			if (item.Id == survivor.Id || orderMergeRemainingTimeStatus(item.Status)) && item.ExpireTime.After(now) {
				accumulatedExpire = accumulatedExpire.Add(item.ExpireTime.Sub(now))
			}

			if item.Id != survivor.Id {
				losers = append(losers, item)
				mergedIDs = append(mergedIDs, item.Id)
			}
			if item.SubscribeId > 0 {
				subscribeIDsToClear[item.SubscribeId] = struct{}{}
			}
		}

		if len(losers) == 0 {
			return nil
		}

		if survivor.SubscribeId > 0 {
			subscribeIDsToClear[survivor.SubscribeId] = struct{}{}
		}

		identitySource := pickSubscriptionIdentitySource(losers)
		if identitySource != nil {
			identitySourceID = identitySource.Id
		}

		updateFields := map[string]interface{}{
			"status":      1,
			"finished_at": nil,
		}
		if accumulatedExpire.After(survivor.ExpireTime) {
			survivor.ExpireTime = accumulatedExpire
			updateFields["expire_time"] = accumulatedExpire
		}
		if identitySource != nil {
			if identitySource.Token != "" {
				survivor.Token = identitySource.Token
				updateFields["token"] = identitySource.Token
			}
			if identitySource.UUID != "" {
				survivor.UUID = identitySource.UUID
				updateFields["uuid"] = identitySource.UUID
			}
		}

		loserIDs := make([]int64, 0, len(losers))
		for i := range losers {
			loserIDs = append(loserIDs, losers[i].Id)
		}
		if len(loserIDs) == 0 {
			return nil
		}

		// user_subscribe 当前没有 deleted_at 字段，这里沿用项目现有删除语义清理 loser 记录。
		if err := tx.Where("id IN ?", loserIDs).Delete(&user.Subscribe{}).Error; err != nil {
			return err
		}

		if err := tx.Model(&user.Subscribe{}).
			Where("id = ?", survivor.Id).
			Updates(updateFields).Error; err != nil {
			return err
		}
		survivor.Status = 1
		survivor.FinishedAt = nil

		return nil
	})
	if err != nil {
		return err
	}

	if missingSurvivor {
		commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "post_order_reconcile_skipped",
			"[SubscriptionFlow] post-order reconcile skipped because survivor subscription was not found",
			append(commonLogic.OrderTraceFields(orderInfo),
				logger.Field("reason", "post_order_reconcile"),
				logger.Field("effective_user_id", effectiveUserID),
			)...,
		)
		return nil
	}

	if ownerMismatchSkipped {
		commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "post_order_reconcile_skipped",
			"[SubscriptionFlow] post-order reconcile skipped because survivor owner mismatch had no duplicates",
			append(commonLogic.OrderTraceFields(orderInfo),
				logger.Field("reason", "post_order_reconcile"),
				logger.Field("effective_user_id", effectiveUserID),
				logger.Field("survivor_subscribe_id", survivor.Id),
				logger.Field("survivor_user_id", survivorBefore.UserId),
			)...,
		)
		return nil
	}

	if len(losers) == 0 {
		return nil
	}

	l.clearPostOrderReconcileCache(ctx, &survivorBefore, &survivor, losers, subscribeIDsToClear)

	commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "post_order_reconciled",
		"[SubscriptionFlow] post-order reconcile merged duplicate subscriptions",
		append(commonLogic.OrderTraceFields(orderInfo),
			logger.Field("reason", "post_order_reconcile"),
			logger.Field("effective_user_id", effectiveUserID),
			logger.Field("survivor_subscribe_id", survivor.Id),
			logger.Field("identity_source_subscribe_id", identitySourceID),
			logger.Field("merged_subscribe_ids", mergedIDs),
			logger.Field("merged_count", len(mergedIDs)),
		)...,
	)

	return nil
}

func shouldReconcilePostOrderSubscriptions(orderInfo *order.Order) bool {
	if orderInfo == nil {
		return false
	}

	switch orderInfo.Type {
	case OrderTypeSubscribe, OrderTypeRenewal, OrderTypeRedemption:
		return true
	default:
		return false
	}
}

func orderMergeRemainingTimeStatus(status uint8) bool {
	switch status {
	case 0, 1, 2:
		return true
	default:
		return false
	}
}

func subscriptionRenewalBaseTime(now time.Time, userSub *user.Subscribe) time.Time {
	if userSub != nil && orderMergeRemainingTimeStatus(userSub.Status) && userSub.ExpireTime.After(now) {
		return userSub.ExpireTime
	}
	return now
}

func pickSubscriptionIdentitySource(candidates []user.Subscribe) *user.Subscribe {
	if len(candidates) == 0 {
		return nil
	}

	best := &candidates[0]
	for i := 1; i < len(candidates); i++ {
		candidate := &candidates[i]
		if subscriptionIdentityPriority(candidate, best) {
			best = candidate
		}
	}
	return best
}

func subscriptionIdentityPriority(candidate *user.Subscribe, current *user.Subscribe) bool {
	if candidate == nil {
		return false
	}
	if current == nil {
		return true
	}

	candidateUsable := candidate.Token != "" || candidate.UUID != ""
	currentUsable := current.Token != "" || current.UUID != ""
	if candidateUsable != currentUsable {
		return candidateUsable
	}

	if candidate.ExpireTime.After(current.ExpireTime) {
		return true
	}
	if current.ExpireTime.After(candidate.ExpireTime) {
		return false
	}

	if candidate.UpdatedAt.After(current.UpdatedAt) {
		return true
	}
	if current.UpdatedAt.After(candidate.UpdatedAt) {
		return false
	}

	return candidate.Id > current.Id
}

func (l *ActivateOrderLogic) clearPostOrderReconcileCache(
	ctx context.Context,
	survivorBefore *user.Subscribe,
	survivorAfter *user.Subscribe,
	losers []user.Subscribe,
	subscribeIDs map[int64]struct{},
) {
	cacheModels := make([]*user.Subscribe, 0, len(losers)+2)
	if survivorBefore != nil {
		cacheModels = append(cacheModels, survivorBefore)
	}
	if survivorAfter != nil {
		cacheModels = append(cacheModels, survivorAfter)
	}
	for i := range losers {
		loser := losers[i]
		cacheModels = append(cacheModels, &loser)
	}

	if len(cacheModels) > 0 {
		if err := l.svc.UserModel.ClearSubscribeCache(ctx, cacheModels...); err != nil {
			logger.WithContext(ctx).Error("Post-order reconcile clear subscribe cache failed",
				logger.Field("reason", "post_order_reconcile"),
				logger.Field("error", err.Error()),
			)
		}
	}

	if l.svc.SubscribeModel != nil {
		for subscribeID := range subscribeIDs {
			if err := l.svc.SubscribeModel.ClearCache(ctx, subscribeID); err != nil {
				logger.WithContext(ctx).Error("Post-order reconcile clear plan cache failed",
					logger.Field("reason", "post_order_reconcile"),
					logger.Field("subscribe_id", subscribeID),
					logger.Field("error", err.Error()),
				)
			}
		}
	}

	if l.svc.NodeModel != nil {
		if err := l.svc.NodeModel.ClearServerAllCache(ctx); err != nil {
			logger.WithContext(ctx).Error("Post-order reconcile clear node cache failed",
				logger.Field("reason", "post_order_reconcile"),
				logger.Field("error", err.Error()),
			)
		}
	}
}

// finalizeCouponAndOrder handles post-processing tasks including coupon updates
// and order status finalization
func (l *ActivateOrderLogic) finalizeCouponAndOrder(ctx context.Context, orderInfo *order.Order) {
	// Update coupon if exists (non-critical, logged but not blocking)
	if orderInfo.Coupon != "" {
		if err := l.svc.CouponModel.UpdateCount(ctx, orderInfo.Coupon); err != nil {
			logger.WithContext(ctx).Error("Update coupon status failed",
				logger.Field("error", err.Error()),
				logger.Field("coupon", orderInfo.Coupon),
			)
		}
	}

	// UpdateOrderStatus uses WHERE status < target, which blocks claimed(6)→finished(5).
	// Use a direct update matching the exact claimed status, then update the full record
	// via the model layer to properly invalidate the cache.
	result := l.svc.DB.WithContext(ctx).
		Model(&order.Order{}).
		Where("order_no = ? AND status = ?", orderInfo.OrderNo, OrderStatusClaimed).
		Update("status", OrderStatusFinished)
	if result.Error != nil {
		logger.WithContext(ctx).Error("Update order status from claimed to finished failed",
			logger.Field("error", result.Error.Error()),
			logger.Field("order_no", orderInfo.OrderNo),
		)
	}
	// Invalidate order cache regardless of whether the DB update succeeded
	orderInfo.Status = OrderStatusFinished
	if err := l.svc.OrderModel.Update(ctx, orderInfo); err != nil {
		logger.WithContext(ctx).Error("Update order cache after finalization failed",
			logger.Field("error", err.Error()),
			logger.Field("order_no", orderInfo.OrderNo),
		)
	}
	commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "order_status_finished",
		"[SubscriptionFlow] order status updated to finished",
		commonLogic.OrderTraceFields(orderInfo)...,
	)
}

// NewPurchase handles new subscription purchase including user creation,
// subscription setup, commission processing, cache updates, and notifications
func (l *ActivateOrderLogic) NewPurchase(ctx context.Context, orderInfo *order.Order) error {
	userInfo, err := l.getUserOrCreate(ctx, orderInfo)
	if err != nil {
		return err
	}

	commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "activation_user_resolved",
		"[SubscriptionFlow] activation resolved subscription recipient user",
		append(commonLogic.OrderTraceFields(orderInfo),
			logger.Field("resolved_user_id", userInfo.Id),
		)...,
	)

	sub, err := l.getSubscribeInfo(ctx, orderInfo.SubscribeId)
	if err != nil {
		return err
	}

	if err = validateNewUserOnlyEligibilityAtActivation(ctx, l.svc.DB, l.svc.Redis, orderInfo, sub); err != nil {
		return err
	}

	var userSub *user.Subscribe

	// 单订阅模式下，优先兜底为”续费语义”：延长已购订阅，避免并发下重复创建 user_subscribe
	if l.svc.Config.Subscribe.SingleModel {
		// 家庭组场景：订阅实际属于 SubscriptionUserId（成员/主），而非付款人 UserId
		singleModeUserId := orderInfo.UserId
		if orderInfo.SubscriptionUserId > 0 {
			singleModeUserId = orderInfo.SubscriptionUserId
		}
		anchorSub, anchorErr := l.svc.UserModel.FindSingleModeAnchorSubscribe(ctx, singleModeUserId)
		switch {
		case anchorErr == nil && anchorSub != nil:
			if orderInfo.ParentId == 0 && anchorSub.OrderId > 0 && anchorSub.OrderId != orderInfo.Id {
				if patchErr := l.patchOrderParentID(ctx, orderInfo.Id, anchorSub.OrderId); patchErr != nil {
					logger.WithContext(ctx).Error("Patch order parent_id failed",
						logger.Field("error", patchErr.Error()),
						logger.Field("order_no", orderInfo.OrderNo),
					)
				} else {
					orderInfo.ParentId = anchorSub.OrderId
				}
			}

			if renewErr := l.updateSubscriptionForRenewal(ctx, anchorSub, sub, orderInfo); renewErr != nil {
				logger.WithContext(ctx).Error("Single mode renewal fallback failed",
					logger.Field("error", renewErr.Error()),
					logger.Field("anchor_user_subscribe_id", anchorSub.Id),
					logger.Field("order_no", orderInfo.OrderNo),
				)
			} else {
				userSub = anchorSub
				commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "subscription_reused",
					"[SubscriptionFlow] activation reused single-mode anchor subscription",
					append(commonLogic.OrderTraceFields(orderInfo),
						append(commonLogic.UserSubscribeTraceFields(anchorSub),
							logger.Field("reuse_reason", "single_mode_purchase_to_renewal"),
							logger.Field("plan_changed", anchorSub.SubscribeId != orderInfo.SubscribeId),
						)...,
					)...,
				)
			}
		case errors.Is(anchorErr, gorm.ErrRecordNotFound):
		case anchorErr != nil:
			logger.WithContext(ctx).Error("Find single mode anchor subscribe failed",
				logger.Field("error", anchorErr.Error()),
				logger.Field("order_no", orderInfo.OrderNo),
			)
		}

	}

	// 如果没有合并已购订阅，再尝试合并赠送订阅（order_id=0）。
	// 全局单订阅口径下，非 SingleModel 也不能让试用订阅和付费订阅并存。
	if userSub == nil {
		effectiveOwner := orderInfo.UserId
		if orderInfo.SubscriptionUserId > 0 {
			effectiveOwner = orderInfo.SubscriptionUserId
		}
		giftSub, giftErr := l.findGiftSubscription(ctx, effectiveOwner, orderInfo.SubscribeId)
		if giftErr == nil && giftSub != nil {
			userSub, err = l.extendGiftSubscription(ctx, giftSub, orderInfo, sub)
			if err != nil {
				logger.WithContext(ctx).Error("Extend gift subscription failed",
					logger.Field("error", err.Error()),
					logger.Field("gift_subscribe_id", giftSub.Id),
				)
				userSub = nil
			}
		}
	}

	// 兜底：创建新订阅前，查找用户是否已有同套餐的订阅记录（含过期/赠送），
	// 有则复用旧记录续期，避免出现重复订阅。
	// 需要同时检查 UserId 和 SubscriptionUserId，因为家庭组绑定前后 owner 可能不同。
	// 使用 SELECT ... FOR UPDATE 防止并发下多个 worker 同时选中同一订阅并创建重复记录。
	if userSub == nil {
		candidateUserIds := []int64{orderInfo.UserId}
		if orderInfo.SubscriptionUserId > 0 && orderInfo.SubscriptionUserId != orderInfo.UserId {
			candidateUserIds = append(candidateUserIds, orderInfo.SubscriptionUserId)
		}
		var existingSub user.Subscribe
		findErr := l.svc.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
			return tx.Clauses(clause.Locking{Strength: "UPDATE"}).
				Model(&user.Subscribe{}).
				Where("user_id IN ? AND token != ''", candidateUserIds).
				Order("expire_time DESC").
				Order("updated_at DESC").
				Order("id DESC").
				First(&existingSub).Error
		})
		if findErr == nil {
			// 家庭组场景：订阅 owner 可能变更（如成员注册的试用 → 被家主收归），
			// 续期前把 user_id 校正为当前订单的 SubscriptionUserId
			effectiveOwner := orderInfo.UserId
			if orderInfo.SubscriptionUserId > 0 {
				effectiveOwner = orderInfo.SubscriptionUserId
			}
			if existingSub.UserId != effectiveOwner {
				existingSub.UserId = effectiveOwner
			}
			// 找到已有记录，走续期逻辑
			if renewErr := l.updateSubscriptionForRenewal(ctx, &existingSub, sub, orderInfo); renewErr != nil {
				logger.WithContext(ctx).Error("Fallback renew existing subscription failed, will create new",
					logger.Field("error", renewErr.Error()),
					logger.Field("existing_subscribe_id", existingSub.Id),
					logger.Field("order_no", orderInfo.OrderNo),
				)
			} else {
				userSub = &existingSub
				commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "subscription_reused",
					"[SubscriptionFlow] activation renewed an existing subscription instead of creating a duplicate",
					append(commonLogic.OrderTraceFields(orderInfo),
						append(commonLogic.UserSubscribeTraceFields(&existingSub),
							logger.Field("reuse_reason", "fallback_existing_subscription"),
							logger.Field("candidate_user_ids", candidateUserIds),
							logger.Field("owner_corrected_to", effectiveOwner),
						)...,
					)...,
				)
			}
		}
	}

	// 如果仍然没有可复用的订阅，才创建新订阅
	if userSub == nil {
		userSub, err = l.createUserSubscription(ctx, orderInfo, sub)
		if err != nil {
			return err
		}
	}

	// Trigger user group recalculation (runs in background)
	l.triggerUserGroupRecalculation(ctx, userInfo.Id)

	// Handle commission in separate goroutine to avoid blocking
	go l.handleCommission(context.Background(), userInfo, orderInfo)

	// Clear cache
	l.clearServerCache(ctx, sub)

	commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "subscription_issued",
		"[SubscriptionFlow] activation finished issuing subscription entitlement",
		append(commonLogic.OrderTraceFields(orderInfo),
			commonLogic.UserSubscribeTraceFields(userSub)...,
		)...,
	)
	return nil
}

// getUserOrCreate retrieves an existing user or creates a new guest user based on order details
func (l *ActivateOrderLogic) getUserOrCreate(ctx context.Context, orderInfo *order.Order) (*user.User, error) {
	if orderInfo.UserId != 0 {
		return l.getExistingUser(ctx, orderInfo.UserId)
	}
	return l.createGuestUser(ctx, orderInfo)
}

// getExistingUser retrieves user information by user ID
func (l *ActivateOrderLogic) getExistingUser(ctx context.Context, userId int64) (*user.User, error) {
	userInfo, err := l.svc.UserModel.FindOne(ctx, userId)
	if err != nil {
		logger.WithContext(ctx).Error("Find user failed",
			logger.Field("error", err.Error()),
			logger.Field("user_id", userId),
		)
		return nil, err
	}
	return userInfo, nil
}

// createGuestUser creates a new user account for guest orders using temporary order information
// stored in Redis cache
func (l *ActivateOrderLogic) createGuestUser(ctx context.Context, orderInfo *order.Order) (*user.User, error) {
	tempOrder, err := l.getTempOrderInfo(ctx, orderInfo.OrderNo)
	if err != nil {
		return nil, err
	}

	userInfo := &user.User{
		Password: tool.EncodePassWord(tempOrder.Password),
		Algo:     "default",
		AuthMethods: []user.AuthMethods{
			{
				AuthType:       tempOrder.AuthType,
				AuthIdentifier: tempOrder.Identifier,
			},
		},
	}

	err = l.svc.UserModel.Transaction(ctx, func(tx *gorm.DB) error {
		if err := tx.Save(userInfo).Error; err != nil {
			return err
		}

		userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id)
		if err := tx.Model(&user.User{}).Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error; err != nil {
			return err
		}

		orderInfo.UserId = userInfo.Id
		return tx.Model(&order.Order{}).Where("order_no = ?", orderInfo.OrderNo).Update("user_id", userInfo.Id).Error
	})

	if err != nil {
		logger.WithContext(ctx).Error("Create user failed", logger.Field("error", err.Error()))
		return nil, err
	}

	// Handle referrer relationship
	l.handleReferrer(ctx, userInfo, tempOrder.InviteCode)

	logger.WithContext(ctx).Info("Create guest user success",
		logger.Field("user_id", userInfo.Id),
		logger.Field("identifier", tempOrder.Identifier),
		logger.Field("auth_type", tempOrder.AuthType),
	)
	commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "guest_user_created",
		"[SubscriptionFlow] guest user created during order activation",
		append(commonLogic.OrderTraceFields(orderInfo),
			logger.Field("created_user_id", userInfo.Id),
			logger.Field("identifier", tempOrder.Identifier),
			logger.Field("auth_type", tempOrder.AuthType),
		)...,
	)

	return userInfo, nil
}

// getTempOrderInfo retrieves temporary order information from Redis cache with DB fallback.
func (l *ActivateOrderLogic) getTempOrderInfo(ctx context.Context, orderNo string) (*constant.TemporaryOrderInfo, error) {
	cacheKey := fmt.Sprintf(constant.TempOrderCacheKey, orderNo)
	data, err := l.svc.Redis.Get(ctx, cacheKey).Result()
	if err == nil {
		var tempOrder constant.TemporaryOrderInfo
		if unmarshalErr := tempOrder.Unmarshal([]byte(data)); unmarshalErr != nil {
			logger.WithContext(ctx).Error("Unmarshal temp order cache failed",
				logger.Field("error", unmarshalErr.Error()),
				logger.Field("cache_key", cacheKey),
				logger.Field("data", data),
			)
			return nil, unmarshalErr
		}
		return &tempOrder, nil
	}

	// Redis miss — fall back to DB activation_context field.
	logger.WithContext(ctx).Infow("Redis cache miss for temp order, falling back to DB",
		logger.Field("cache_key", cacheKey),
		logger.Field("error", err.Error()),
	)

	orderInfo, dbErr := l.svc.OrderModel.FindOneByOrderNo(ctx, orderNo)
	if dbErr != nil {
		logger.WithContext(ctx).Error("DB fallback for temp order failed",
			logger.Field("order_no", orderNo),
			logger.Field("error", dbErr.Error()),
		)
		return nil, dbErr
	}

	if orderInfo.ActivationContext == "" {
		logger.WithContext(ctx).Error("CRITICAL: activation_context missing in DB and Redis expired; cannot recover guest order",
			logger.Field("order_no", orderNo),
		)
		return nil, errors.New("activation context not found: Redis expired and no DB fallback available")
	}

	var tempOrder constant.TemporaryOrderInfo
	if unmarshalErr := tempOrder.Unmarshal([]byte(orderInfo.ActivationContext)); unmarshalErr != nil {
		logger.WithContext(ctx).Error("Unmarshal DB activation_context failed",
			logger.Field("order_no", orderNo),
			logger.Field("error", unmarshalErr.Error()),
		)
		return nil, unmarshalErr
	}

	return &tempOrder, nil
}

// handleReferrer establishes referrer relationship if an invite code is provided
func (l *ActivateOrderLogic) handleReferrer(ctx context.Context, userInfo *user.User, inviteCode string) {
	if inviteCode == "" {
		return
	}

	referer, err := l.svc.UserModel.FindOneByReferCode(ctx, inviteCode)
	if err != nil {
		logger.WithContext(ctx).Error("Find referer failed",
			logger.Field("error", err.Error()),
			logger.Field("refer_code", inviteCode),
		)
		return
	}

	userInfo.RefererId = referer.Id
	if err = l.svc.UserModel.Update(ctx, userInfo); err != nil {
		logger.WithContext(ctx).Error("Update user referer failed",
			logger.Field("error", err.Error()),
			logger.Field("user_id", userInfo.Id),
		)
	}
}

// getSubscribeInfo retrieves subscription plan details by subscription ID
func (l *ActivateOrderLogic) getSubscribeInfo(ctx context.Context, subscribeId int64) (*subscribe.Subscribe, error) {
	sub, err := l.svc.SubscribeModel.FindOne(ctx, subscribeId)
	if err != nil {
		logger.WithContext(ctx).Error("Find subscribe failed",
			logger.Field("error", err.Error()),
			logger.Field("subscribe_id", subscribeId),
		)
		return nil, err
	}
	return sub, nil
}

// createUserSubscription creates a new user subscription record based on order and subscription plan details
func (l *ActivateOrderLogic) createUserSubscription(ctx context.Context, orderInfo *order.Order, sub *subscribe.Subscribe) (*user.Subscribe, error) {
	now := time.Now()

	// Determine subscription owner: use SubscriptionUserId if set, otherwise fall back to UserId
	subscriptionUserId := orderInfo.UserId
	if orderInfo.SubscriptionUserId > 0 {
		subscriptionUserId = orderInfo.SubscriptionUserId
	}

	userSub := &user.Subscribe{
		UserId:      subscriptionUserId,
		OrderId:     orderInfo.Id,
		SubscribeId: orderInfo.SubscribeId,
		StartTime:   now,
		ExpireTime:  tool.AddTime(sub.UnitTime, orderInfo.Quantity, now),
		Traffic:     sub.Traffic,
		Download:    0,
		Upload:      0,
		Token:       uuidx.SubscribeToken(orderInfo.OrderNo),
		UUID:        uuid.New().String(),
		Status:      1,
	}

	// Check quota limit before creating subscription (final safeguard)
	if sub.Quota > 0 {
		var count int64
		if err := l.svc.DB.Model(&user.Subscribe{}).Where("user_id = ?", subscriptionUserId).Count(&count).Error; err != nil {
			logger.WithContext(ctx).Error("Count user subscribe failed", logger.Field("error", err.Error()))
			return nil, err
		}
		if count >= sub.Quota {
			logger.WithContext(ctx).Infow("Subscribe quota limit exceeded",
				logger.Field("user_id", orderInfo.UserId),
				logger.Field("subscribe_id", orderInfo.SubscribeId),
				logger.Field("quota", sub.Quota),
				logger.Field("current_count", count),
			)
			return nil, fmt.Errorf("subscribe quota limit exceeded")
		}
	}

	if err := l.svc.UserModel.InsertSubscribe(ctx, userSub); err != nil {
		logger.WithContext(ctx).Error("Insert user subscribe failed", logger.Field("error", err.Error()))
		return nil, err
	}

	commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "subscription_created",
		"[SubscriptionFlow] new user subscription record created",
		append(commonLogic.OrderTraceFields(orderInfo),
			commonLogic.UserSubscribeTraceFields(userSub)...,
		)...,
	)

	return userSub, nil
}

func (l *ActivateOrderLogic) patchOrderParentID(ctx context.Context, orderID int64, parentID int64) error {
	return l.svc.DB.WithContext(ctx).Model(&order.Order{}).Where("id = ? AND (parent_id = 0 OR parent_id IS NULL)", orderID).Update("parent_id", parentID).Error
}

// findGiftSubscription 查找用户的赠送订阅（order_id=0），包括已过期的。
// 单订阅模式下，用户若以不同套餐首次购买，需要将赠送订阅合并为付费订阅，
// 因此不再过滤 subscribe_id，避免套餐不同时绕过合并路径创建重复订阅。
func (l *ActivateOrderLogic) findGiftSubscription(ctx context.Context, userId int64, _ int64) (*user.Subscribe, error) {
	var giftSub user.Subscribe
	err := l.svc.DB.Model(&user.Subscribe{}).
		Where("user_id = ? AND order_id = 0", userId).
		Order("created_at DESC").
		First(&giftSub).Error
	if err != nil {
		return nil, err
	}
	return &giftSub, nil
}

// extendGiftSubscription 在现有赠送订阅上延长到期时间，保持 token/UUID 不变。
// 若购买套餐与赠送套餐不同，同步更新套餐 ID 和流量配额并重置已用量（套餐变更语义）。
func (l *ActivateOrderLogic) extendGiftSubscription(ctx context.Context, giftSub *user.Subscribe, orderInfo *order.Order, sub *subscribe.Subscribe) (*user.Subscribe, error) {
	now := time.Now()
	baseTime := subscriptionRenewalBaseTime(now, giftSub)
	newExpireTime := tool.AddTime(sub.UnitTime, orderInfo.Quantity, baseTime)

	giftSub.OrderId = orderInfo.Id
	giftSub.ExpireTime = newExpireTime
	giftSub.Status = 1

	// 套餐变更：更新套餐 ID 和流量配额，重置已用流量（与 updateSubscriptionForRenewal 逻辑一致）
	if giftSub.SubscribeId != orderInfo.SubscribeId {
		giftSub.SubscribeId = orderInfo.SubscribeId
		giftSub.Traffic = sub.Traffic
		giftSub.Download = 0
		giftSub.Upload = 0
	}

	if err := l.svc.UserModel.UpdateSubscribe(ctx, giftSub); err != nil {
		logger.WithContext(ctx).Error("Update gift subscription failed",
			logger.Field("error", err.Error()),
			logger.Field("subscribe_id", giftSub.Id),
		)
		return nil, err
	}

	commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "subscription_reused",
		"[SubscriptionFlow] paid order extended an existing gift subscription",
		append(commonLogic.OrderTraceFields(orderInfo),
			append(commonLogic.UserSubscribeTraceFields(giftSub),
				logger.Field("reuse_reason", "gift_subscription_promoted"),
				logger.Field("old_expire_time", baseTime),
				logger.Field("new_expire_time", newExpireTime),
			)...,
		)...,
	)

	return giftSub, nil
}

// handleCommission processes referral commission for the referrer if applicable.
// This runs asynchronously to avoid blocking the main order processing flow.
func (l *ActivateOrderLogic) handleCommission(ctx context.Context, userInfo *user.User, orderInfo *order.Order) {
	if !l.shouldProcessCommission(userInfo, orderInfo.IsNew) {
		// 普通用户路径（佣金比例=0）：只有首单才双方赠N天
		if orderInfo.IsNew {
			l.grantGiftDaysToBothParties(ctx, userInfo, orderInfo)
		}
		return
	}

	referer, err := l.svc.UserModel.FindOne(ctx, userInfo.RefererId)
	if err != nil {
		logger.WithContext(ctx).Error("Find referer failed",
			logger.Field("error", err.Error()),
			logger.Field("referer_id", userInfo.RefererId),
		)
		return
	}

	var referralPercentage uint8
	if referer.ReferralPercentage != 0 {
		referralPercentage = referer.ReferralPercentage
	} else {
		referralPercentage = uint8(l.svc.Config.Invite.ReferralPercentage)
	}

	// Order commission calculation： (Order Amount - Order Fee) * Referral Percentage
	amount := l.calculateCommission(orderInfo.Amount-orderInfo.FeeAmount, referralPercentage)

	// Use transaction for commission updates
	err = l.svc.DB.Transaction(func(tx *gorm.DB) error {
		// Idempotency: check if commission log already exists for this order
		var existingLogCount int64
		if e := tx.Model(&log.SystemLog{}).
			Where("type = ? AND object_id = ? AND content LIKE ?",
				log.TypeCommission.Uint8(), referer.Id, fmt.Sprintf("%%\"%s\"%%", orderInfo.OrderNo)).
			Count(&existingLogCount).Error; e != nil {
			return e
		}
		if existingLogCount > 0 {
			logger.WithContext(ctx).Info("Commission already processed, skip",
				logger.Field("order_no", orderInfo.OrderNo),
				logger.Field("referer_id", referer.Id),
			)
			return nil
		}

		// Atomic increment to prevent lost updates under concurrency
		if e := tx.Model(&user.User{}).Where("id = ?", referer.Id).
			UpdateColumn("commission", gorm.Expr("commission + ?", amount)).Error; e != nil {
			return e
		}
		referer.Commission += amount

		var commissionType uint16
		switch orderInfo.Type {
		case OrderTypeSubscribe:
			commissionType = log.CommissionTypePurchase
		case OrderTypeRenewal:
			commissionType = log.CommissionTypeRenewal
		default:
			commissionType = log.CommissionTypePurchase
		}

		commissionLog := &log.Commission{
			Type:      commissionType,
			Amount:    amount,
			OrderNo:   orderInfo.OrderNo,
			Timestamp: orderInfo.CreatedAt.UnixMilli(),
		}

		content, _ := commissionLog.Marshal()
		return tx.Model(&log.SystemLog{}).Create(&log.SystemLog{
			Type:     log.TypeCommission.Uint8(),
			Date:     time.Now().Format("2006-01-02"),
			ObjectID: referer.Id,
			Content:  string(content),
		}).Error
	})

	if err != nil {
		logger.WithContext(ctx).Error("Update referer commission failed", logger.Field("error", err.Error()))
		return
	}

	// Update cache
	if err = l.svc.UserModel.UpdateUserCache(ctx, referer); err != nil {
		logger.WithContext(ctx).Error("Update referer cache failed",
			logger.Field("error", err.Error()),
			logger.Field("user_id", referer.Id),
		)
	}

	// 有佣金路径：邀请人拿佣金，被邀请用户（首单）拿天数
	if orderInfo.IsNew {
		giftTarget := l.resolveGiftTargetUser(ctx, userInfo, orderInfo.SubscriptionUserId)
		if giftErr := l.grantGiftDays(ctx, giftTarget, int(l.svc.Config.Invite.GiftDays), orderInfo.OrderNo, "邀请赠送"); giftErr != nil {
			l.logGiftDaysError(ctx, giftErr, giftTarget, orderInfo, "commission_path_referee")
		}
	}
}

func (l *ActivateOrderLogic) grantGiftDaysToBothParties(ctx context.Context, referee *user.User, orderInfo *order.Order) {
	giftDays := l.svc.Config.Invite.GiftDays
	if giftDays <= 0 || referee == nil || referee.Id == 0 || referee.RefererId == 0 || orderInfo == nil {
		return
	}
	refereeTarget := l.resolveGiftTargetUser(ctx, referee, orderInfo.SubscriptionUserId)
	if err := l.grantGiftDays(ctx, refereeTarget, int(giftDays), orderInfo.OrderNo, "邀请赠送"); err != nil {
		l.logGiftDaysError(ctx, err, refereeTarget, orderInfo, "no_commission_referee")
	}
	if referee.RefererId == 0 {
		return
	}
	referer, err := l.svc.UserModel.FindOne(ctx, referee.RefererId)
	if err != nil || referer == nil {
		return
	}
	refererTarget := l.resolveGiftTargetUser(ctx, referer, 0)
	if err = l.grantGiftDays(ctx, refererTarget, int(giftDays), orderInfo.OrderNo, "邀请赠送"); err != nil {
		l.logGiftDaysError(ctx, err, refererTarget, orderInfo, "no_commission_referer")
	}
}

func (l *ActivateOrderLogic) resolveGiftTargetUser(ctx context.Context, source *user.User, forcedOwnerID int64) *user.User {
	if source == nil || source.Id == 0 {
		return source
	}
	targetID := source.Id
	if forcedOwnerID > 0 {
		targetID = forcedOwnerID
	} else if entitlement, err := commonLogic.ResolveEntitlementUser(ctx, l.svc.DB, source.Id); err == nil && entitlement != nil && entitlement.EffectiveUserID > 0 {
		targetID = entitlement.EffectiveUserID
	}
	if targetID == source.Id {
		return source
	}
	target, err := l.svc.UserModel.FindOne(ctx, targetID)
	if err != nil || target == nil {
		logger.WithContext(ctx).Error("Resolve gift target owner failed",
			logger.Field("source_user_id", source.Id),
			logger.Field("target_user_id", targetID),
		)
		return source
	}
	return target
}

func (l *ActivateOrderLogic) logGiftDaysError(ctx context.Context, err error, target *user.User, orderInfo *order.Order, stage string) {
	if err == nil {
		return
	}
	var targetID int64
	if target != nil {
		targetID = target.Id
	}
	var orderNo string
	if orderInfo != nil {
		orderNo = orderInfo.OrderNo
	}
	logger.WithContext(ctx).Error("Grant invite gift days failed",
		logger.Field("error", err.Error()),
		logger.Field("stage", stage),
		logger.Field("target_user_id", targetID),
		logger.Field("order_no", orderNo),
	)
}

func (l *ActivateOrderLogic) grantGiftDays(ctx context.Context, u *user.User, days int, orderNo string, remark string) error {
	if u == nil || days <= 0 {
		return nil
	}

	// Idempotency: check if gift log already exists for this order + user
	var existingLogCount int64
	if e := l.svc.DB.Model(&log.SystemLog{}).
		Where("type = ? AND object_id = ? AND content LIKE ?",
			log.TypeGift.Uint8(), u.Id, fmt.Sprintf("%%\"%s\"%%", orderNo)).
		Count(&existingLogCount).Error; e != nil {
		return e
	}
	if existingLogCount > 0 {
		logger.WithContext(ctx).Info("Gift days already granted, skip",
			logger.Field("order_no", orderNo),
			logger.Field("user_id", u.Id),
		)
		return nil
	}

	activeSubscribe, err := l.findGiftDaysSubscription(ctx, u.Id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			giftLog := &log.Gift{
				Type:        log.GiftTypeIncrease,
				OrderNo:     orderNo,
				SubscribeId: 0,
				Amount:      int64(days),
				Balance:     u.Balance,
				Remark:      remark + " skipped: no active subscription",
				Timestamp:   time.Now().UnixMilli(),
			}
			content, _ := giftLog.Marshal()
			return l.svc.LogModel.Insert(ctx, &log.SystemLog{
				Type:     log.TypeGift.Uint8(),
				Date:     time.Now().Format("2006-01-02"),
				ObjectID: u.Id,
				Content:  string(content),
			})
		}
		return err
	}
	now := time.Now()
	if !activeSubscribe.ExpireTime.Equal(time.UnixMilli(0)) {
		if activeSubscribe.ExpireTime.Before(now) {
			activeSubscribe.ExpireTime = now.Add(time.Duration(days) * 24 * time.Hour)
		} else {
			activeSubscribe.ExpireTime = activeSubscribe.ExpireTime.Add(time.Duration(days) * 24 * time.Hour)
		}
	}
	activeSubscribe.Status = 1
	activeSubscribe.FinishedAt = nil
	activeSubscribe.ExpiredDownload = 0
	activeSubscribe.ExpiredUpload = 0
	err = l.svc.UserModel.UpdateSubscribe(ctx, activeSubscribe)
	if err != nil {
		return err
	}

	// Insert system log
	giftLog := &log.Gift{
		Type:        log.GiftTypeIncrease,
		OrderNo:     orderNo,
		SubscribeId: activeSubscribe.Id,
		Amount:      int64(days),
		Balance:     u.Balance,
		Remark:      remark,
		Timestamp:   time.Now().UnixMilli(),
	}
	content, _ := giftLog.Marshal()
	return l.svc.LogModel.Insert(ctx, &log.SystemLog{
		Type:     log.TypeGift.Uint8(),
		Date:     time.Now().Format("2006-01-02"),
		ObjectID: u.Id,
		Content:  string(content),
	})
}

func (l *ActivateOrderLogic) findGiftDaysSubscription(ctx context.Context, userID int64) (*user.Subscribe, error) {
	activeSubscribe, err := l.svc.UserModel.FindActiveSubscribe(ctx, userID)
	if err == nil {
		return activeSubscribe, nil
	}
	if !errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, err
	}

	var fallback user.Subscribe
	err = l.svc.DB.WithContext(ctx).
		Model(&user.Subscribe{}).
		Where("user_id = ? AND token != ''", userID).
		Where("status IN ?", []int64{0, 1, 2, 3}).
		Order("expire_time DESC").
		Order("updated_at DESC").
		Order("id DESC").
		First(&fallback).Error
	if err != nil {
		return nil, err
	}
	return &fallback, nil
}

// shouldProcessCommission determines if commission should be processed based on
// referrer existence, commission settings, and order type
func (l *ActivateOrderLogic) shouldProcessCommission(userInfo *user.User, isFirstPurchase bool) bool {
	if userInfo == nil || userInfo.RefererId == 0 {
		return false
	}

	referer, err := l.svc.UserModel.FindOne(context.Background(), userInfo.RefererId)
	if err != nil {
		logger.Errorw("Find referer failed",
			logger.Field("error", err.Error()),
			logger.Field("referer_id", userInfo.RefererId))
		return false
	}
	if referer == nil {
		return false
	}

	// use referer's custom settings if set
	if referer.ReferralPercentage > 0 {
		if referer.OnlyFirstPurchase != nil && *referer.OnlyFirstPurchase && !isFirstPurchase {
			return false
		}
		return true
	}

	// use global settings
	if l.svc.Config.Invite.ReferralPercentage == 0 {
		return false
	}
	if l.svc.Config.Invite.OnlyFirstPurchase && !isFirstPurchase {
		return false
	}

	return true
}

// calculateCommission computes the commission amount based on order price and referral percentage
func (l *ActivateOrderLogic) calculateCommission(price int64, percentage uint8) int64 {
	return int64(float64(price) * (float64(percentage) / 100))
}

// clearServerCache clears user list cache for all servers associated with the subscription
func (l *ActivateOrderLogic) clearServerCache(ctx context.Context, sub *subscribe.Subscribe) {
	if err := l.svc.SubscribeModel.ClearCache(ctx, sub.Id); err != nil {
		logger.WithContext(ctx).Error("[Order Queue] Clear subscribe cache failed", logger.Field("error", err.Error()))
	}
}

// triggerUserGroupRecalculation triggers user group recalculation after subscription changes
// This runs asynchronously in background to avoid blocking the main order processing flow
func (l *ActivateOrderLogic) triggerUserGroupRecalculation(ctx context.Context, userId int64) {
	go func() {
		// Use a new context with timeout for group recalculation
		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		defer cancel()

		// Check if group management is enabled
		var groupEnabled string
		err := l.svc.DB.Table("system").
			Where("`category` = ? AND `key` = ?", "group", "enabled").
			Select("value").
			Scan(&groupEnabled).Error
		if err != nil || groupEnabled != "true" && groupEnabled != "1" {
			logger.Debugf("[Group Trigger] Group management not enabled, skipping recalculation")
			return
		}

		// Get the configured grouping mode
		var groupMode string
		err = l.svc.DB.Table("system").
			Where("`category` = ? AND `key` = ?", "group", "mode").
			Select("value").
			Scan(&groupMode).Error
		if err != nil {
			logger.Errorw("[Group Trigger] Failed to get group mode", logger.Field("error", err.Error()))
			return
		}

		// Validate group mode
		if groupMode != "average" && groupMode != "subscribe" && groupMode != "traffic" {
			logger.Debugf("[Group Trigger] Invalid group mode (current: %s), skipping", groupMode)
			return
		}

		// Trigger group recalculation with the configured mode
		logic := group.NewRecalculateGroupLogic(ctx, l.svc)
		req := &types.RecalculateGroupRequest{
			Mode: groupMode,
		}

		if err := logic.RecalculateGroup(req); err != nil {
			logger.Errorw("[Group Trigger] Failed to recalculate user group",
				logger.Field("user_id", userId),
				logger.Field("error", err.Error()),
			)
			return
		}

		logger.Infow("[Group Trigger] Successfully recalculated user group",
			logger.Field("user_id", userId),
			logger.Field("mode", groupMode),
		)
	}()
}

// Renewal handles subscription renewal including subscription extension,
// traffic reset (if configured), commission processing, and notifications
func (l *ActivateOrderLogic) Renewal(ctx context.Context, orderInfo *order.Order, iapExpireAt int64) error {
	userInfo, err := l.getExistingUser(ctx, orderInfo.UserId)
	if err != nil {
		return err
	}

	userSub, err := l.resolveRenewalActivationSubscription(ctx, orderInfo)
	if err != nil {
		return err
	}

	sub, err := l.getSubscribeInfo(ctx, orderInfo.SubscribeId)
	if err != nil {
		return err
	}

	if iapExpireAt > 0 {
		// Apple IAP 续费：按“累计加时”语义处理，避免连续购买时仅覆盖到期时间。
		if err = l.updateSubscriptionWithIAPExpire(ctx, userSub, sub, orderInfo, iapExpireAt); err != nil {
			return err
		}
	} else {
		if err = l.updateSubscriptionForRenewal(ctx, userSub, sub, orderInfo); err != nil {
			return err
		}
	}

	// Trigger user group recalculation (needed when renewing an expired subscription)
	l.triggerUserGroupRecalculation(ctx, userSub.UserId)

	// Clear user subscription cache
	err = l.svc.UserModel.ClearSubscribeCache(ctx, userSub)
	if err != nil {
		logger.WithContext(ctx).Error("Clear user subscribe cache failed",
			logger.Field("error", err.Error()),
			logger.Field("subscribe_id", userSub.Id),
			logger.Field("user_id", userSub.UserId),
		)
	}

	// Clear cache
	l.clearServerCache(ctx, sub)

	// Handle commission
	go l.handleCommission(context.Background(), userInfo, orderInfo)

	commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "subscription_renewed",
		"[SubscriptionFlow] renewal order updated existing subscription",
		append(commonLogic.OrderTraceFields(orderInfo),
			append(commonLogic.UserSubscribeTraceFields(userSub),
				logger.Field("iap_expire_at", iapExpireAt),
			)...,
		)...,
	)

	return nil
}

func (l *ActivateOrderLogic) resolveRenewalActivationSubscription(ctx context.Context, orderInfo *order.Order) (*user.Subscribe, error) {
	if orderInfo == nil {
		return nil, fmt.Errorf("renewal activation order is nil")
	}
	userSub, err := l.getUserSubscription(ctx, orderInfo.SubscribeToken)
	if err != nil {
		// Fallback: token may have been lost (subscription deleted or owner changed).
		// Locate the most recent subscription by user_id + subscribe_id with FOR UPDATE
		// to prevent a concurrent worker from renewing the same record twice.
		targetUserID := orderInfo.UserId
		if orderInfo.SubscriptionUserId > 0 {
			targetUserID = orderInfo.SubscriptionUserId
		}
		var fallbackSub user.Subscribe
		txErr := l.svc.DB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
			return tx.Clauses(clause.Locking{Strength: "UPDATE"}).
				Model(&user.Subscribe{}).
				Where("user_id = ? AND subscribe_id = ?", targetUserID, orderInfo.SubscribeId).
				Where("status IN ?", []int64{0, 1, 2, 3}).
				Order("expire_time DESC").
				Order("updated_at DESC").
				Order("id DESC").
				First(&fallbackSub).Error
		})
		if txErr != nil {
			logger.WithContext(ctx).Error("CRITICAL: Renewal activation token and fallback lookup both failed",
				logger.Field("token_error", err.Error()),
				logger.Field("fallback_error", txErr.Error()),
				logger.Field("order_no", orderInfo.OrderNo),
				logger.Field("target_user_id", targetUserID),
				logger.Field("subscribe_id", orderInfo.SubscribeId),
			)
			return nil, fmt.Errorf("renewal activation subscription not found by token or user_id+subscribe_id for order %s: %w", orderInfo.OrderNo, err)
		}
		logger.WithContext(ctx).Info("Renewal token lookup failed; found subscription via fallback user_id+subscribe_id",
			logger.Field("order_no", orderInfo.OrderNo),
			logger.Field("fallback_subscribe_id", fallbackSub.Id),
			logger.Field("target_user_id", targetUserID),
		)
		userSub = &fallbackSub
	}
	if orderInfo.UserId <= 0 {
		return userSub, nil
	}

	expectedUserID := orderInfo.SubscriptionUserId
	entitlement, err := commonLogic.ResolveEntitlementUser(ctx, l.svc.DB, orderInfo.UserId)
	if err != nil {
		return nil, err
	}
	if entitlement != nil && entitlement.Source == commonLogic.EntitlementSourceFamilyOwner && entitlement.EffectiveUserID > 0 {
		expectedUserID = entitlement.EffectiveUserID
	}
	if expectedUserID <= 0 {
		if entitlement != nil && entitlement.EffectiveUserID > 0 {
			expectedUserID = entitlement.EffectiveUserID
		} else {
			expectedUserID = orderInfo.UserId
		}
	}
	if userSub.UserId == expectedUserID {
		return userSub, nil
	}

	var target user.Subscribe
	err = l.svc.DB.WithContext(ctx).
		Model(&user.Subscribe{}).
		Where("user_id = ? AND subscribe_id = ? AND token != ''", expectedUserID, orderInfo.SubscribeId).
		Where("status IN ?", []int64{0, 1, 2, 3}).
		Order("expire_time DESC").
		Order("updated_at DESC").
		Order("id DESC").
		First(&target).Error
	if err != nil {
		logger.WithContext(ctx).Error("Resolve renewal activation target failed",
			logger.Field("error", err.Error()),
			logger.Field("order_no", orderInfo.OrderNo),
			logger.Field("order_user_id", orderInfo.UserId),
			logger.Field("expected_user_id", expectedUserID),
			logger.Field("token_owner_user_id", userSub.UserId),
			logger.Field("token_user_subscribe_id", userSub.Id),
			logger.Field("subscribe_id", orderInfo.SubscribeId),
		)
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fmt.Errorf("renewal activation target mismatch: expected user %d subscription for subscribe %d not found", expectedUserID, orderInfo.SubscribeId)
		}
		return nil, err
	}

	commonLogic.SubscriptionTraceInfo(logger.WithContext(ctx), commonLogic.SubscriptionTraceFlowOrder, "renewal_activation_target_redirected",
		"[SubscriptionFlow] renewal activation redirected to entitlement owner subscription",
		append(commonLogic.OrderTraceFields(orderInfo),
			logger.Field("expected_user_id", expectedUserID),
			logger.Field("original_user_subscribe_id", userSub.Id),
			logger.Field("original_owner_user_id", userSub.UserId),
			logger.Field("resolved_user_subscribe_id", target.Id),
			logger.Field("resolved_owner_user_id", target.UserId),
		)...,
	)
	return &target, nil
}

// getUserSubscription retrieves user subscription by token
func (l *ActivateOrderLogic) getUserSubscription(ctx context.Context, token string) (*user.Subscribe, error) {
	userSub, err := l.svc.UserModel.FindOneSubscribeByToken(ctx, token)
	if err != nil {
		logger.WithContext(ctx).Error("Find user subscribe failed", logger.Field("error", err.Error()))
		return nil, err
	}
	return userSub, nil
}

// updateSubscriptionWithIAPExpire 用于 Apple IAP 续费：按累计加时语义更新到期时间。
func (l *ActivateOrderLogic) updateSubscriptionWithIAPExpire(ctx context.Context, userSub *user.Subscribe, sub *subscribe.Subscribe, orderInfo *order.Order, iapExpireAt int64) error {
	now := time.Now()
	baseTime := subscriptionRenewalBaseTime(now, userSub)
	newExpire := tool.AddTime(sub.UnitTime, orderInfo.Quantity, baseTime)
	if iapExpireAt > 0 {
		appleExpire := time.Unix(iapExpireAt, 0)
		if appleExpire.After(newExpire) {
			newExpire = appleExpire
		}
	}
	today := now.Day()
	resetDay := newExpire.Day()

	if sub.RenewalReset != nil && *sub.RenewalReset || today == resetDay {
		userSub.Download = 0
		userSub.Upload = 0
	}

	if userSub.FinishedAt != nil {
		userSub.FinishedAt = nil
	}

	userSub.OrderId = orderInfo.Id
	userSub.ExpireTime = newExpire
	userSub.Status = 1

	if err := l.svc.UserModel.UpdateSubscribe(ctx, userSub); err != nil {
		logger.WithContext(ctx).Error("Update user subscribe (IAP) failed", logger.Field("error", err.Error()))
		return err
	}
	return nil
}

// updateSubscriptionForRenewal updates subscription details for renewal including
// expiration time extension and traffic reset if configured
func (l *ActivateOrderLogic) updateSubscriptionForRenewal(ctx context.Context, userSub *user.Subscribe, sub *subscribe.Subscribe, orderInfo *order.Order) error {
	now := time.Now()
	baseTime := subscriptionRenewalBaseTime(now, userSub)
	today := now.Day()
	resetDay := baseTime.Day()

	// 套餐变更：更新套餐ID和流量配额，并重置已用流量
	if userSub.SubscribeId != orderInfo.SubscribeId {
		userSub.SubscribeId = orderInfo.SubscribeId
		userSub.Traffic = sub.Traffic
		userSub.Download = 0
		userSub.Upload = 0
	} else {
		// Reset traffic if enabled
		if (sub.RenewalReset != nil && *sub.RenewalReset) || today == resetDay {
			userSub.Download = 0
			userSub.Upload = 0
		}
	}

	if userSub.FinishedAt != nil {
		if userSub.FinishedAt.Before(now) && today > resetDay {
			// reset user traffic if finished at is before now
			userSub.Download = 0
			userSub.Upload = 0
		}

		userSub.FinishedAt = nil
	}

	userSub.OrderId = orderInfo.Id
	userSub.ExpireTime = tool.AddTime(sub.UnitTime, orderInfo.Quantity, baseTime)
	userSub.Status = 1
	// 续费时重置过期流量字段
	userSub.ExpiredDownload = 0
	userSub.ExpiredUpload = 0

	if err := l.svc.UserModel.UpdateSubscribe(ctx, userSub); err != nil {
		logger.WithContext(ctx).Error("Update user subscribe failed", logger.Field("error", err.Error()))
		return err
	}

	return nil
}

// ResetTraffic handles traffic quota reset for existing subscriptions
func (l *ActivateOrderLogic) ResetTraffic(ctx context.Context, orderInfo *order.Order) error {
	userInfo, err := l.getExistingUser(ctx, orderInfo.UserId)
	if err != nil {
		return err
	}

	userSub, err := l.getUserSubscription(ctx, orderInfo.SubscribeToken)
	if err != nil {
		return err
	}

	// Reset traffic
	userSub.Download = 0
	userSub.Upload = 0
	userSub.ExpiredDownload = 0
	userSub.ExpiredUpload = 0
	userSub.Status = 1

	if err := l.svc.UserModel.UpdateSubscribe(ctx, userSub); err != nil {
		logger.WithContext(ctx).Error("Update user subscribe failed", logger.Field("error", err.Error()))
		return err
	}

	sub, err := l.getSubscribeInfo(ctx, userSub.SubscribeId)
	if err != nil {
		return err
	}

	// Clear user subscription cache
	err = l.svc.UserModel.ClearSubscribeCache(ctx, userSub)
	if err != nil {
		logger.WithContext(ctx).Error("Clear user subscribe cache failed",
			logger.Field("error", err.Error()),
			logger.Field("subscribe_id", userSub.Id),
			logger.Field("user_id", userInfo.Id),
		)
	}

	// Clear cache
	l.clearServerCache(ctx, sub)

	// insert reset traffic log
	resetLog := &log.ResetSubscribe{
		Type:      log.ResetSubscribeTypePaid,
		UserId:    userInfo.Id,
		OrderNo:   orderInfo.OrderNo,
		Timestamp: time.Now().UnixMilli(),
	}

	content, _ := resetLog.Marshal()
	if err = l.svc.LogModel.Insert(ctx, &log.SystemLog{
		Type:     log.TypeResetSubscribe.Uint8(),
		Date:     time.Now().Format(time.DateOnly),
		ObjectID: userSub.Id,
		Content:  string(content),
	}); err != nil {
		logger.WithContext(ctx).Error("[Order Queue]Insert reset subscribe log failed", logger.Field("error", err.Error()))
	}

	return nil
}

// Recharge handles balance recharge orders including balance updates,
// transaction logging, and notifications
func (l *ActivateOrderLogic) Recharge(ctx context.Context, orderInfo *order.Order) error {
	userInfo, err := l.getExistingUser(ctx, orderInfo.UserId)
	if err != nil {
		return err
	}

	// Update balance in transaction
	err = l.svc.DB.Transaction(func(tx *gorm.DB) error {
		// Idempotency: check if balance log already exists for this order
		var existingLogCount int64
		if e := tx.Model(&log.SystemLog{}).
			Where("type = ? AND object_id = ? AND content LIKE ?",
				log.TypeBalance.Uint8(), userInfo.Id, fmt.Sprintf("%%\"%s\"%%", orderInfo.OrderNo)).
			Count(&existingLogCount).Error; e != nil {
			return e
		}
		if existingLogCount > 0 {
			logger.WithContext(ctx).Info("Recharge already processed, skip",
				logger.Field("order_no", orderInfo.OrderNo),
				logger.Field("user_id", userInfo.Id),
			)
			return nil
		}

		// Atomic increment to prevent lost updates
		if e := tx.Model(&user.User{}).Where("id = ?", userInfo.Id).
			UpdateColumn("balance", gorm.Expr("balance + ?", orderInfo.Price)).Error; e != nil {
			return e
		}
		userInfo.Balance += orderInfo.Price

		balanceLog := &log.Balance{
			Amount:    orderInfo.Price,
			Type:      log.BalanceTypeRecharge,
			OrderNo:   orderInfo.OrderNo,
			Balance:   userInfo.Balance,
			Timestamp: time.Now().UnixMilli(),
		}
		content, _ := balanceLog.Marshal()

		return tx.Model(&log.SystemLog{}).Create(&log.SystemLog{
			Type:     log.TypeBalance.Uint8(),
			Date:     time.Now().Format("2006-01-02"),
			ObjectID: userInfo.Id,
			Content:  string(content),
		}).Error
	})

	if err != nil {
		logger.WithContext(ctx).Error("[Recharge] Database transaction failed", logger.Field("error", err.Error()))
		return err
	}

	// clear user cache
	if err = l.svc.UserModel.UpdateUserCache(ctx, userInfo); err != nil {
		logger.WithContext(ctx).Error("[Recharge] Update user cache failed", logger.Field("error", err.Error()))
		return err
	}

	return nil
}

// RedemptionActivate handles redemption code activation including subscription creation,
// redemption record creation, used count update, cache clearing, and notifications
func (l *ActivateOrderLogic) RedemptionActivate(ctx context.Context, orderInfo *order.Order) error {
	// 1. 获取用户信息
	userInfo, err := l.getExistingUser(ctx, orderInfo.UserId)
	if err != nil {
		return err
	}

	// 2. 获取套餐信息
	sub, err := l.getSubscribeInfo(ctx, orderInfo.SubscribeId)
	if err != nil {
		return err
	}

	// 3. 从Redis获取兑换码信息，Redis缺失时回查DB
	var redemptionData struct {
		RedemptionCodeId int64  `json:"redemption_code_id"`
		UnitTime         string `json:"unit_time"`
		Quantity         int64  `json:"quantity"`
	}

	cacheKey := fmt.Sprintf("redemption_order:%s", orderInfo.OrderNo)
	data, redisErr := l.svc.Redis.Get(ctx, cacheKey).Result()
	if redisErr == nil {
		if err = json.Unmarshal([]byte(data), &redemptionData); err != nil {
			logger.WithContext(ctx).Error("Unmarshal redemption cache failed", logger.Field("error", err.Error()))
			return err
		}
	} else {
		// Redis miss — fall back to DB activation_context field.
		logger.WithContext(ctx).Infow("Redis cache miss for redemption order, falling back to DB",
			logger.Field("cache_key", cacheKey),
			logger.Field("error", redisErr.Error()),
		)

		if orderInfo.ActivationContext == "" {
			logger.WithContext(ctx).Error("CRITICAL: activation_context missing in DB and Redis expired; cannot recover redemption order",
				logger.Field("order_no", orderInfo.OrderNo),
			)
			return errors.New("activation context not found: Redis expired and no DB fallback available")
		}

		var fullContext struct {
			Type             string `json:"type"`
			RedemptionCodeId int64  `json:"redemption_code_id"`
			UnitTime         string `json:"unit_time"`
			Quantity         int64  `json:"quantity"`
		}
		if err = json.Unmarshal([]byte(orderInfo.ActivationContext), &fullContext); err != nil {
			logger.WithContext(ctx).Error("Unmarshal DB activation_context failed",
				logger.Field("order_no", orderInfo.OrderNo),
				logger.Field("error", err.Error()),
			)
			return err
		}
		redemptionData.RedemptionCodeId = fullContext.RedemptionCodeId
		redemptionData.UnitTime = fullContext.UnitTime
		redemptionData.Quantity = fullContext.Quantity
	}

	// 4. 幂等性检查：查询是否已有兑换记录
	existingRecords, err := l.svc.RedemptionRecordModel.FindByUserId(ctx, userInfo.Id)
	if err == nil {
		for _, record := range existingRecords {
			if record.RedemptionCodeId == redemptionData.RedemptionCodeId {
				logger.WithContext(ctx).Info("Redemption already processed, skip",
					logger.Field("order_no", orderInfo.OrderNo),
					logger.Field("user_id", userInfo.Id),
					logger.Field("redemption_code_id", redemptionData.RedemptionCodeId),
				)
				// 已处理过，直接返回成功（幂等性保护）
				return nil
			}
		}
	}

	// 5. 查找用户现有订阅
	var existingSubscribe *user.Subscribe
	userSubscribes, err := l.svc.UserModel.QueryUserSubscribe(ctx, userInfo.Id, 0, 1)
	if err == nil {
		for _, us := range userSubscribes {
			if us.SubscribeId == orderInfo.SubscribeId {
				existingSubscribe = &user.Subscribe{
					Id:          us.Id,
					UserId:      us.UserId,
					SubscribeId: us.SubscribeId,
					ExpireTime:  us.ExpireTime,
					Status:      us.Status,
					Traffic:     us.Traffic,
					Download:    us.Download,
					Upload:      us.Upload,
					NodeGroupId: us.NodeGroupId,
				}
				break
			}
		}
	}

	now := time.Now()

	// 6. 使用事务保护核心操作
	err = l.svc.DB.Transaction(func(tx *gorm.DB) error {
		// 6.1 创建或更新订阅
		if existingSubscribe != nil {
			// 续期现有订阅
			var newExpireTime time.Time
			if existingSubscribe.ExpireTime.After(now) {
				newExpireTime = existingSubscribe.ExpireTime
			} else {
				newExpireTime = now
			}

			// 计算新的过期时间
			newExpireTime = tool.AddTime(redemptionData.UnitTime, redemptionData.Quantity, newExpireTime)

			// 更新订阅
			existingSubscribe.OrderId = orderInfo.Id // 设置OrderId用于追溯
			existingSubscribe.ExpireTime = newExpireTime
			existingSubscribe.Status = 1

			// 重置流量（如果套餐有流量限制）
			if sub.Traffic > 0 {
				existingSubscribe.Traffic = sub.Traffic * 1024 * 1024 * 1024
				existingSubscribe.Download = 0
				existingSubscribe.Upload = 0
			}

			err = l.svc.UserModel.UpdateSubscribe(ctx, existingSubscribe, tx)
			if err != nil {
				logger.WithContext(ctx).Error("Update subscribe failed", logger.Field("error", err.Error()))
				return err
			}

			logger.WithContext(ctx).Info("Extended existing subscription",
				logger.Field("subscribe_id", existingSubscribe.Id),
				logger.Field("new_expire_time", newExpireTime),
			)
		} else {
			// 检查配额限制
			if sub.Quota > 0 {
				var count int64
				if err := tx.Model(&user.Subscribe{}).
					Where("user_id = ? AND subscribe_id = ?", userInfo.Id, orderInfo.SubscribeId).
					Count(&count).Error; err != nil {
					logger.WithContext(ctx).Error("Count user subscribe failed", logger.Field("error", err.Error()))
					return err
				}
				if count >= sub.Quota {
					logger.WithContext(ctx).Infow("Subscribe quota limit exceeded",
						logger.Field("user_id", userInfo.Id),
						logger.Field("subscribe_id", orderInfo.SubscribeId),
						logger.Field("quota", sub.Quota),
						logger.Field("current_count", count),
					)
					return fmt.Errorf("subscribe quota limit exceeded")
				}
			}

			// 创建新订阅
			expireTime := tool.AddTime(redemptionData.UnitTime, redemptionData.Quantity, now)
			traffic := int64(0)
			if sub.Traffic > 0 {
				traffic = sub.Traffic * 1024 * 1024 * 1024
			}

			newSubscribe := &user.Subscribe{
				UserId:      userInfo.Id,
				OrderId:     orderInfo.Id,
				SubscribeId: orderInfo.SubscribeId,
				StartTime:   now,
				ExpireTime:  expireTime,
				FinishedAt:  nil,
				Traffic:     traffic,
				Download:    0,
				Upload:      0,
				Token:       uuidx.SubscribeToken(orderInfo.OrderNo),
				UUID:        uuid.New().String(),
				Status:      1,
				NodeGroupId: sub.NodeGroupId, // Inherit node_group_id from subscription plan
			}

			err = l.svc.UserModel.InsertSubscribe(ctx, newSubscribe, tx)
			if err != nil {
				logger.WithContext(ctx).Error("Insert subscribe failed", logger.Field("error", err.Error()))
				return err
			}

			logger.WithContext(ctx).Info("Created new subscription",
				logger.Field("subscribe_id", newSubscribe.Id),
				logger.Field("expire_time", expireTime),
			)
		}

		// 6.2 更新兑换码使用次数
		err = l.svc.RedemptionCodeModel.IncrementUsedCount(ctx, redemptionData.RedemptionCodeId, tx)
		if err != nil {
			logger.WithContext(ctx).Error("Increment used count failed", logger.Field("error", err.Error()))
			return err
		}

		// 6.3 创建兑换记录
		redemptionRecord := &redemption.RedemptionRecord{
			RedemptionCodeId: redemptionData.RedemptionCodeId,
			UserId:           userInfo.Id,
			SubscribeId:      orderInfo.SubscribeId,
			UnitTime:         redemptionData.UnitTime,
			Quantity:         redemptionData.Quantity,
			RedeemedAt:       now,
			CreatedAt:        now,
		}

		err = l.svc.RedemptionRecordModel.Insert(ctx, redemptionRecord, tx)
		if err != nil {
			logger.WithContext(ctx).Error("Insert redemption record failed", logger.Field("error", err.Error()))
			return err
		}

		return nil
	})

	if err != nil {
		logger.WithContext(ctx).Error("Redemption transaction failed", logger.Field("error", err.Error()))
		return err
	}

	// Trigger user group recalculation (runs in background)
	l.triggerUserGroupRecalculation(ctx, userInfo.Id)

	// 7. 清理缓存（关键步骤：让节点获取最新订阅）
	l.clearServerCache(ctx, sub)

	// 7.1 清理用户订阅缓存（确保用户端显示最新信息）
	if existingSubscribe != nil {
		err = l.svc.UserModel.ClearSubscribeCache(ctx, existingSubscribe)
		if err != nil {
			logger.WithContext(ctx).Error("Clear user subscribe cache failed",
				logger.Field("error", err.Error()),
				logger.Field("subscribe_id", existingSubscribe.Id),
				logger.Field("user_id", userInfo.Id),
			)
		}
	}

	// 8. 删除Redis临时数据
	l.svc.Redis.Del(ctx, cacheKey)

	// 9. 发送通知（可选）
	// 可以复用现有的通知模板或创建新的兑换通知模板
	// l.sendNotifications(ctx, orderInfo, userInfo, sub, existingSubscribe, telegram.RedemptionNotify)

	logger.WithContext(ctx).Info("Redemption activation success",
		logger.Field("order_no", orderInfo.OrderNo),
		logger.Field("user_id", userInfo.Id),
		logger.Field("subscribe_id", orderInfo.SubscribeId),
	)

	return nil
}

// isNewUserOnlyForQuantity checks whether the matched discount tier has new_user_only enabled.
// Returns true only when all matching tiers for the given quantity require new-user status
// (i.e. no fallback tier with new_user_only=false exists). When both a new-user-only tier
// and a general tier exist for the same quantity, non-new-users can still purchase via the
// general tier, so this returns false.
func isNewUserOnlyForQuantity(discounts []internaltypes.SubscribeDiscount, inputQuantity int64) bool {
	hasNewUserOnly := false
	hasFallback := false
	for _, d := range discounts {
		if d.Quantity != inputQuantity {
			continue
		}
		if d.NewUserOnly {
			hasNewUserOnly = true
		} else {
			hasFallback = true
		}
	}
	return hasNewUserOnly && !hasFallback
}
