package app

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

	"github.com/break/junhong_cmp_fiber/internal/middleware"
	"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"
	rechargeSvc "github.com/break/junhong_cmp_fiber/internal/service/recharge"
	wechatConfigSvc "github.com/break/junhong_cmp_fiber/internal/service/wechat_config"
	"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/break/junhong_cmp_fiber/pkg/fuiou"
	"github.com/break/junhong_cmp_fiber/pkg/response"
	"github.com/break/junhong_cmp_fiber/pkg/wechat"
	"github.com/gofiber/fiber/v2"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

// ClientWalletHandler C 端钱包处理器
// 提供 C1~C5 钱包详情、流水、充值前校验、充值下单、充值记录接口
type ClientWalletHandler struct {
	assetService        *asset.Service
	customerBinding     *customerBinding.Service
	walletStore         *postgres.AssetWalletStore
	transactionStore    *postgres.AssetWalletTransactionStore
	rechargeOrderStore  *postgres.RechargeOrderStore
	paymentStore        *postgres.PaymentStore
	rechargeService     *rechargeSvc.Service
	openIDStore         *postgres.PersonalCustomerOpenIDStore
	wechatConfigService *wechatConfigSvc.Service
	redis               *redis.Client
	logger              *zap.Logger
	db                  *gorm.DB
	iotCardStore        *postgres.IotCardStore
	deviceStore         *postgres.DeviceStore
	paymentMethodPolicy ClientPaymentMethodPolicy
}

// SetPaymentMethodPolicy 注入 C 端支付方式策略。
func (h *ClientWalletHandler) SetPaymentMethodPolicy(policy ClientPaymentMethodPolicy) {
	h.paymentMethodPolicy = policy
}

// NewClientWalletHandler 创建 C 端钱包处理器
func NewClientWalletHandler(
	assetService *asset.Service,
	binding *customerBinding.Service,
	walletStore *postgres.AssetWalletStore,
	transactionStore *postgres.AssetWalletTransactionStore,
	rechargeOrderStore *postgres.RechargeOrderStore,
	paymentStore *postgres.PaymentStore,
	rechargeService *rechargeSvc.Service,
	openIDStore *postgres.PersonalCustomerOpenIDStore,
	wechatConfigService *wechatConfigSvc.Service,
	redisClient *redis.Client,
	logger *zap.Logger,
	db *gorm.DB,
	iotCardStore *postgres.IotCardStore,
	deviceStore *postgres.DeviceStore,
) *ClientWalletHandler {
	return &ClientWalletHandler{
		assetService:        assetService,
		customerBinding:     binding,
		walletStore:         walletStore,
		transactionStore:    transactionStore,
		rechargeOrderStore:  rechargeOrderStore,
		paymentStore:        paymentStore,
		rechargeService:     rechargeService,
		openIDStore:         openIDStore,
		wechatConfigService: wechatConfigService,
		redis:               redisClient,
		logger:              logger,
		db:                  db,
		iotCardStore:        iotCardStore,
		deviceStore:         deviceStore,
	}
}

type resolvedWalletAssetContext struct {
	CustomerID        uint
	Identifier        string
	Asset             *dto.AssetResolveResponse
	Generation        int
	ResourceType      string
	SkipPermissionCtx context.Context
}

// GetWalletDetail C1 钱包详情
// GET /api/c/v1/wallet/detail
func (h *ClientWalletHandler) GetWalletDetail(c *fiber.Ctx) error {
	var req dto.WalletDetailRequest
	if err := c.QueryParser(&req); err != nil {
		return errors.New(errors.CodeInvalidParam)
	}

	resolved, err := h.resolveAssetFromIdentifier(c, req.Identifier)
	if err != nil {
		return err
	}

	wallet, err := h.getOrCreateWallet(resolved)
	if err != nil {
		return err
	}

	resp := &dto.WalletDetailResponse{
		WalletID:      wallet.ID,
		ResourceType:  wallet.ResourceType,
		ResourceID:    wallet.ResourceID,
		Balance:       wallet.Balance,
		FrozenBalance: wallet.FrozenBalance,
		UpdatedAt:     wallet.UpdatedAt.Format(time.RFC3339),
	}

	return response.Success(c, resp)
}

