// Package client_order 提供 C 端订单下单服务。
package client_order

import (
	"context"
	"fmt"
	"math/rand"
	"slices"
	"strconv"
	"strings"
	"time"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/model/dto"
	asset "github.com/break/junhong_cmp_fiber/internal/service/asset"
	customerBinding "github.com/break/junhong_cmp_fiber/internal/service/customer_binding"
	"github.com/break/junhong_cmp_fiber/internal/service/purchase_validation"
	"github.com/break/junhong_cmp_fiber/internal/store/postgres"
	"github.com/break/junhong_cmp_fiber/pkg/alipay"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/bytedance/sonic"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
	"gorm.io/datatypes"
	"gorm.io/gorm"
)

const (
	clientPurchaseIdempotencyTTL = 5 * time.Minute
	clientPurchaseLockTTL        = 10 * time.Second
)

// WechatConfigServiceInterface 微信配置服务接口。
type WechatConfigServiceInterface interface {
	GetActiveConfig(ctx context.Context) (*model.WechatConfig, error)
}

// OrderWalletPayServiceInterface 订单钱包支付服务接口。
// 用于将钱包扣款、套餐激活、佣金计算等核心逻辑委托给 B 端 order.Service 处理。
type OrderWalletPayServiceInterface interface {
	WalletPay(ctx context.Context, orderID uint, buyerType string, buyerID uint) error
}

// PaymentMethodPolicy 提供按资产类型校验支付方式的能力。
type PaymentMethodPolicy interface {
	AllowedMethods(ctx context.Context, assetType string) ([]string, error)
	EnsureAllowed(ctx context.Context, assetType, paymentMethod string) error
}

// ForceRechargeRequirement 强充要求。
type ForceRechargeRequirement struct {
	NeedForceRecharge   bool
	ForceRechargeAmount int64
}

// Service 客户端订单服务。
type Service struct {
	assetService               *asset.Service
	purchaseValidationService  *purchase_validation.Service
	orderStore                 *postgres.OrderStore
	rechargeOrderStore         *postgres.RechargeOrderStore
	paymentStore               *postgres.PaymentStore
	walletStore                *postgres.AssetWalletStore
	customerBinding            *customerBinding.Service
	openIDStore                *postgres.PersonalCustomerOpenIDStore
	personalCustomerStore      *postgres.PersonalCustomerStore
	personalCustomerPhoneStore *postgres.PersonalCustomerPhoneStore
	wechatConfigService        WechatConfigServiceInterface
	orderPaymentService        OrderWalletPayServiceInterface
	packageSeriesStore         *postgres.PackageSeriesStore
	shopSeriesAllocationStore  *postgres.ShopSeriesAllocationStore
	iotCardStore               *postgres.IotCardStore
	deviceStore                *postgres.DeviceStore
	db                         *gorm.DB
	redis                      *redis.Client
	logger                     *zap.Logger
	paymentMethodPolicy        PaymentMethodPolicy
}

// SetPaymentMethodPolicy 注入 C 端支付方式策略。
func (s *Service) SetPaymentMethodPolicy(policy PaymentMethodPolicy) {
	s.paymentMethodPolicy = policy
}

// New 创建客户端订单服务。
func New(
	assetService *asset.Service,
	purchaseValidationService *purchase_validation.Service,
	orderStore *postgres.OrderStore,
	rechargeOrderStore *postgres.RechargeOrderStore,
	paymentStore *postgres.PaymentStore,
	walletStore *postgres.AssetWalletStore,
	binding *customerBinding.Service,
	openIDStore *postgres.PersonalCustomerOpenIDStore,
	personalCustomerStore *postgres.PersonalCustomerStore,
	personalCustomerPhoneStore *postgres.PersonalCustomerPhoneStore,
	wechatConfigService WechatConfigServiceInterface,
	orderPaymentService OrderWalletPayServiceInterface,
	packageSeriesStore *postgres.PackageSeriesStore,
	shopSeriesAllocationStore *postgres.ShopSeriesAllocationStore,
	iotCardStore *postgres.IotCardStore,
	deviceStore *postgres.DeviceStore,
	db *gorm.DB,
	redisClient *redis.Client,
	logger *zap.Logger,
) *Service {
	return &Service{
		assetService:               assetService,
		purchaseValidationService:  purchaseValidationService,
		orderStore:                 orderStore,
		rechargeOrderStore:         rechargeOrderStore,
		paymentStore:               paymentStore,
		walletStore:                walletStore,
		customerBinding:            binding,
		openIDStore:                openIDStore,
		personalCustomerStore:      personalCustomerStore,
		personalCustomerPhoneStore: personalCustomerPhoneStore,
		wechatConfigService:        wechatConfigService,
		orderPaymentService:        orderPaymentService,
		packageSeriesStore:         packageSeriesStore,
		shopSeriesAllocationStore:  shopSeriesAllocationStore,
		iotCardStore:               iotCardStore,
		deviceStore:                deviceStore,
		db:                         db,
		redis:                      redisClient,
		logger:                     logger,
	}
}