// GetWalletTransactions C2 钱包流水列表
// GET /api/c/v1/wallet/transactions
func (h *ClientWalletHandler) GetWalletTransactions(c *fiber.Ctx) error {
	var req dto.WalletTransactionListRequest
	if err := c.QueryParser(&req); err != nil {
		return errors.New(errors.CodeInvalidParam)
	}

	if req.Page < 1 {
		req.Page = 1
	}
	if req.PageSize < 1 {
		req.PageSize = constants.DefaultPageSize
	}
	if req.PageSize > constants.MaxPageSize {
		req.PageSize = constants.MaxPageSize
	}

	resolved, err := h.resolveAssetFromIdentifier(c, req.Identifier)
	if err != nil {
		return err
	}

	wallet, err := h.walletStore.GetByResourceTypeAndID(resolved.SkipPermissionCtx, resolved.ResourceType, resolved.Asset.AssetID)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return response.SuccessWithPagination(c, []dto.WalletTransactionItem{}, 0, req.Page, req.PageSize)
		}
		return errors.Wrap(errors.CodeDatabaseError, err, "查询钱包失败")
	}

	var txType *string
	if strings.TrimSpace(req.TransactionType) != "" {
		v := strings.TrimSpace(req.TransactionType)
		txType = &v
	}

	startTime, err := parseOptionalTime(req.StartTime)
	if err != nil {
		return errors.New(errors.CodeInvalidParam)
	}
	endTime, err := parseOptionalTime(req.EndTime)
	if err != nil {
		return errors.New(errors.CodeInvalidParam)
	}
	if startTime != nil && endTime != nil && endTime.Before(*startTime) {
		return errors.New(errors.CodeInvalidParam)
	}

	offset := (req.Page - 1) * req.PageSize
	list, err := h.transactionStore.ListByResourceIDWithFilter(
		resolved.SkipPermissionCtx,
		wallet.ResourceType,
		wallet.ResourceID,
		txType,
		startTime,
		endTime,
		offset,
		req.PageSize,
	)
	if err != nil {
		return errors.Wrap(errors.CodeDatabaseError, err, "查询钱包流水失败")
	}

	total, err := h.transactionStore.CountByResourceIDWithFilter(
		resolved.SkipPermissionCtx,
		wallet.ResourceType,
		wallet.ResourceID,
		txType,
		startTime,
		endTime,
	)
	if err != nil {
		return errors.Wrap(errors.CodeDatabaseError, err, "查询钱包流水总数失败")
	}

	items := make([]dto.WalletTransactionItem, 0, len(list))
	for _, tx := range list {
		if tx == nil {
			continue
		}

		remark := ""
		if tx.Remark != nil {
			remark = *tx.Remark
		}

		items = append(items, dto.WalletTransactionItem{
			TransactionID: tx.ID,
			Type:          tx.TransactionType,
			Amount:        tx.Amount,
			BalanceAfter:  tx.BalanceAfter,
			CreatedAt:     tx.CreatedAt.Format(time.RFC3339),
			Remark:        remark,
		})
	}

	return response.SuccessWithPagination(c, items, total, req.Page, req.PageSize)
}