// CreateOrder 创建客户端订单。
// 普通套餐下单：仅创建待支付订单，不发起支付，需后续调用 POST /orders/:id/pay 支付。
// 强充场景：检测到需要强充时，直接创建充值单并发起微信支付（一步完成），此时 app_type 必传。
func (s *Service) CreateOrder(ctx context.Context, customerID uint, req *dto.ClientCreateOrderRequest) (*dto.ClientCreateOrderResponse, error) {
	if req == nil {
		return nil, errors.New(errors.CodeInvalidParam)
	}
	if s.redis == nil {
		return nil, errors.New(errors.CodeInternalError, "Redis 服务未配置")
	}

	skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{})
	assetInfo, err := s.assetService.Resolve(skipCtx, strings.TrimSpace(req.Identifier), false)
	if err != nil {
		return nil, err
	}
	if owned, err := s.customerBinding.OwnsAsset(skipCtx, customerID, assetInfo.AssetType, assetInfo.AssetID); err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询资产归属失败")
	} else if !owned {
		return nil, errors.New(errors.CodeForbidden, "无权限操作该资产或资源不存在")
	}
	if s.paymentMethodPolicy == nil {
		return nil, errors.New(errors.CodeNoPaymentConfig)
	}
	if req.PaymentMethod == "" {
		return nil, errors.New(errors.CodeInvalidParam, "请选择支付方式")
	}
	if err := s.paymentMethodPolicy.EnsureAllowed(skipCtx, assetInfo.AssetType, req.PaymentMethod); err != nil {
		return nil, err
	}

	validationResult, err := s.validatePurchase(skipCtx, assetInfo, req.PackageIDs)
	if err != nil {
		return nil, err
	}

	// before_order 策略：必须先实名才能购买套餐
	if assetInfo.RealnamePolicy == constants.RealnamePolicyBeforeOrder && assetInfo.RealNameStatus != 1 {
		return nil, errors.New(errors.CodeNeedRealname)
	}

	// 先判断是否需要强充，避免普通下单时不必要地校验微信配置
	forceRecharge := s.checkForceRechargeRequirement(skipCtx, validationResult)

	businessKey := buildClientPurchaseBusinessKey(customerID, assetInfo, req.PackageIDs, req.PaymentMethod)
	redisKey := constants.RedisClientPurchaseIdempotencyKey(businessKey)
	lockKey := constants.RedisClientPurchaseLockKey(assetInfo.AssetType, assetInfo.AssetID)

	lockAcquired, err := s.redis.SetNX(skipCtx, lockKey, time.Now().String(), clientPurchaseLockTTL).Result()
	if err != nil {
		s.logger.Warn("获取客户端购买分布式锁失败，继续尝试幂等标记",
			zap.Error(err),
			zap.String("lock_key", lockKey),
		)
	}
	if err == nil && !lockAcquired {
		return nil, errors.New(errors.CodeTooManyRequests, "订单正在创建中，请勿重复提交")
	}

	claimed, err := s.redis.SetNX(skipCtx, redisKey, "processing", clientPurchaseIdempotencyTTL).Result()
	if err != nil {
		if lockAcquired {
			_ = s.redis.Del(skipCtx, lockKey).Err()
		}
		return nil, errors.Wrap(errors.CodeInternalError, err, "设置客户端购买幂等标记失败")
	}
	if !claimed {
		if lockAcquired {
			_ = s.redis.Del(skipCtx, lockKey).Err()
		}
		existingValue, err := s.redis.Get(skipCtx, redisKey).Result()
		if err == nil && strings.HasPrefix(existingValue, "CRCH") {
			existingOrder, queryErr := s.rechargeOrderStore.GetByRechargeOrderNo(skipCtx, existingValue)
			if queryErr == nil && existingOrder.Status == model.RechargeOrderStatusPending {
				resp := &dto.ClientCreateOrderResponse{
					OrderType: "recharge",
					Recharge: &dto.ClientRechargeInfo{
						RechargeID:         existingOrder.ID,
						RechargeNo:         existingOrder.RechargeOrderNo,
						Amount:             existingOrder.Amount,
						Status:             rechargeStatusToClientStatus(int(existingOrder.Status)),
						StatusName:         clientRechargeStatusName(rechargeStatusToClientStatus(int(existingOrder.Status))),
						AutoPurchaseStatus: existingOrder.AutoPurchaseStatus,
					},
					Idempotent: true,
				}
				// 支付宝强充幂等：复用已有待支付 payment 或生成新链接
				if req.PaymentMethod == model.PaymentMethodAlipay {
					paymentLink, linkErr := s.getOrBuildAlipayPaymentLink(
						skipCtx, existingOrder.ID, model.PaymentOrderTypeRecharge,
						existingOrder.Amount, "余额充值",
					)
					if linkErr != nil {
						s.logger.Warn("强充幂等复用支付宝链接失败",
							zap.String("recharge_no", existingOrder.RechargeOrderNo),
							zap.Error(linkErr),
						)
					} else {
						resp.PaymentLink = paymentLink
					}
				}
				return resp, nil
			}
			if queryErr == nil {
				_ = s.redis.Del(skipCtx, redisKey).Err()
			}
		}
		return nil, errors.New(errors.CodeTooManyRequests, "订单正在创建中，请勿重复提交")
	}

	created := false
	defer func() {
		if lockAcquired {
			_ = s.redis.Del(skipCtx, lockKey).Err()
		}
		if !created {
			_ = s.redis.Del(skipCtx, redisKey).Err()
		}
	}()

	if forceRecharge.NeedForceRecharge {
		if s.paymentMethodPolicy == nil {
			return nil, errors.New(errors.CodeNoPaymentConfig)
		}
		if err := s.paymentMethodPolicy.EnsureAllowed(skipCtx, assetInfo.AssetType, req.PaymentMethod); err != nil {
			return nil, err
		}
		if req.PaymentMethod == model.PaymentMethodWallet {
			return nil, errors.New(errors.CodePaymentMethodUnavailable, "该套餐需要先充值，请选择当前资产允许的微信或支付宝方式")
		}
		// 强充第三方支付方式与资产类型必须匹配：单卡只允许支付宝，设备只允许微信（已暂时注释）
		// switch assetInfo.AssetType {
		// case "card":
		// 	if req.PaymentMethod != model.PaymentMethodAlipay {
		// 		return nil, errors.New(errors.CodeInvalidParam, "单卡资产强充只支持支付宝支付")
		// 	}
		// case constants.ResourceTypeDevice:
		// 	if req.PaymentMethod == model.PaymentMethodAlipay {
		// 		return nil, errors.New(errors.CodeInvalidParam, "设备资产强充只支持微信支付")
		// 	}
		// }

		if req.PaymentMethod == model.PaymentMethodAlipay {
			// 支付宝强充：不需要 app_type / OpenID
			activeConfig, err := s.wechatConfigService.GetActiveConfig(skipCtx)
			if err != nil {
				return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询支付配置失败")
			}
			if activeConfig == nil {
				return nil, errors.New(errors.CodeInvalidParam, "未找到生效的支付配置")
			}
			return s.createAlipayForceRechargeOrder(skipCtx, customerID, assetInfo, validationResult, activeConfig, forceRecharge, redisKey, &created)
		}
		// 微信强充：app_type 必须传入
		activeConfig, appID, err := s.resolveWechatConfig(skipCtx, req.AppType)
		if err != nil {
			return nil, err
		}
		openID, err := s.resolveCustomerOpenID(skipCtx, customerID, appID)
		if err != nil {
			return nil, err
		}
		paymentProvider, err := s.newPaymentProvider(activeConfig, appID, req.AppType)
		if err != nil {
			return nil, err
		}
		return s.createForceRechargeOrder(skipCtx, customerID, openID, assetInfo, validationResult, activeConfig, forceRecharge, redisKey, paymentProvider, &created)
	}

	return s.createPackageOrder(skipCtx, customerID, validationResult, req.PaymentMethod, redisKey, &created)
}

// getOrderAssetInfo 从订单中提取资产类型和 ID，无需额外 DB 查询
func getOrderAssetInfo(order *model.Order) (string, uint, error) {
	if order == nil {
		return "", 0, errors.New(errors.CodeNotFound, "订单不存在")
	}
	switch order.OrderType {
	case model.OrderTypeSingleCard:
		if order.IotCardID == nil || *order.IotCardID == 0 {
			return "", 0, errors.New(errors.CodeInvalidParam)
		}
		return "iot_card", *order.IotCardID, nil
	case model.OrderTypeDevice:
		if order.DeviceID == nil || *order.DeviceID == 0 {
			return "", 0, errors.New(errors.CodeInvalidParam)
		}
		return "device", *order.DeviceID, nil
	default:
		return "", 0, errors.New(errors.CodeInvalidParam)
	}
}

func (s *Service) validatePurchase(ctx context.Context, assetInfo *dto.AssetResolveResponse, packageIDs []uint) (*purchase_validation.PurchaseValidationResult, error) {
	switch assetInfo.AssetType {
	case "card":
		return s.purchaseValidationService.ValidatePersonalCardPurchase(ctx, assetInfo.AssetID, packageIDs)
	case constants.ResourceTypeDevice:
		return s.purchaseValidationService.ValidatePersonalDevicePurchase(ctx, assetInfo.AssetID, packageIDs)
	default:
		return nil, errors.New(errors.CodeInvalidParam)
	}
}

func (s *Service) resolveWechatConfig(ctx context.Context, appType string) (*model.WechatConfig, string, error) {
	activeConfig, err := s.wechatConfigService.GetActiveConfig(ctx)
	if err != nil {
		return nil, "", errors.Wrap(errors.CodeDatabaseError, err, "查询微信配置失败")
	}
	if activeConfig == nil {
		return nil, "", errors.New(errors.CodeWechatPayFailed, "未找到生效的微信支付配置")
	}

	switch appType {
	case "official_account":
		if activeConfig.OaAppID == "" {
			return nil, "", errors.New(errors.CodeWechatPayFailed, "公众号支付配置不完整")
		}
		return activeConfig, activeConfig.OaAppID, nil
	case "miniapp":
		if activeConfig.MiniappAppID == "" {
			return nil, "", errors.New(errors.CodeWechatPayFailed, "小程序支付配置不完整")
		}
		return activeConfig, activeConfig.MiniappAppID, nil
	default:
		return nil, "", errors.New(errors.CodeInvalidParam)
	}
}