// GetRechargeCheck C3 充值前校验
// GET /api/c/v1/wallet/recharge-check
func (h *ClientWalletHandler) GetRechargeCheck(c *fiber.Ctx) error {
	var req dto.ClientRechargeCheckRequest
	if err := c.QueryParser(&req); err != nil {
		return errors.New(errors.CodeInvalidParam)
	}

	resolved, err := h.resolveAssetFromIdentifier(c, req.Identifier)
	if err != nil {
		return err
	}

	// before_order 策略：必须先实名才能充值
	if resolved.Asset.RealnamePolicy == constants.RealnamePolicyBeforeOrder && resolved.Asset.RealNameStatus != 1 {
		return errors.New(errors.CodeNeedRealname)
	}

	check, err := h.rechargeService.GetRechargeCheck(resolved.SkipPermissionCtx, resolved.ResourceType, resolved.Asset.AssetID)
	if err != nil {
		return err
	}
	if h.paymentMethodPolicy == nil {
		return errors.New(errors.CodeNoPaymentConfig)
	}
	allowedPaymentMethods, err := h.paymentMethodPolicy.AllowedRechargeMethods(resolved.SkipPermissionCtx, resolved.Asset.AssetType)
	if err != nil {
		return err
	}

	resp := &dto.ClientRechargeCheckResponse{
		NeedForceRecharge:     check.NeedForceRecharge,
		ForceRechargeAmount:   check.ForceRechargeAmount,
		TriggerType:           check.TriggerType,
		MinAmount:             check.MinAmount,
		MaxAmount:             check.MaxAmount,
		Message:               check.Message,
		AllowedPaymentMethods: allowedPaymentMethods,
	}

	return response.Success(c, resp)
}

// CreateRecharge C4 创建充值订单
// POST /api/c/v1/wallet/recharge
func (h *ClientWalletHandler) CreateRecharge(c *fiber.Ctx) error {
	var req dto.ClientCreateRechargeRequest
	if err := c.BodyParser(&req); err != nil {
		return errors.New(errors.CodeInvalidParam)
	}

	resolved, err := h.resolveAssetFromIdentifier(c, req.Identifier)
	if err != nil {
		return err
	}

	// before_order 策略：必须先实名才能充值
	if resolved.Asset.RealnamePolicy == constants.RealnamePolicyBeforeOrder && resolved.Asset.RealNameStatus != 1 {
		return errors.New(errors.CodeNeedRealname)
	}
	if h.paymentMethodPolicy == nil {
		return errors.New(errors.CodeNoPaymentConfig)
	}
	if err := h.paymentMethodPolicy.EnsureRechargeAllowed(resolved.SkipPermissionCtx, resolved.Asset.AssetType, req.PaymentMethod); err != nil {
		return err
	}

	wallet, err := h.getOrCreateWallet(resolved)
	if err != nil {
		return err
	}

	// 第三方支付方式与资产类型必须匹配：单卡只允许支付宝，设备只允许微信（已暂时注释）
	// switch resolved.Asset.AssetType {
	// case "card":
	// 	if req.PaymentMethod != constants.RechargeMethodAlipay {
	// 		return errors.New(errors.CodeInvalidParam, "单卡资产只支持支付宝充值")
	// 	}
	// case "device":
	// 	if req.PaymentMethod != constants.RechargeMethodWechat {
	// 		return errors.New(errors.CodeInvalidParam, "设备资产只支持微信充值")
	// 	}
	// }

	config, err := h.wechatConfigService.GetActiveConfig(resolved.SkipPermissionCtx)
	if err != nil {
		return err
	}
	if config == nil {
		return errors.New(errors.CodeWechatConfigUnavailable)
	}

	switch req.PaymentMethod {
	case constants.RechargeMethodAlipay:
		return h.createAlipayRecharge(c, resolved, config, wallet, req)
	case constants.RechargeMethodWechat:
		return h.createWechatRecharge(c, resolved, config, wallet, req)
	default:
		return errors.New(errors.CodePaymentMethodUnavailable)
	}
}

// createWechatRecharge 微信充值分支
func (h *ClientWalletHandler) createWechatRecharge(
	c *fiber.Ctx,
	resolved *resolvedWalletAssetContext,
	config *model.WechatConfig,
	wallet *model.AssetWallet,
	req dto.ClientCreateRechargeRequest,
) error {
	appID, err := pickAppIDByType(config, req.AppType)
	if err != nil {
		return err
	}

	openID, err := h.findOpenIDByCustomerAndAppID(resolved.SkipPermissionCtx, resolved.CustomerID, appID)
	if err != nil {
		return err
	}

	rechargeNo := generateClientRechargeNo()
	paymentNo := generateClientPaymentNo()

	// 先初始化生效支付通道并创建预支付订单，确认支付通道可用
	// 避免先写入充值记录后支付初始化失败，导致产生孤儿记录
	payConfig, err := h.createClientRechargePayConfig(
		resolved.SkipPermissionCtx,
		config,
		appID,
		req.AppType,
		paymentNo,
		"资产钱包充值",
		openID,
		int(req.Amount),
	)
	if err != nil {
		return err
	}

	// 支付通道确认可用后，再创建充值订单和支付记录
	rechargeOrder := &model.RechargeOrder{
		RechargeOrderNo: rechargeNo,
		UserID:          resolved.CustomerID,
		AssetWalletID:   wallet.ID,
		ResourceType:    resolved.ResourceType,
		ResourceID:      resolved.Asset.AssetID,
		Amount:          req.Amount,
		Status:          model.RechargeOrderStatusPending,
		PaymentConfigID: &config.ID,
		ShopIDTag:       wallet.ShopIDTag,
		EnterpriseIDTag: wallet.EnterpriseIDTag,
		OperatorType:    constants.OperatorTypePersonalCustomer,
		Generation:      resolved.Generation,
	}
	if err := h.rechargeOrderStore.Create(resolved.SkipPermissionCtx, rechargeOrder); err != nil {
		return errors.Wrap(errors.CodeDatabaseError, err, "创建充值订单失败")
	}

	payment := &model.Payment{
		PaymentNo:       paymentNo,
		OrderID:         rechargeOrder.ID,
		OrderType:       model.PaymentOrderTypeRecharge,
		PaymentMethod:   model.PaymentByWechat,
		Amount:          req.Amount,
		Status:          model.PaymentRecordStatusPending,
		PaymentConfigID: &config.ID,
	}
	if err := h.paymentStore.Create(resolved.SkipPermissionCtx, payment); err != nil {
		return errors.Wrap(errors.CodeDatabaseError, err, "创建支付记录失败")
	}

	return response.Success(c, &dto.ClientRechargeResponse{
		Recharge: dto.ClientRechargeResult{
			RechargeID: rechargeOrder.ID,
			RechargeNo: rechargeOrder.RechargeOrderNo,
			Amount:     rechargeOrder.Amount,
			Status:     rechargeOrder.Status,
		},
		PayConfig: &payConfig,
	})
}