func (s *Service) resolveCustomerOpenID(ctx context.Context, customerID uint, appID string) (string, error) {
	records, err := s.openIDStore.ListByCustomerID(ctx, customerID)
	if err != nil {
		return "", errors.Wrap(errors.CodeDatabaseError, err, "查询微信授权信息失败")
	}

	for _, record := range records {
		if record == nil {
			continue
		}
		if record.AppID == appID && strings.TrimSpace(record.OpenID) != "" {
			return record.OpenID, nil
		}
	}

	return "", errors.New(errors.CodeNotFound, "未找到当前应用的微信授权信息")
}

func (s *Service) createPackageOrder(
	ctx context.Context,
	customerID uint,
	validationResult *purchase_validation.PurchaseValidationResult,
	paymentMethod string,
	redisKey string,
	created *bool,
) (*dto.ClientCreateOrderResponse, error) {
	// 查询卖家成本价，用于差价佣金链式计算的起点
	// 平台直销（sellerShopID == 0）时成本价为 0，CalculateCostDiffCommission 会直接跳过
	var sellerCostPrice int64
	sellerShopID := resolveSellerShopID(validationResult)
	if sellerShopID > 0 && len(validationResult.Packages) > 0 {
		costPrice, err := s.purchaseValidationService.GetCostPrice(ctx, validationResult.Packages[0], sellerShopID)
		if err != nil {
			return nil, errors.Wrap(errors.CodeInternalError, err, "查询卖家成本价失败")
		}
		sellerCostPrice = costPrice
	}

	order, err := s.buildPendingOrder(ctx, customerID, validationResult, sellerCostPrice, paymentMethod)
	if err != nil {
		return nil, err
	}

	items, err := s.buildOrderItems(ctx, customerID, validationResult)
	if err != nil {
		return nil, err
	}

	if err := s.orderStore.Create(ctx, order, items); err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建订单失败")
	}

	s.markClientPurchaseCreated(ctx, redisKey, order.OrderNo)
	*created = true

	return &dto.ClientCreateOrderResponse{
		OrderType: "package",
		Order: &dto.ClientOrderInfo{
			OrderID:           order.ID,
			OrderNo:           order.OrderNo,
			TotalAmount:       order.TotalAmount,
			PaymentMethod:     order.PaymentMethod,
			PaymentStatus:     order.PaymentStatus,
			PaymentStatusName: constants.GetOrderPaymentStatusName(order.PaymentStatus),
			CreatedAt:         formatClientServiceTime(order.CreatedAt),
		},
	}, nil
}

func (s *Service) createForceRechargeOrder(
	ctx context.Context,
	customerID uint,
	openID string,
	assetInfo *dto.AssetResolveResponse,
	validationResult *purchase_validation.PurchaseValidationResult,
	activeConfig *model.WechatConfig,
	forceRecharge *ForceRechargeRequirement,
	redisKey string,
	paymentProvider PaymentProvider,
	created *bool,
) (*dto.ClientCreateOrderResponse, error) {
	resourceType, resourceID, err := resolveWalletResource(validationResult)
	if err != nil {
		return nil, err
	}

	wallet, err := s.getOrCreateWallet(ctx, resourceType, resourceID, resolveSellerShopID(validationResult))
	if err != nil {
		return nil, err
	}

	linkedPackageIDs, err := sonic.Marshal(extractPackageIDs(validationResult.Packages))
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "序列化关联套餐失败")
	}

	rechargeOrderNo := generateClientRechargeNo()
	var iotCardID *uint
	var deviceID *uint
	if validationResult.Card != nil {
		iotCardID = &validationResult.Card.ID
	}
	if validationResult.Device != nil {
		deviceID = &validationResult.Device.ID
	}

	rechargeOrder := &model.RechargeOrder{
		RechargeOrderNo:    rechargeOrderNo,
		UserID:             customerID,
		AssetWalletID:      wallet.ID,
		ResourceType:       resourceType,
		ResourceID:         resourceID,
		IotCardID:          iotCardID,
		DeviceID:           deviceID,
		Amount:             forceRecharge.ForceRechargeAmount,
		Status:             model.RechargeOrderStatusPending,
		ShopIDTag:          wallet.ShopIDTag,
		EnterpriseIDTag:    wallet.EnterpriseIDTag,
		OperatorType:       "personal_customer",
		Generation:         resolveGeneration(validationResult),
		LinkedPackageIDs:   datatypes.JSON(linkedPackageIDs),
		LinkedOrderType:    resolveOrderType(validationResult),
		LinkedCarrierType:  assetInfo.AssetType,
		LinkedCarrierID:    &resourceID,
		AutoPurchaseStatus: model.AutoPurchaseStatusPending,
	}

	if err := s.rechargeOrderStore.Create(ctx, rechargeOrder); err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建充值订单失败")
	}

	paymentNo := generateClientPaymentNo()
	payment := &model.Payment{
		PaymentNo:       paymentNo,
		OrderID:         rechargeOrder.ID,
		OrderType:       model.PaymentOrderTypeRecharge,
		PaymentMethod:   model.PaymentByWechat,
		Amount:          forceRecharge.ForceRechargeAmount,
		Status:          model.PaymentRecordStatusPending,
		PaymentConfigID: &activeConfig.ID,
	}

	if err := s.paymentStore.Create(ctx, payment); err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建支付记录失败")
	}

	paymentResult, err := paymentProvider.CreateJSAPIPayment(ctx, paymentNo, "余额充值", openID, int(rechargeOrder.Amount))
	if err != nil {
		return nil, err
	}

	s.markClientPurchaseCreated(ctx, redisKey, rechargeOrderNo)
	*created = true

	clientStatus := rechargeStatusToClientStatus(int(rechargeOrder.Status))
	return &dto.ClientCreateOrderResponse{
		OrderType: "recharge",
		Recharge: &dto.ClientRechargeInfo{
			RechargeID:         rechargeOrder.ID,
			RechargeNo:         rechargeOrderNo,
			Amount:             rechargeOrder.Amount,
			Status:             clientStatus,
			StatusName:         clientRechargeStatusName(clientStatus),
			AutoPurchaseStatus: rechargeOrder.AutoPurchaseStatus,
		},
		PayConfig:         buildClientPayConfigFromResult(paymentResult),
		LinkedPackageInfo: buildLinkedPackageInfo(validationResult, forceRecharge),
	}, nil
}

func (s *Service) fetchBuyerSnapshot(ctx context.Context, customerID uint) (phone, nickname *string) {
	if customerID == 0 {
		return nil, nil
	}
	customer, err := s.personalCustomerStore.GetByID(ctx, customerID)
	if err != nil {
		s.logger.Warn("fetchBuyerSnapshot: 查询个人客户失败", zap.Uint("customer_id", customerID), zap.Error(err))
	} else if customer != nil && customer.Nickname != "" {
		nickname = &customer.Nickname
	}

	phoneRecord, err := s.personalCustomerPhoneStore.GetPrimaryPhone(ctx, customerID)
	if err != nil {
		s.logger.Warn("fetchBuyerSnapshot: 查询买家手机号失败", zap.Uint("customer_id", customerID), zap.Error(err))
	} else if phoneRecord != nil && phoneRecord.Phone != "" {
		phone = &phoneRecord.Phone
	}
	return phone, nickname
}