// createAlipayRecharge 支付宝充值分支。
// WAP URL 为本地签名生成，事务内先写 DB，再生成链接；链接失败则标记 payment failed。
func (h *ClientWalletHandler) createAlipayRecharge(
	c *fiber.Ctx,
	resolved *resolvedWalletAssetContext,
	config *model.WechatConfig,
	wallet *model.AssetWallet,
	req dto.ClientCreateRechargeRequest,
) error {
	rechargeNo := generateClientRechargeNo()
	paymentNo := generateClientPaymentNo()
	expireMinutes := config.AliPayExpireMinutes
	if expireMinutes <= 0 {
		expireMinutes = 30
	}
	expireAt := time.Now().Add(time.Duration(expireMinutes) * time.Minute)

	rechargeOrder := &model.RechargeOrder{
		RechargeOrderNo: rechargeNo,
		UserID:          resolved.CustomerID,
		AssetWalletID:   wallet.ID,
		ResourceType:    resolved.ResourceType,
		ResourceID:      resolved.Asset.AssetID,
		Amount:          req.Amount,
		Status:          model.RechargeOrderStatusPending,
		PaymentConfigID: &config.ID,
		ShopIDTag:       wallet.ShopIDTag,
		EnterpriseIDTag: wallet.EnterpriseIDTag,
		OperatorType:    constants.OperatorTypePersonalCustomer,
		Generation:      resolved.Generation,
	}
	payment := &model.Payment{
		PaymentNo:       paymentNo,
		OrderType:       model.PaymentOrderTypeRecharge,
		PaymentMethod:   model.PaymentByAlipay,
		Amount:          req.Amount,
		Status:          model.PaymentRecordStatusPending,
		PaymentConfigID: &config.ID,
		ExpireAt:        &expireAt,
	}

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

	wapURL, err := alipay.BuildWapPayURL(resolved.SkipPermissionCtx, config, payment, "资产钱包充值")
	if err != nil {
		if updateErr := h.paymentStore.UpdateStatus(resolved.SkipPermissionCtx, payment.ID, model.PaymentRecordStatusFailed); updateErr != nil {
			h.logger.Warn("标记支付宝支付单 failed 失败",
				zap.String("payment_no", paymentNo),
				zap.Error(updateErr),
			)
		}
		return err
	}

	h.logger.Info("支付宝充值支付单已创建",
		zap.String("payment_no", paymentNo),
		zap.String("recharge_no", rechargeNo),
		zap.Int64("amount", req.Amount),
		zap.Time("expire_at", expireAt),
		zap.Uint("config_id", config.ID),
	)

	expireStr := expireAt.Format(time.RFC3339)
	return response.Success(c, &dto.ClientRechargeResponse{
		Recharge: dto.ClientRechargeResult{
			RechargeID: rechargeOrder.ID,
			RechargeNo: rechargeOrder.RechargeOrderNo,
			Amount:     rechargeOrder.Amount,
			Status:     rechargeOrder.Status,
		},
		PaymentLink: &dto.ClientPaymentLink{
			PaymentNo:   paymentNo,
			QRLink:      wapURL,
			CopyLink:    wapURL,
			PayExpireAt: expireStr,
		},
	})
}

// GetRechargeList C5 充值记录列表
// GET /api/c/v1/wallet/recharges
func (h *ClientWalletHandler) GetRechargeList(c *fiber.Ctx) error {
	var req dto.ClientRechargeListRequest
	if err := c.QueryParser(&req); err != nil {
		return errors.New(errors.CodeInvalidParam)
	}

	if req.Page < 1 {
		req.Page = 1
	}
	if req.PageSize < 1 {
		req.PageSize = constants.DefaultPageSize
	}
	if req.PageSize > constants.MaxPageSize {
		req.PageSize = constants.MaxPageSize
	}

	resolved, err := h.resolveAssetFromIdentifier(c, req.Identifier)
	if err != nil {
		return err
	}

	query := h.db.WithContext(resolved.SkipPermissionCtx).
		Model(&model.RechargeOrder{}).
		Where("resource_type = ? AND resource_id = ? AND generation = ?", resolved.ResourceType, resolved.Asset.AssetID, resolved.Generation)
	if req.Status != nil {
		query = query.Where("status = ?", *req.Status)
	}

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

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

	items := make([]dto.ClientRechargeListItem, 0, len(orders))
	for _, order := range orders {
		if order == nil {
			continue
		}
		items = append(items, dto.ClientRechargeListItem{
			RechargeID:         order.ID,
			RechargeNo:         order.RechargeOrderNo,
			Amount:             order.Amount,
			Status:             order.Status,
			PaymentMethod:      "", // 支付方式在 Payment 表中，列表查询不返回
			CreatedAt:          order.CreatedAt.Format(time.RFC3339),
			AutoPurchaseStatus: order.AutoPurchaseStatus,
		})
	}

	return response.SuccessWithPagination(c, items, total, req.Page, req.PageSize)
}