func (s *Service) buildPersonalCustomerOperatorSnapshot(ctx context.Context, customerID uint, nickname *string) (*uint, string, string) {
	if customerID == 0 {
		return nil, "", ""
	}

	name := ""
	if nickname != nil {
		name = strings.TrimSpace(*nickname)
	}
	if name == "" {
		customer, err := s.personalCustomerStore.GetByID(ctx, customerID)
		if err != nil {
			s.logger.Warn("buildPersonalCustomerOperatorSnapshot: 查询个人客户失败", zap.Uint("customer_id", customerID), zap.Error(err))
		} else if customer != nil {
			name = strings.TrimSpace(customer.Nickname)
		}
	}

	id := customerID
	return &id, model.OperatorAccountTypePersonalCustomer, name
}

func (s *Service) buildPendingOrder(ctx context.Context, customerID uint, result *purchase_validation.PurchaseValidationResult, sellerCostPrice int64, paymentMethod string) (*model.Order, error) {
	orderType := resolveOrderType(result)
	if orderType == "" {
		return nil, errors.New(errors.CodeInvalidParam)
	}

	now := time.Now()
	expiresAt := now.Add(constants.OrderExpireTimeout)
	order := &model.Order{
		BaseModel: model.BaseModel{
			Creator: customerID,
			Updater: customerID,
		},
		OrderNo:                 s.orderStore.GenerateOrderNo(),
		OrderType:               orderType,
		BuyerType:               model.BuyerTypePersonal,
		BuyerID:                 customerID,
		TotalAmount:             result.TotalPrice,
		PaymentMethod:           paymentMethod,
		PaymentStatus:           model.PaymentStatusPending,
		CommissionStatus:        model.CommissionStatusPending,
		CommissionConfigVersion: 0,
		Source:                  constants.OrderSourceClient,
		PurchaseRole:            model.PurchaseRoleSelfPurchase,
		Generation:              resolveGeneration(result),
		ExpiresAt:               &expiresAt,
		SellerCostPrice:         sellerCostPrice,
	}

	if result.Card != nil {
		order.IotCardID = &result.Card.ID
		order.SeriesID = result.Card.SeriesID
		order.SellerShopID = result.Card.ShopID
		order.AssetIdentifier = result.Card.ICCID
	} else if result.Device != nil {
		order.DeviceID = &result.Device.ID
		order.SeriesID = result.Device.SeriesID
		order.SellerShopID = result.Device.ShopID
		order.AssetIdentifier = firstNonEmptyClientOrderIdentifier(result.Device.VirtualNo, result.Device.IMEI)
	}

	order.BuyerPhone, order.BuyerNickname = s.fetchBuyerSnapshot(ctx, customerID)
	order.OperatorAccountID, order.OperatorAccountType, order.OperatorAccountName = s.buildPersonalCustomerOperatorSnapshot(ctx, customerID, order.BuyerNickname)

	return order, nil
}

// firstNonEmptyClientOrderIdentifier 返回 C 端订单可用的第一个资产标识快照。
func firstNonEmptyClientOrderIdentifier(values ...string) string {
	for _, value := range values {
		if value != "" {
			return value
		}
	}
	return ""
}

func (s *Service) buildOrderItems(ctx context.Context, customerID uint, result *purchase_validation.PurchaseValidationResult) ([]*model.OrderItem, error) {
	sellerShopID := resolveSellerShopID(result)
	items := make([]*model.OrderItem, 0, len(result.Packages))

	for _, pkg := range result.Packages {
		if pkg == nil {
			continue
		}

		unitPrice, err := s.purchaseValidationService.GetPurchasePrice(ctx, pkg, sellerShopID)
		if err != nil {
			return nil, err
		}

		items = append(items, &model.OrderItem{
			BaseModel: model.BaseModel{
				Creator: customerID,
				Updater: customerID,
			},
			PackageID:   pkg.ID,
			PackageName: pkg.PackageName,
			PackageType: &pkg.PackageType,
			Quantity:    1,
			UnitPrice:   unitPrice,
			Amount:      unitPrice,
		})
	}

	return items, nil
}

func (s *Service) checkForceRechargeRequirement(ctx context.Context, result *purchase_validation.PurchaseValidationResult) *ForceRechargeRequirement {
	defaultResult := &ForceRechargeRequirement{NeedForceRecharge: false}

	var seriesID *uint
	var sellerShopID uint
	var firstCommissionTriggered bool
	if result.Card != nil {
		seriesID = result.Card.SeriesID
		if seriesID != nil {
			firstCommissionTriggered = result.Card.IsFirstRechargeTriggeredBySeries(*seriesID)
		}
		if result.Card.ShopID != nil {
			sellerShopID = *result.Card.ShopID
		}
	} else if result.Device != nil {
		seriesID = result.Device.SeriesID
		if seriesID != nil {
			firstCommissionTriggered = result.Device.IsFirstRechargeTriggeredBySeries(*seriesID)
		}
		if result.Device.ShopID != nil {
			sellerShopID = *result.Device.ShopID
		}
	}

	if seriesID == nil || *seriesID == 0 {
		return defaultResult
	}

	series, err := s.packageSeriesStore.GetByID(ctx, *seriesID)
	if err != nil {
		s.logger.Warn("查询套餐系列失败", zap.Uint("series_id", *seriesID), zap.Error(err))
		return defaultResult
	}

	config, err := series.GetOneTimeCommissionConfig()
	if err != nil || config == nil || !config.Enable {
		return defaultResult
	}
	if firstCommissionTriggered {
		return defaultResult
	}

	if config.TriggerType == model.OneTimeCommissionTriggerFirstRecharge {
		return &ForceRechargeRequirement{
			NeedForceRecharge:   true,
			ForceRechargeAmount: config.Threshold,
		}
	}

	if config.EnableForceRecharge {
		amount := config.ForceAmount
		if amount == 0 {
			amount = config.Threshold
		}
		return &ForceRechargeRequirement{
			NeedForceRecharge:   true,
			ForceRechargeAmount: amount,
		}
	}

	if sellerShopID > 0 {
		allocation, allocErr := s.shopSeriesAllocationStore.GetByShopAndSeries(ctx, sellerShopID, *seriesID)
		if allocErr == nil && allocation.EnableForceRecharge {
			amount := allocation.ForceRechargeAmount
			if amount == 0 {
				amount = config.Threshold
			}
			return &ForceRechargeRequirement{
				NeedForceRecharge:   true,
				ForceRechargeAmount: amount,
			}
		}
	}

	return defaultResult
}

func (s *Service) markClientPurchaseCreated(ctx context.Context, redisKey string, value string) {
	if err := s.redis.Set(ctx, redisKey, value, clientPurchaseIdempotencyTTL).Err(); err != nil {
		s.logger.Warn("设置客户端购买幂等标记失败",
			zap.String("redis_key", redisKey),
			zap.Error(err),
		)
	}
}

func buildLinkedPackageInfo(result *purchase_validation.PurchaseValidationResult, forceRecharge *ForceRechargeRequirement) *dto.LinkedPackageInfo {
	packageNames := make([]string, 0, len(result.Packages))
	for _, pkg := range result.Packages {
		if pkg == nil || pkg.PackageName == "" {
			continue
		}
		packageNames = append(packageNames, pkg.PackageName)
	}

	return &dto.LinkedPackageInfo{
		PackageNames:        packageNames,
		TotalPackageAmount:  result.TotalPrice,
		ForceRechargeAmount: forceRecharge.ForceRechargeAmount,
		WalletCredit:        forceRecharge.ForceRechargeAmount,
	}
}