// resolveAssetFromIdentifier 统一执行资产解析与归属校验
func (h *ClientWalletHandler) resolveAssetFromIdentifier(c *fiber.Ctx, identifier string) (*resolvedWalletAssetContext, error) {
	customerID, ok := middleware.GetCustomerID(c)
	if !ok || customerID == 0 {
		return nil, errors.New(errors.CodeUnauthorized)
	}

	identifier = strings.TrimSpace(identifier)
	if identifier == "" {
		identifier = strings.TrimSpace(c.Query("identifier"))
	}
	if identifier == "" {
		return nil, errors.New(errors.CodeInvalidParam)
	}

	skipPermissionCtx := context.WithValue(c.UserContext(), constants.ContextKeySubordinateShopIDs, []uint{})
	assetInfo, err := h.assetService.Resolve(skipPermissionCtx, identifier, false)
	if err != nil {
		return nil, err
	}

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

	resourceType, mapErr := mapAssetTypeToWalletResource(assetInfo.AssetType)
	if mapErr != nil {
		return nil, mapErr
	}

	generation, genErr := h.getAssetGeneration(skipPermissionCtx, assetInfo.AssetType, assetInfo.AssetID)
	if genErr != nil {
		return nil, genErr
	}

	return &resolvedWalletAssetContext{
		CustomerID:        customerID,
		Identifier:        identifier,
		Asset:             assetInfo,
		Generation:        generation,
		ResourceType:      resourceType,
		SkipPermissionCtx: skipPermissionCtx,
	}, nil
}

func (h *ClientWalletHandler) getAssetGeneration(ctx context.Context, assetType string, assetID uint) (int, error) {
	switch assetType {
	case "card":
		card, err := h.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 "device":
		device, err := h.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 (h *ClientWalletHandler) getOrCreateWallet(resolved *resolvedWalletAssetContext) (*model.AssetWallet, error) {
	wallet, err := h.walletStore.GetByResourceTypeAndID(resolved.SkipPermissionCtx, resolved.ResourceType, resolved.Asset.AssetID)
	if err == nil {
		return wallet, nil
	}
	if err != gorm.ErrRecordNotFound {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询钱包失败")
	}

	shopIDTag := uint(0)
	if resolved.Asset.ShopID != nil {
		shopIDTag = *resolved.Asset.ShopID
	}

	newWallet := &model.AssetWallet{
		ResourceType:  resolved.ResourceType,
		ResourceID:    resolved.Asset.AssetID,
		Balance:       0,
		FrozenBalance: 0,
		Currency:      "CNY",
		Status:        constants.AssetWalletStatusNormal,
		Version:       0,
		ShopIDTag:     shopIDTag,
	}
	if createErr := h.walletStore.Create(resolved.SkipPermissionCtx, newWallet); createErr != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, createErr, "创建钱包失败")
	}

	wallet, err = h.walletStore.GetByResourceTypeAndID(resolved.SkipPermissionCtx, resolved.ResourceType, resolved.Asset.AssetID)
	if err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询钱包失败")
	}
	return wallet, nil
}

func (h *ClientWalletHandler) findOpenIDByCustomerAndAppID(ctx context.Context, customerID uint, appID string) (string, error) {
	list, err := h.openIDStore.ListByCustomerID(ctx, customerID)
	if err != nil {
		return "", errors.Wrap(errors.CodeDatabaseError, err, "查询微信授权信息失败")
	}
	for _, item := range list {
		if item == nil {
			continue
		}
		if item.AppID == appID && strings.TrimSpace(item.OpenID) != "" {
			return item.OpenID, nil
		}
	}
	return "", errors.New(errors.CodeOpenIDNotFound)
}

func mapAssetTypeToWalletResource(assetType string) (string, error) {
	switch assetType {
	case "card":
		return constants.AssetWalletResourceTypeIotCard, nil
	case "device":
		return constants.AssetWalletResourceTypeDevice, nil
	default:
		return "", errors.New(errors.CodeInvalidParam)
	}
}

func parseOptionalTime(value string) (*time.Time, error) {
	v := strings.TrimSpace(value)
	if v == "" {
		return nil, nil
	}

	layouts := []string{time.RFC3339, "2006-01-02 15:04:05", "2006-01-02"}
	for _, layout := range layouts {
		t, err := time.Parse(layout, v)
		if err == nil {
			return &t, nil
		}
	}

	return nil, fmt.Errorf("invalid time format")
}