// getOrCreateWallet 获取或自动创建资产钱包
// 兜底机制：当钱包不存在时自动创建，避免因钱包未提前创建导致业务中断
func (s *Service) getOrCreateWallet(ctx context.Context, resourceType string, resourceID uint, shopIDTag uint) (*model.AssetWallet, error) {
	wallet, err := s.walletStore.GetByResourceTypeAndID(ctx, resourceType, resourceID)
	if err == nil {
		return wallet, nil
	}
	if err != gorm.ErrRecordNotFound {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询资产钱包失败")
	}

	// 钱包不存在，自动创建空钱包
	s.logger.Info("资产钱包不存在，自动创建",
		zap.String("resource_type", resourceType),
		zap.Uint("resource_id", resourceID),
		zap.Uint("shop_id_tag", shopIDTag),
	)

	newWallet := &model.AssetWallet{
		ResourceType:  resourceType,
		ResourceID:    resourceID,
		Balance:       0,
		FrozenBalance: 0,
		Currency:      "CNY",
		Status:        constants.AssetWalletStatusNormal,
		Version:       0,
		ShopIDTag:     shopIDTag,
	}
	if createErr := s.walletStore.Create(ctx, newWallet); createErr != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, createErr, "自动创建资产钱包失败")
	}

	// 重新查询获取完整数据（含数据库生成的字段）
	wallet, err = s.walletStore.GetByResourceTypeAndID(ctx, resourceType, resourceID)
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询资产钱包失败")
	}
	return wallet, nil
}

func resolveWalletResource(result *purchase_validation.PurchaseValidationResult) (string, uint, error) {
	if result.Card != nil {
		return constants.AssetWalletResourceTypeIotCard, result.Card.ID, nil
	}
	if result.Device != nil {
		return constants.AssetWalletResourceTypeDevice, result.Device.ID, nil
	}
	return "", 0, errors.New(errors.CodeInvalidParam)
}

func resolveOrderType(result *purchase_validation.PurchaseValidationResult) string {
	if result.Card != nil {
		return model.OrderTypeSingleCard
	}
	if result.Device != nil {
		return model.OrderTypeDevice
	}
	return ""
}

func resolveGeneration(result *purchase_validation.PurchaseValidationResult) int {
	if result.Card != nil && result.Card.Generation > 0 {
		return result.Card.Generation
	}
	if result.Device != nil && result.Device.Generation > 0 {
		return result.Device.Generation
	}
	return 1
}

func resolveSellerShopID(result *purchase_validation.PurchaseValidationResult) uint {
	if result.Card != nil && result.Card.ShopID != nil {
		return *result.Card.ShopID
	}
	if result.Device != nil && result.Device.ShopID != nil {
		return *result.Device.ShopID
	}
	return 0
}

func extractPackageIDs(packages []*model.Package) []uint {
	ids := make([]uint, 0, len(packages))
	for _, pkg := range packages {
		if pkg == nil {
			continue
		}
		ids = append(ids, pkg.ID)
	}
	return ids
}

func buildClientPurchaseBusinessKey(customerID uint, assetInfo *dto.AssetResolveResponse, packageIDs []uint, paymentMethod string) string {
	sorted := make([]uint, len(packageIDs))
	copy(sorted, packageIDs)
	slices.Sort(sorted)

	parts := make([]string, 0, len(sorted))
	for _, id := range sorted {
		parts = append(parts, strconv.FormatUint(uint64(id), 10))
	}

	return fmt.Sprintf("%d:%s:%d:%s:%s", customerID, assetInfo.AssetType, assetInfo.AssetID, strings.Join(parts, ","), paymentMethod)
}

func rechargeStatusToClientStatus(status int) int {
	// DB 使用 0-based 常量（0=待支付, 1=已支付, 2=已关闭, 3=已退款）
	// 对外透出 1-based（与 spec 约定一致）：1=待支付, 2=已支付, 3=已关闭, 4=已退款
	switch status {
	case model.RechargeOrderStatusPending:
		return 1
	case model.RechargeOrderStatusPaid:
		return 2
	case model.RechargeOrderStatusClosed:
		return 3
	default:
		return 4
	}
}

func clientRechargeStatusName(clientStatus int) string {
	switch clientStatus {
	case 1:
		return "待支付"
	case 2:
		return "已支付"
	case 3:
		return "已关闭"
	default:
		return "已退款"
	}
}

func formatClientServiceTime(t time.Time) string {
	if t.IsZero() {
		return ""
	}
	return t.Format(time.RFC3339)
}

func generateClientRechargeNo() string {
	return fmt.Sprintf("CRCH%d%06d", time.Now().UnixNano()/1e6, rand.Intn(1000000))
}

func generateClientPaymentNo() string {
	return fmt.Sprintf("PAY%d%06d", time.Now().UnixNano()/1e6, rand.Intn(1000000))
}

// getOrBuildAlipayPaymentLink 查找未过期的支付宝待支付单并返回链接；
// 过期则标记 failed 并创建新单；新建失败则整体返回错误。
func (s *Service) getOrBuildAlipayPaymentLink(
	ctx context.Context,
	orderID uint,
	orderType string,
	amount int64,
	subject string,
) (*dto.ClientPaymentLink, error) {
	activeConfig, err := s.wechatConfigService.GetActiveConfig(ctx)
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询支付配置失败")
	}
	if activeConfig == nil {
		return nil, errors.New(errors.CodeInvalidParam, "未找到生效的支付配置")
	}

	// 查找最新的 alipay 待支付单
	existing, _ := s.paymentStore.FindLatestPendingByOrderAndMethod(
		ctx, orderID, orderType, model.PaymentByAlipay,
	)

	var payment *model.Payment
	now := time.Now()
	if existing != nil && existing.ExpireAt != nil && existing.ExpireAt.After(now) {
		// 复用未过期的支付单
		payment = existing
	} else {
		// 过期或不存在，标记旧单 failed 并新建
		if existing != nil {
			if updateErr := s.paymentStore.UpdateStatus(ctx, existing.ID, model.PaymentRecordStatusFailed); updateErr != nil {
				s.logger.Warn("标记过期支付宝支付单 failed 失败",
					zap.Uint("payment_id", existing.ID),
					zap.Error(updateErr),
				)
			}
		}
		expireMinutes := activeConfig.AliPayExpireMinutes
		if expireMinutes <= 0 {
			expireMinutes = 30
		}
		expireAt := time.Now().Add(time.Duration(expireMinutes) * time.Minute)
		newPayment := &model.Payment{
			PaymentNo:       generateClientPaymentNo(),
			OrderID:         orderID,
			OrderType:       orderType,
			PaymentMethod:   model.PaymentByAlipay,
			Amount:          amount,
			Status:          model.PaymentRecordStatusPending,
			PaymentConfigID: &activeConfig.ID,
			ExpireAt:        &expireAt,
		}
		if err := s.paymentStore.Create(ctx, newPayment); err != nil {
			return nil, errors.Wrap(errors.CodeDatabaseError, err, "创建支付宝支付单失败")
		}
		payment = newPayment
		s.logger.Info("创建支付宝支付单",
			zap.String("payment_no", payment.PaymentNo),
			zap.String("order_type", orderType),
			zap.Uint("order_id", orderID),
			zap.Int64("amount", amount),
			zap.Time("expire_at", expireAt),
			zap.Uint("config_id", activeConfig.ID),
		)
	}

	wapURL, err := alipay.BuildWapPayURL(ctx, activeConfig, payment, subject)
	if err != nil {
		// 新建的 payment 生成链接失败，标记 failed
		if existing == nil || payment.ID != existing.ID {
			_ = s.paymentStore.UpdateStatus(ctx, payment.ID, model.PaymentRecordStatusFailed)
		}
		return nil, err
	}

	expireStr := ""
	if payment.ExpireAt != nil {
		expireStr = payment.ExpireAt.Format(time.RFC3339)
	}
	return &dto.ClientPaymentLink{
		PaymentNo:   payment.PaymentNo,
		QRLink:      wapURL,
		CopyLink:    wapURL,
		PayExpireAt: expireStr,
	}, nil
}