func pickAppIDByType(config *model.WechatConfig, appType string) (string, error) {
	switch appType {
	case "official_account":
		if strings.TrimSpace(config.OaAppID) == "" {
			return "", errors.New(errors.CodeWechatConfigUnavailable)
		}
		return config.OaAppID, nil
	case "miniapp":
		if strings.TrimSpace(config.MiniappAppID) == "" {
			return "", errors.New(errors.CodeWechatConfigUnavailable)
		}
		return config.MiniappAppID, nil
	default:
		return "", errors.New(errors.CodeInvalidParam)
	}
}

// createClientRechargePayConfig 根据当前生效支付配置创建充值预支付订单。
// provider_type 必须显式匹配，避免富友等渠道误落到微信直连。
func (h *ClientWalletHandler) createClientRechargePayConfig(
	ctx context.Context,
	config *model.WechatConfig,
	appID, appType, orderNo, description, openID string,
	amount int,
) (dto.ClientRechargePayConfig, error) {
	switch config.ProviderType {
	case model.ProviderTypeWechatV2:
		// 微信 v2：仅需 APIv2Key，无需证书序列号
		svc, err := wechat.NewPaymentV2ServiceFromConfig(config, appID, h.logger)
		if err != nil {
			return dto.ClientRechargePayConfig{}, errors.Wrap(errors.CodeWechatPayFailed, err, "初始化微信 v2 支付实例失败")
		}
		result, err := svc.CreateJSAPIOrder(ctx, orderNo, description, openID, amount)
		if err != nil {
			return dto.ClientRechargePayConfig{}, err
		}
		return buildClientRechargePayConfig(appID, result), nil
	case model.ProviderTypeWechat:
		// 微信 v3（PowerWeChat）：需要证书序列号和 API v3 Key
		cache := wechat.NewRedisCache(h.redis)
		paymentApp, err := wechat.NewPaymentAppFromConfig(config, appID, cache, h.logger)
		if err != nil {
			return dto.ClientRechargePayConfig{}, errors.Wrap(errors.CodeWechatPayFailed, err, "初始化微信支付实例失败")
		}
		result, err := wechat.NewPaymentService(paymentApp, h.logger).CreateJSAPIOrder(ctx, orderNo, description, openID, amount)
		if err != nil {
			return dto.ClientRechargePayConfig{}, err
		}
		return buildClientRechargePayConfig(appID, result), nil
	case model.ProviderTypeFuiou:
		return h.createFuiouRechargePayConfig(ctx, config, appID, appType, orderNo, description, openID, amount)
	default:
		return dto.ClientRechargePayConfig{}, errors.New(errors.CodeWechatConfigUnavailable, "不支持的支付渠道类型")
	}
}