// createAlipayForceRechargeOrder 强充场景下创建支付宝充值单并返回支付链接。
func (s *Service) createAlipayForceRechargeOrder(
	ctx context.Context,
	customerID uint,
	assetInfo *dto.AssetResolveResponse,
	validationResult *purchase_validation.PurchaseValidationResult,
	activeConfig *model.WechatConfig,
	forceRecharge *ForceRechargeRequirement,
	redisKey string,
	created *bool,
) (*dto.ClientCreateOrderResponse, error) {
	resourceType, resourceID, err := resolveWalletResource(validationResult)
	if err != nil {
		return nil, err
	}

	wallet, err := s.getOrCreateWallet(ctx, resourceType, resourceID, resolveSellerShopID(validationResult))
	if err != nil {
		return nil, err
	}

	linkedPackageIDs, err := sonic.Marshal(extractPackageIDs(validationResult.Packages))
	if err != nil {
		return nil, errors.Wrap(errors.CodeInternalError, err, "序列化关联套餐失败")
	}

	rechargeOrderNo := generateClientRechargeNo()
	var iotCardID *uint
	var deviceID *uint
	if validationResult.Card != nil {
		iotCardID = &validationResult.Card.ID
	}
	if validationResult.Device != nil {
		deviceID = &validationResult.Device.ID
	}

	rechargeOrder := &model.RechargeOrder{
		RechargeOrderNo:    rechargeOrderNo,
		UserID:             customerID,
		AssetWalletID:      wallet.ID,
		ResourceType:       resourceType,
		ResourceID:         resourceID,
		IotCardID:          iotCardID,
		DeviceID:           deviceID,
		Amount:             forceRecharge.ForceRechargeAmount,
		Status:             model.RechargeOrderStatusPending,
		ShopIDTag:          wallet.ShopIDTag,
		EnterpriseIDTag:    wallet.EnterpriseIDTag,
		OperatorType:       "personal_customer",
		Generation:         resolveGeneration(validationResult),
		LinkedPackageIDs:   datatypes.JSON(linkedPackageIDs),
		LinkedOrderType:    resolveOrderType(validationResult),
		LinkedCarrierType:  assetInfo.AssetType,
		LinkedCarrierID:    &resourceID,
		AutoPurchaseStatus: model.AutoPurchaseStatusPending,
		PaymentConfigID:    &activeConfig.ID,
	}

	expireMinutesForce := activeConfig.AliPayExpireMinutes
	if expireMinutesForce <= 0 {
		expireMinutesForce = 30
	}
	expireAt := time.Now().Add(time.Duration(expireMinutesForce) * time.Minute)
	paymentNo := generateClientPaymentNo()
	payment := &model.Payment{
		PaymentNo:       paymentNo,
		OrderType:       model.PaymentOrderTypeRecharge,
		PaymentMethod:   model.PaymentByAlipay,
		Amount:          forceRecharge.ForceRechargeAmount,
		Status:          model.PaymentRecordStatusPending,
		PaymentConfigID: &activeConfig.ID,
		ExpireAt:        &expireAt,
	}

	// 事务创建充值单 + 支付单（WAP URL 本地签名，不需要先调第三方）
	if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := s.rechargeOrderStore.CreateWithTx(ctx, tx, rechargeOrder); err != nil {
			return errors.Wrap(errors.CodeDatabaseError, err, "创建充值订单失败")
		}
		payment.OrderID = rechargeOrder.ID
		return s.paymentStore.CreateWithTx(ctx, tx, payment)
	}); err != nil {
		return nil, err
	}

	wapURL, err := alipay.BuildWapPayURL(ctx, activeConfig, payment, "余额充值")
	if err != nil {
		if updateErr := s.paymentStore.UpdateStatus(ctx, payment.ID, model.PaymentRecordStatusFailed); updateErr != nil {
			s.logger.Warn("标记支付宝支付单 failed 失败",
				zap.String("payment_no", paymentNo),
				zap.Error(updateErr),
			)
		}
		return nil, err
	}

	s.logger.Info("支付宝强充支付单已创建",
		zap.String("payment_no", paymentNo),
		zap.String("recharge_no", rechargeOrderNo),
		zap.Int64("amount", forceRecharge.ForceRechargeAmount),
		zap.Time("expire_at", expireAt),
		zap.Uint("config_id", activeConfig.ID),
	)

	s.markClientPurchaseCreated(ctx, redisKey, rechargeOrderNo)
	*created = true

	clientStatus := rechargeStatusToClientStatus(int(rechargeOrder.Status))
	expireStr := expireAt.Format(time.RFC3339)
	return &dto.ClientCreateOrderResponse{
		OrderType: "recharge",
		Recharge: &dto.ClientRechargeInfo{
			RechargeID:         rechargeOrder.ID,
			RechargeNo:         rechargeOrderNo,
			Amount:             rechargeOrder.Amount,
			Status:             clientStatus,
			StatusName:         clientRechargeStatusName(clientStatus),
			AutoPurchaseStatus: rechargeOrder.AutoPurchaseStatus,
		},
		PaymentLink: &dto.ClientPaymentLink{
			PaymentNo:   paymentNo,
			QRLink:      wapURL,
			CopyLink:    wapURL,
			PayExpireAt: expireStr,
		},
		LinkedPackageInfo: buildLinkedPackageInfo(validationResult, forceRecharge),
	}, nil
}

// ListOrders 查询 C 端订单列表。
func (s *Service) ListOrders(ctx context.Context, customerID uint, req *dto.ClientOrderListRequest) ([]dto.ClientOrderListItem, int64, error) {
	skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{})

	assetInfo, err := s.assetService.Resolve(skipCtx, strings.TrimSpace(req.Identifier), false)
	if err != nil {
		return nil, 0, err
	}

	if owned, err := s.customerBinding.OwnsAsset(skipCtx, customerID, assetInfo.AssetType, assetInfo.AssetID); err != nil {
		return nil, 0, errors.Wrap(errors.CodeDatabaseError, err, "查询资产归属失败")
	} else if !owned {
		return nil, 0, errors.New(errors.CodeForbidden, "无权限操作该资产或资源不存在")
	}

	generation, err := s.getAssetGeneration(skipCtx, assetInfo.AssetType, assetInfo.AssetID)
	if err != nil {
		return nil, 0, err
	}

	query := s.db.WithContext(skipCtx).
		Model(&model.Order{}).
		Where("generation = ? AND buyer_type = ? AND buyer_id = ?", generation, model.BuyerTypePersonal, customerID)

	if assetInfo.AssetType == constants.ResourceTypeDevice {
		query = query.Where("order_type = ? AND device_id = ?", model.OrderTypeDevice, assetInfo.AssetID)
	} else {
		query = query.Where("order_type = ? AND iot_card_id = ?", model.OrderTypeSingleCard, assetInfo.AssetID)
	}

	if req.PaymentStatus != nil {
		query = query.Where("payment_status = ?", *req.PaymentStatus)
	}

	var total int64
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, errors.Wrap(errors.CodeDatabaseError, err, "查询订单总数失败")
	}

	var orders []*model.Order
	offset := (req.Page - 1) * req.PageSize
	if err := query.Order("created_at DESC").Offset(offset).Limit(req.PageSize).Find(&orders).Error; err != nil {
		return nil, 0, errors.Wrap(errors.CodeDatabaseError, err, "查询订单列表失败")
	}

	orderIDs := make([]uint, 0, len(orders))
	for _, order := range orders {
		if order != nil {
			orderIDs = append(orderIDs, order.ID)
		}
	}

	itemMap, err := s.loadOrderItemMap(skipCtx, orderIDs)
	if err != nil {
		return nil, 0, err
	}

	list := make([]dto.ClientOrderListItem, 0, len(orders))
	for _, order := range orders {
		if order == nil {
			continue
		}

		packageNames := make([]string, 0)
		packageIDs := make([]uint, 0)
		for _, item := range itemMap[order.ID] {
			if item != nil {
				packageIDs = append(packageIDs, item.PackageID)
				if item.PackageName != "" {
					packageNames = append(packageNames, item.PackageName)
				}
			}
		}
		assetType, assetID := clientOrderAssetReference(order)

		list = append(list, dto.ClientOrderListItem{
			OrderID:           order.ID,
			OrderNo:           order.OrderNo,
			AssetType:         assetType,
			AssetID:           assetID,
			AssetIdentifier:   order.AssetIdentifier,
			TotalAmount:       order.TotalAmount,
			PaymentMethod:     order.PaymentMethod,
			PaymentStatus:     order.PaymentStatus,
			PaymentStatusName: constants.GetOrderPaymentStatusName(order.PaymentStatus),
			CreatedAt:         formatClientServiceTime(order.CreatedAt),
			PackageIDs:        packageIDs,
			PackageNames:      packageNames,
		})
	}

	return list, total, nil
}

// GetOrderDetail 查询 C 端订单详情。
func (s *Service) GetOrderDetail(ctx context.Context, customerID uint, orderID uint) (*dto.ClientOrderDetailResponse, error) {
	order, items, err := s.orderStore.GetByIDWithItems(ctx, orderID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeNotFound, "订单不存在")
		}
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询订单详情失败")
	}
	if order.BuyerType != model.BuyerTypePersonal || order.BuyerID != customerID {
		return nil, errors.New(errors.CodeForbidden, "无权查看此订单")
	}

	skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{})
	orderAssetType, orderAssetID, err := getOrderAssetInfo(order)
	if err != nil {
		return nil, err
	}
	if owned, ownErr := s.customerBinding.OwnsAsset(skipCtx, customerID, orderAssetType, orderAssetID); ownErr != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, ownErr, "查询资产归属失败")
	} else if !owned {
		return nil, errors.New(errors.CodeForbidden, "无权限操作该资产或资源不存在")
	}

	packages := make([]dto.ClientOrderPackageItem, 0, len(items))
	for _, item := range items {
		if item == nil {
			continue
		}
		packages = append(packages, dto.ClientOrderPackageItem{
			PackageID:   item.PackageID,
			PackageName: item.PackageName,
			Price:       item.UnitPrice,
			Quantity:    item.Quantity,
		})
	}
	assetType, assetID := clientOrderAssetReference(order)

	return &dto.ClientOrderDetailResponse{
		OrderID:           order.ID,
		OrderNo:           order.OrderNo,
		AssetType:         assetType,
		AssetID:           assetID,
		AssetIdentifier:   order.AssetIdentifier,
		TotalAmount:       order.TotalAmount,
		PaymentStatus:     order.PaymentStatus,
		PaymentStatusName: constants.GetOrderPaymentStatusName(order.PaymentStatus),
		PaymentMethod:     order.PaymentMethod,
		CreatedAt:         formatClientServiceTime(order.CreatedAt),
		PaidAt:            formatClientServiceTimePtr(order.PaidAt),
		CompletedAt:       nil,
		Packages:          packages,
	}, nil
}

func clientOrderAssetReference(order *model.Order) (string, uint) {
	if order == nil {
		return "", 0
	}
	if order.OrderType == model.OrderTypeDevice && order.DeviceID != nil {
		return constants.ResourceTypeDevice, *order.DeviceID
	}
	if order.OrderType == model.OrderTypeSingleCard && order.IotCardID != nil {
		return "card", *order.IotCardID
	}
	return "", 0
}