// createFuiouRechargePayConfig 使用富友支付配置创建充值预支付订单。
func (h *ClientWalletHandler) createFuiouRechargePayConfig(
	ctx context.Context,
	config *model.WechatConfig,
	appID, appType, orderNo, description, openID string,
	amount int,
) (dto.ClientRechargePayConfig, error) {
	if strings.TrimSpace(config.FyInsCd) == "" ||
		strings.TrimSpace(config.FyMchntCd) == "" ||
		strings.TrimSpace(config.FyTermID) == "" ||
		strings.TrimSpace(config.FyPrivateKey) == "" ||
		strings.TrimSpace(config.FyPublicKey) == "" ||
		strings.TrimSpace(config.FyAPIURL) == "" ||
		strings.TrimSpace(config.FyNotifyURL) == "" ||
		strings.TrimSpace(appID) == "" {
		return dto.ClientRechargePayConfig{}, errors.New(errors.CodeFuiouPayFailed, "富友支付配置不完整")
	}

	client, err := fuiou.NewClient(
		config.FyInsCd,
		config.FyMchntCd,
		config.FyTermID,
		config.FyAPIURL,
		config.FyNotifyURL,
		config.FyPrivateKey,
		config.FyPublicKey,
		h.logger,
	)
	if err != nil {
		return dto.ClientRechargePayConfig{}, errors.Wrap(errors.CodeFuiouPayFailed, err, "创建富友支付客户端失败")
	}

	tradeType := "JSAPI"
	if appType == "miniapp" {
		tradeType = "LETPAY"
	}

	termIP := fuiou.GetServerIP()
	if ip, ok := ctx.Value(constants.ContextKeyIP).(string); ok && strings.TrimSpace(ip) != "" {
		termIP = ip
	}

	resp, err := client.WxPreCreate(orderNo, strconv.Itoa(amount), description, termIP, tradeType, appID, openID)
	if err != nil {
		h.logger.Error("富友充值预下单失败",
			zap.String("payment_no", orderNo),
			zap.String("trade_type", tradeType),
			zap.Error(err),
		)
		return dto.ClientRechargePayConfig{}, errors.Wrap(errors.CodeFuiouPayFailed, err, "富友预下单失败")
	}

	return dto.ClientRechargePayConfig{
		AppID:      resp.SdkAppid,
		Timestamp:  resp.SdkTimestamp,
		NonceStr:   resp.SdkNoncestr,
		PackageVal: resp.SdkPackage,
		SignType:   resp.SdkSigntype,
		PaySign:    resp.SdkPaysign,
	}, nil
}

func generateClientRechargeNo() string {
	timestamp := time.Now().Format("20060102150405")
	randomNum := rand.Intn(1000000)
	return fmt.Sprintf("%s%s%06d", constants.AssetRechargeOrderPrefix, timestamp, randomNum)
}

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

func buildClientRechargePayConfig(appID string, result *wechat.JSAPIPayResult) dto.ClientRechargePayConfig {
	resp := dto.ClientRechargePayConfig{AppID: appID}
	if result == nil || result.PayConfig == nil {
		return resp
	}

	if cfg, ok := result.PayConfig.(map[string]any); ok {
		resp.Timestamp = getStringFromAnyMap(cfg, "timeStamp", "timestamp")
		resp.NonceStr = getStringFromAnyMap(cfg, "nonceStr", "nonce_str")
		resp.PackageVal = getStringFromAnyMap(cfg, "package")
		resp.SignType = getStringFromAnyMap(cfg, "signType", "sign_type")
		resp.PaySign = getStringFromAnyMap(cfg, "paySign", "pay_sign")
		if appIDVal := getStringFromAnyMap(cfg, "appId", "app_id"); appIDVal != "" {
			resp.AppID = appIDVal
		}
		return resp
	}

	if cfg, ok := result.PayConfig.(map[string]string); ok {
		resp.Timestamp = cfg["timeStamp"]
		if resp.Timestamp == "" {
			resp.Timestamp = cfg["timestamp"]
		}
		resp.NonceStr = cfg["nonceStr"]
		if resp.NonceStr == "" {
			resp.NonceStr = cfg["nonce_str"]
		}
		resp.PackageVal = cfg["package"]
		resp.SignType = cfg["signType"]
		if resp.SignType == "" {
			resp.SignType = cfg["sign_type"]
		}
		resp.PaySign = cfg["paySign"]
		if resp.PaySign == "" {
			resp.PaySign = cfg["pay_sign"]
		}
		if cfg["appId"] != "" {
			resp.AppID = cfg["appId"]
		} else if cfg["app_id"] != "" {
			resp.AppID = cfg["app_id"]
		}
	}

	return resp
}

func getStringFromAnyMap(m map[string]any, keys ...string) string {
	for _, key := range keys {
		val, ok := m[key]
		if !ok || val == nil {
			continue
		}
		switch v := val.(type) {
		case string:
			if v != "" {
				return v
			}
		case fmt.Stringer:
			text := v.String()
			if text != "" {
				return text
			}
		default:
			text := fmt.Sprintf("%v", v)
			if text != "" && text != "<nil>" {
				return text
			}
		}
	}
	return ""
}