// PayOrder D4 对待支付的 C 端订单发起支付。
// 支持 wallet（钱包扣款）和 wechat（微信 JSAPI）两种支付方式。
// 先校验订单归属和状态，再按 payment_method 路由到对应支付逻辑：
// - wallet：委托 B 端 WalletPay（乐观锁扣款 + 激活套餐 + 佣金入队）
// - wechat：查微信配置 + OpenID → 预下单 → 更新 payment_config_id → 返回 JS 支付参数
func (s *Service) PayOrder(ctx context.Context, customerID uint, orderID uint, req *dto.ClientPayOrderRequest) (*dto.ClientPayOrderResponse, error) {
	if req == nil {
		return nil, errors.New(errors.CodeInvalidParam)
	}

	// 跳过 GORM 店铺过滤，C 端订单查询不依赖 seller_shop_id 过滤
	skipCtx := context.WithValue(ctx, constants.ContextKeySubordinateShopIDs, []uint{})

	order, err := s.orderStore.GetByID(skipCtx, orderID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, errors.New(errors.CodeNotFound, "订单不存在")
		}
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询订单失败")
	}

	if order.BuyerType != model.BuyerTypePersonal || order.BuyerID != customerID {
		return nil, errors.New(errors.CodeForbidden, "无权操作此订单")
	}

	if order.PaymentStatus != model.PaymentStatusPending {
		return nil, errors.New(errors.CodeInvalidStatus, "订单状态不允许支付")
	}
	paymentMethod := order.PaymentMethod
	if paymentMethod == "" {
		return nil, errors.New(errors.CodePaymentMethodUnavailable, "历史订单缺少支付方式，请重新下单")
	}
	if req.PaymentMethod != "" && req.PaymentMethod != paymentMethod {
		return nil, errors.New(errors.CodeConflict, "支付方式与订单创建时选择不一致")
	}
	if s.paymentMethodPolicy == nil {
		return nil, errors.New(errors.CodeNoPaymentConfig)
	}
	if err := s.paymentMethodPolicy.EnsureAllowed(skipCtx, order.OrderType, paymentMethod); err != nil {
		return nil, err
	}

	// before_order 策略：必须先实名才能支付订单
	var assetRealnamePolicy string
	var assetRealnameStatus int
	if order.OrderType == model.OrderTypeSingleCard && order.IotCardID != nil {
		card, err := s.iotCardStore.GetByID(skipCtx, *order.IotCardID)
		if err == nil && card != nil {
			assetRealnamePolicy = card.RealnamePolicy
			assetRealnameStatus = card.RealNameStatus
		}
	} else if order.OrderType == model.OrderTypeDevice && order.DeviceID != nil {
		device, err := s.deviceStore.GetByID(skipCtx, *order.DeviceID)
		if err == nil && device != nil {
			assetRealnamePolicy = device.RealnamePolicy
			// 设备的 RealNameStatus 需要通过 Resolve 获取（从当前卡派生）
			resolved, resolveErr := s.assetService.Resolve(skipCtx, device.VirtualNo, false)
			if resolveErr == nil && resolved != nil {
				assetRealnameStatus = resolved.RealNameStatus
			}
		}
	}
	if assetRealnamePolicy == constants.RealnamePolicyBeforeOrder && assetRealnameStatus != 1 {
		return nil, errors.New(errors.CodeNeedRealname)
	}

	// 第三方支付方式与资产类型必须匹配：单卡只允许支付宝，设备只允许微信（已暂时注释）
	// if req.PaymentMethod == model.PaymentMethodWechat || req.PaymentMethod == model.PaymentMethodAlipay {
	// 	switch order.OrderType {
	// 	case model.OrderTypeSingleCard:
	// 		if req.PaymentMethod != model.PaymentMethodAlipay {
	// 			return nil, errors.New(errors.CodeInvalidParam, "单卡资产只支持支付宝支付")
	// 		}
	// 	case model.OrderTypeDevice:
	// 		if req.PaymentMethod != model.PaymentMethodWechat {
	// 			return nil, errors.New(errors.CodeInvalidParam, "设备资产只支持微信支付")
	// 		}
	// 	}
	// }

	switch paymentMethod {
	case model.PaymentMethodWallet:
		if err := s.orderPaymentService.WalletPay(skipCtx, orderID, model.BuyerTypePersonal, customerID); err != nil {
			return nil, err
		}
		return &dto.ClientPayOrderResponse{PaymentMethod: paymentMethod}, nil

	case model.PaymentMethodWechat:
		activeConfig, appID, err := s.resolveWechatConfig(skipCtx, req.AppType)
		if err != nil {
			return nil, err
		}
		openID, err := s.resolveCustomerOpenID(skipCtx, customerID, appID)
		if err != nil {
			return nil, err
		}
		paymentProvider, err := s.newPaymentProvider(activeConfig, appID, req.AppType)
		if err != nil {
			return nil, err
		}

		paymentNo := generateClientPaymentNo()
		payment := &model.Payment{
			PaymentNo:       paymentNo,
			OrderID:         order.ID,
			OrderType:       model.PaymentOrderTypePackage,
			PaymentMethod:   model.PaymentByWechat,
			Amount:          order.TotalAmount,
			Status:          model.PaymentRecordStatusPending,
			PaymentConfigID: &activeConfig.ID,
		}

		if err := s.db.WithContext(skipCtx).Transaction(func(tx *gorm.DB) error {
			// 订单仍保留最近一次支付配置，兼容旧 ORD 回调。
			if err := tx.Model(&model.Order{}).
				Where("id = ?", orderID).
				Update("payment_config_id", activeConfig.ID).Error; err != nil {
				return errors.Wrap(errors.CodeDatabaseError, err, "更新订单支付配置失败")
			}
			if err := s.paymentStore.CreateWithTx(skipCtx, tx, payment); err != nil {
				return errors.Wrap(errors.CodeDatabaseError, err, "创建支付记录失败")
			}
			return nil
		}); err != nil {
			return nil, err
		}

		paymentResult, err := paymentProvider.CreateJSAPIPayment(skipCtx, paymentNo, "套餐购买", openID, int(order.TotalAmount))
		if err != nil {
			if updateErr := s.paymentStore.UpdateStatus(skipCtx, payment.ID, model.PaymentRecordStatusFailed); updateErr != nil {
				s.logger.Warn("标记支付记录失败状态失败",
					zap.Uint("payment_id", payment.ID),
					zap.String("payment_no", paymentNo),
					zap.Error(updateErr),
				)
			}
			return nil, err
		}
		return &dto.ClientPayOrderResponse{
			PaymentMethod: paymentMethod,
			PayConfig:     buildClientPayConfigFromResult(paymentResult),
		}, nil

	case model.PaymentMethodAlipay:
		// 支付宝支付：不需要 app_type / OpenID
		activeConfig, err := s.wechatConfigService.GetActiveConfig(skipCtx)
		if err != nil {
			return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询支付配置失败")
		}
		if activeConfig == nil {
			return nil, errors.New(errors.CodeInvalidParam, "未找到生效的支付配置")
		}

		paymentLink, err := s.getOrBuildAlipayPaymentLink(
			skipCtx, orderID, model.PaymentOrderTypePackage, order.TotalAmount, "套餐购买",
		)
		if err != nil {
			return nil, err
		}
		// 更新订单支付配置 ID，兼容回调配置回溯
		if dbErr := s.db.WithContext(skipCtx).Model(&model.Order{}).
			Where("id = ?", orderID).
			Update("payment_config_id", activeConfig.ID).Error; dbErr != nil {
			s.logger.Warn("更新订单支付配置 ID 失败",
				zap.Uint("order_id", orderID),
				zap.Error(dbErr),
			)
		}
		return &dto.ClientPayOrderResponse{
			PaymentMethod: paymentMethod,
			PaymentLink:   paymentLink,
		}, nil

	default:
		return nil, errors.New(errors.CodeInvalidParam, "不支持的支付方式")
	}
}

func (s *Service) getAssetGeneration(ctx context.Context, assetType string, assetID uint) (int, error) {
	switch assetType {
	case "card":
		card, err := s.iotCardStore.GetByID(ctx, assetID)
		if err != nil {
			if err == gorm.ErrRecordNotFound {
				return 0, errors.New(errors.CodeAssetNotFound)
			}
			return 0, errors.Wrap(errors.CodeDatabaseError, err, "查询卡信息失败")
		}
		return card.Generation, nil
	case constants.ResourceTypeDevice:
		device, err := s.deviceStore.GetByID(ctx, assetID)
		if err != nil {
			if err == gorm.ErrRecordNotFound {
				return 0, errors.New(errors.CodeAssetNotFound)
			}
			return 0, errors.Wrap(errors.CodeDatabaseError, err, "查询设备信息失败")
		}
		return device.Generation, nil
	default:
		return 0, errors.New(errors.CodeInvalidParam)
	}
}

func (s *Service) loadOrderItemMap(ctx context.Context, orderIDs []uint) (map[uint][]*model.OrderItem, error) {
	result := make(map[uint][]*model.OrderItem)
	if len(orderIDs) == 0 {
		return result, nil
	}

	var items []*model.OrderItem
	if err := s.db.WithContext(ctx).Where("order_id IN ?", orderIDs).Order("id ASC").Find(&items).Error; err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询订单明细失败")
	}

	for _, item := range items {
		if item != nil {
			result[item.OrderID] = append(result[item.OrderID], item)
		}
	}

	return result, nil
}

func (s *Service) getOrderVirtualNo(ctx context.Context, order *model.Order) (string, error) {
	if order == nil {
		return "", errors.New(errors.CodeNotFound, "订单不存在")
	}

	switch order.OrderType {
	case model.OrderTypeSingleCard:
		if order.IotCardID == nil || *order.IotCardID == 0 {
			return "", errors.New(errors.CodeInvalidParam)
		}
		card, err := s.iotCardStore.GetByID(ctx, *order.IotCardID)
		if err != nil {
			if err == gorm.ErrRecordNotFound {
				return "", errors.New(errors.CodeAssetNotFound)
			}
			return "", errors.Wrap(errors.CodeDatabaseError, err, "查询卡信息失败")
		}
		return card.VirtualNo, nil
	case model.OrderTypeDevice:
		if order.DeviceID == nil || *order.DeviceID == 0 {
			return "", errors.New(errors.CodeInvalidParam)
		}
		device, err := s.deviceStore.GetByID(ctx, *order.DeviceID)
		if err != nil {
			if err == gorm.ErrRecordNotFound {
				return "", errors.New(errors.CodeAssetNotFound)
			}
			return "", errors.Wrap(errors.CodeDatabaseError, err, "查询设备信息失败")
		}
		return device.VirtualNo, nil
	default:
		return "", errors.New(errors.CodeInvalidParam)
	}
}

func formatClientServiceTimePtr(t *time.Time) *string {
	if t == nil || t.IsZero() {
		return nil
	}
	formatted := formatClientServiceTime(*t)
	return &formatted
}
