package grpc

import (
	"context"

	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"
	walletservice "hyapp/services/wallet-service/internal/service/wallet"
)

// Server 把 wallet-service 用例层适配为 gRPC 接口。
type Server struct {
	walletv1.UnimplementedWalletServiceServer

	svc *walletservice.Service
}

// NewServer 创建 wallet gRPC server。
func NewServer(svc *walletservice.Service) *Server {
	return &Server{svc: svc}
}

// DebitGift 只负责 protobuf 转换和错误映射，扣费规则在 service/repository。
func (s *Server) DebitGift(ctx context.Context, req *walletv1.DebitGiftRequest) (*walletv1.DebitGiftResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	receipt, err := s.svc.DebitGift(ctx, ledger.DebitGiftCommand{
		AppCode:      req.GetAppCode(),
		CommandID:    req.GetCommandId(),
		RoomID:       req.GetRoomId(),
		SenderUserID: req.GetSenderUserId(),
		TargetUserID: req.GetTargetUserId(),
		GiftID:       req.GetGiftId(),
		GiftCount:    req.GetGiftCount(),
		PriceVersion: req.GetPriceVersion(),
		RegionID:     req.GetRegionId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &walletv1.DebitGiftResponse{
		BillingReceiptId: receipt.BillingReceiptID,
		BalanceAfter:     receipt.BalanceAfter,
		TransactionId:    receipt.TransactionID,
		CoinSpent:        receipt.CoinSpent,
		ChargeAssetType:  receipt.ChargeAssetType,
		ChargeAmount:     receipt.ChargeAmount,
		GiftPointAdded:   receipt.GiftPointAdded,
		HeatValue:        receipt.HeatValue,
		PriceVersion:     receipt.PriceVersion,
	}, nil
}

// GetBalances 返回用户多资产余额投影。
func (s *Server) GetBalances(ctx context.Context, req *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	balances, err := s.svc.GetBalances(ctx, req.GetUserId(), req.GetAssetTypes())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	response := &walletv1.GetBalancesResponse{Balances: make([]*walletv1.AssetBalance, 0, len(balances))}
	for _, balance := range balances {
		response.Balances = append(response.Balances, &walletv1.AssetBalance{
			AppCode:         balance.AppCode,
			AssetType:       balance.AssetType,
			AvailableAmount: balance.AvailableAmount,
			FrozenAmount:    balance.FrozenAmount,
			Version:         balance.Version,
		})
	}

	return response, nil
}

// GetWalletOverview 返回钱包首页摘要，供我的页和钱包二级页复用。
func (s *Server) GetWalletOverview(ctx context.Context, req *walletv1.GetWalletOverviewRequest) (*walletv1.GetWalletOverviewResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	overview, err := s.svc.GetWalletOverview(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.GetWalletOverviewResponse{
		Balances:     balancesToProto(overview.Balances),
		FeatureFlags: walletFeatureFlagsToProto(overview.FeatureFlags),
	}, nil
}

// GetWalletValueSummary 返回我的页钱包卡片最小金币余额。
func (s *Server) GetWalletValueSummary(ctx context.Context, req *walletv1.GetWalletValueSummaryRequest) (*walletv1.GetWalletValueSummaryResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	summary, err := s.svc.GetWalletValueSummary(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.GetWalletValueSummaryResponse{Summary: &walletv1.WalletValueSummary{
		CoinAmount:  summary.CoinAmount,
		UpdatedAtMs: summary.UpdatedAtMS,
	}}, nil
}

// ListRechargeProducts 返回当前用户区域可用的充值渠道和档位。
func (s *Server) ListRechargeProducts(ctx context.Context, req *walletv1.ListRechargeProductsRequest) (*walletv1.ListRechargeProductsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	products, channels, err := s.svc.ListRechargeProducts(ctx, req.GetUserId(), req.GetRegionId(), req.GetPlatform())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &walletv1.ListRechargeProductsResponse{Channels: channels, Products: make([]*walletv1.RechargeProduct, 0, len(products))}
	for _, product := range products {
		resp.Products = append(resp.Products, rechargeProductToProto(product))
	}
	return resp, nil
}

// ListAdminRechargeProducts 返回后台内购配置列表。
func (s *Server) ListAdminRechargeProducts(ctx context.Context, req *walletv1.ListAdminRechargeProductsRequest) (*walletv1.ListAdminRechargeProductsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	products, total, err := s.svc.ListAdminRechargeProducts(ctx, ledger.ListRechargeProductsQuery{
		AppCode:  req.GetAppCode(),
		Status:   req.GetStatus(),
		Platform: req.GetPlatform(),
		RegionID: req.GetRegionId(),
		Keyword:  req.GetKeyword(),
		Page:     req.GetPage(),
		PageSize: req.GetPageSize(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &walletv1.ListAdminRechargeProductsResponse{Products: make([]*walletv1.RechargeProduct, 0, len(products)), Total: total}
	for _, product := range products {
		resp.Products = append(resp.Products, rechargeProductToProto(product))
	}
	return resp, nil
}

// CreateRechargeProduct 创建后台内购商品配置。
func (s *Server) CreateRechargeProduct(ctx context.Context, req *walletv1.CreateRechargeProductRequest) (*walletv1.RechargeProductResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	product, err := s.svc.CreateRechargeProduct(ctx, ledger.RechargeProductCommand{
		AppCode:        req.GetAppCode(),
		AmountMicro:    req.GetAmountMicro(),
		CoinAmount:     req.GetCoinAmount(),
		ProductName:    req.GetProductName(),
		Description:    req.GetDescription(),
		Platform:       req.GetPlatform(),
		RegionIDs:      req.GetRegionIds(),
		Enabled:        req.GetEnabled(),
		OperatorUserID: req.GetOperatorUserId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.RechargeProductResponse{Product: rechargeProductToProto(product)}, nil
}

// UpdateRechargeProduct 修改后台内购商品配置。
func (s *Server) UpdateRechargeProduct(ctx context.Context, req *walletv1.UpdateRechargeProductRequest) (*walletv1.RechargeProductResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	product, err := s.svc.UpdateRechargeProduct(ctx, ledger.RechargeProductCommand{
		AppCode:        req.GetAppCode(),
		ProductID:      req.GetProductId(),
		AmountMicro:    req.GetAmountMicro(),
		CoinAmount:     req.GetCoinAmount(),
		ProductName:    req.GetProductName(),
		Description:    req.GetDescription(),
		Platform:       req.GetPlatform(),
		RegionIDs:      req.GetRegionIds(),
		Enabled:        req.GetEnabled(),
		OperatorUserID: req.GetOperatorUserId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.RechargeProductResponse{Product: rechargeProductToProto(product)}, nil
}

// DeleteRechargeProduct 删除后台内购商品配置。
func (s *Server) DeleteRechargeProduct(ctx context.Context, req *walletv1.DeleteRechargeProductRequest) (*walletv1.DeleteRechargeProductResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	if err := s.svc.DeleteRechargeProduct(ctx, req.GetAppCode(), req.GetProductId()); err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.DeleteRechargeProductResponse{Deleted: true}, nil
}

// GetDiamondExchangeConfig 返回钻石兑换配置。
func (s *Server) GetDiamondExchangeConfig(ctx context.Context, req *walletv1.GetDiamondExchangeConfigRequest) (*walletv1.GetDiamondExchangeConfigResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	rules, err := s.svc.GetDiamondExchangeConfig(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &walletv1.GetDiamondExchangeConfigResponse{Rules: make([]*walletv1.DiamondExchangeRule, 0, len(rules))}
	for _, rule := range rules {
		resp.Rules = append(resp.Rules, &walletv1.DiamondExchangeRule{
			ExchangeType:  rule.ExchangeType,
			FromAssetType: rule.FromAssetType,
			ToAssetType:   rule.ToAssetType,
			FromAmount:    rule.FromAmount,
			ToAmount:      rule.ToAmount,
			Enabled:       rule.Enabled,
		})
	}
	return resp, nil
}

func rechargeProductToProto(product ledger.RechargeProduct) *walletv1.RechargeProduct {
	return &walletv1.RechargeProduct{
		AppCode:           product.AppCode,
		ProductId:         product.ProductID,
		ProductCode:       product.ProductCode,
		ProductName:       product.ProductName,
		Description:       product.Description,
		Platform:          product.Platform,
		Channel:           product.Channel,
		CurrencyCode:      product.CurrencyCode,
		AmountMicro:       product.AmountMicro,
		AmountMinor:       product.AmountMinor,
		CoinAmount:        product.CoinAmount,
		PolicyVersion:     product.PolicyVersion,
		Status:            product.Status,
		Enabled:           product.Enabled,
		SortOrder:         product.SortOrder,
		RegionIds:         product.RegionIDs,
		ResourceAssetType: product.ResourceAssetType,
		CreatedByUserId:   product.CreatedByUserID,
		UpdatedByUserId:   product.UpdatedByUserID,
		CreatedAtMs:       product.CreatedAtMS,
		UpdatedAtMs:       product.UpdatedAtMS,
	}
}

// ListWalletTransactions 返回当前用户钱包流水。
func (s *Server) ListWalletTransactions(ctx context.Context, req *walletv1.ListWalletTransactionsRequest) (*walletv1.ListWalletTransactionsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	items, total, err := s.svc.ListWalletTransactions(ctx, ledger.ListWalletTransactionsQuery{
		AppCode:   req.GetAppCode(),
		UserID:    req.GetUserId(),
		AssetType: req.GetAssetType(),
		Page:      req.GetPage(),
		PageSize:  req.GetPageSize(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &walletv1.ListWalletTransactionsResponse{Transactions: make([]*walletv1.WalletTransaction, 0, len(items)), Total: total}
	for _, item := range items {
		resp.Transactions = append(resp.Transactions, &walletv1.WalletTransaction{
			EntryId:            item.EntryID,
			TransactionId:      item.TransactionID,
			BizType:            item.BizType,
			AssetType:          item.AssetType,
			AvailableDelta:     item.AvailableDelta,
			FrozenDelta:        item.FrozenDelta,
			AvailableAfter:     item.AvailableAfter,
			FrozenAfter:        item.FrozenAfter,
			CounterpartyUserId: item.CounterpartyUserID,
			RoomId:             item.RoomID,
			CreatedAtMs:        item.CreatedAtMS,
		})
	}
	return resp, nil
}

// ApplyWithdrawal 创建提现申请并冻结美元余额。
func (s *Server) ApplyWithdrawal(ctx context.Context, req *walletv1.ApplyWithdrawalRequest) (*walletv1.ApplyWithdrawalResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	withdrawal, balance, err := s.svc.ApplyWithdrawal(ctx, ledger.WithdrawalCommand{
		AppCode:       req.GetAppCode(),
		CommandID:     req.GetCommandId(),
		UserID:        req.GetUserId(),
		Amount:        req.GetAmount(),
		PayoutAccount: req.GetPayoutAccount(),
		Reason:        req.GetReason(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.ApplyWithdrawalResponse{
		Withdrawal: withdrawalToProto(withdrawal),
		Balance:    balanceToProto(balance),
	}, nil
}

// ListVipPackages 返回当前 VIP 和可购买包列表。
func (s *Server) ListVipPackages(ctx context.Context, req *walletv1.ListVipPackagesRequest) (*walletv1.ListVipPackagesResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	current, levels, err := s.svc.ListVipPackages(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &walletv1.ListVipPackagesResponse{CurrentVip: vipToProto(current), Packages: make([]*walletv1.VipLevel, 0, len(levels))}
	for _, level := range levels {
		resp.Packages = append(resp.Packages, vipLevelToProto(level))
	}
	return resp, nil
}

// GetMyVip 返回当前登录用户 VIP 状态。
func (s *Server) GetMyVip(ctx context.Context, req *walletv1.GetMyVipRequest) (*walletv1.GetMyVipResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	vip, err := s.svc.GetMyVip(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.GetMyVipResponse{Vip: vipToProto(vip)}, nil
}

// PurchaseVip 购买、续期或升级 VIP。
func (s *Server) PurchaseVip(ctx context.Context, req *walletv1.PurchaseVipRequest) (*walletv1.PurchaseVipResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	receipt, err := s.svc.PurchaseVip(ctx, ledger.PurchaseVipCommand{
		AppCode:   req.GetAppCode(),
		CommandID: req.GetCommandId(),
		UserID:    req.GetUserId(),
		Level:     req.GetLevel(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.PurchaseVipResponse{
		OrderId:          receipt.OrderID,
		TransactionId:    receipt.TransactionID,
		Vip:              vipToProto(receipt.Vip),
		CoinSpent:        receipt.CoinSpent,
		CoinBalanceAfter: receipt.CoinBalanceAfter,
		RewardItems:      vipRewardItemsToProto(receipt.RewardItems),
	}, nil
}

// AdminCreditAsset 处理后台手动入账，公网权限和审计入口由 gateway/admin 层控制。
func (s *Server) AdminCreditAsset(ctx context.Context, req *walletv1.AdminCreditAssetRequest) (*walletv1.AdminCreditAssetResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	balance, transactionID, err := s.svc.AdminCreditAsset(ctx, ledger.AdminCreditAssetCommand{
		AppCode:        req.GetAppCode(),
		CommandID:      req.GetCommandId(),
		TargetUserID:   req.GetTargetUserId(),
		AssetType:      req.GetAssetType(),
		Amount:         req.GetAmount(),
		OperatorUserID: req.GetOperatorUserId(),
		Reason:         req.GetReason(),
		EvidenceRef:    req.GetEvidenceRef(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &walletv1.AdminCreditAssetResponse{
		TransactionId: transactionID,
		Balance: &walletv1.AssetBalance{
			AppCode:         balance.AppCode,
			AssetType:       balance.AssetType,
			AvailableAmount: balance.AvailableAmount,
			FrozenAmount:    balance.FrozenAmount,
			Version:         balance.Version,
		},
	}, nil
}

func balancesToProto(balances []ledger.AssetBalance) []*walletv1.AssetBalance {
	items := make([]*walletv1.AssetBalance, 0, len(balances))
	for _, balance := range balances {
		items = append(items, balanceToProto(balance))
	}
	return items
}

func balanceToProto(balance ledger.AssetBalance) *walletv1.AssetBalance {
	return &walletv1.AssetBalance{
		AppCode:         balance.AppCode,
		AssetType:       balance.AssetType,
		AvailableAmount: balance.AvailableAmount,
		FrozenAmount:    balance.FrozenAmount,
		Version:         balance.Version,
	}
}

func walletFeatureFlagsToProto(flags ledger.WalletFeatureFlags) *walletv1.WalletFeatureFlags {
	return &walletv1.WalletFeatureFlags{
		RechargeEnabled:        flags.RechargeEnabled,
		DiamondExchangeEnabled: flags.DiamondExchangeEnabled,
		WithdrawEnabled:        flags.WithdrawEnabled,
	}
}

func withdrawalToProto(withdrawal ledger.WithdrawalRequest) *walletv1.WithdrawalRequest {
	return &walletv1.WithdrawalRequest{
		WithdrawalId:  withdrawal.WithdrawalID,
		UserId:        withdrawal.UserID,
		AssetType:     withdrawal.AssetType,
		Amount:        withdrawal.Amount,
		Status:        withdrawal.Status,
		PayoutAccount: withdrawal.PayoutAccount,
		Reason:        withdrawal.Reason,
		CreatedAtMs:   withdrawal.CreatedAtMS,
		UpdatedAtMs:   withdrawal.UpdatedAtMS,
	}
}

func vipToProto(vip ledger.UserVip) *walletv1.UserVip {
	return &walletv1.UserVip{
		UserId:      vip.UserID,
		Level:       vip.Level,
		Name:        vip.Name,
		Active:      vip.Active,
		StartedAtMs: vip.StartedAtMS,
		ExpiresAtMs: vip.ExpiresAtMS,
		UpdatedAtMs: vip.UpdatedAtMS,
	}
}

func vipLevelToProto(level ledger.VipLevel) *walletv1.VipLevel {
	return &walletv1.VipLevel{
		Level:                 level.Level,
		Name:                  level.Name,
		Status:                level.Status,
		PriceCoin:             level.PriceCoin,
		DurationMs:            level.DurationMS,
		RewardResourceGroupId: level.RewardResourceGroupID,
		RewardItems:           vipRewardItemsToProto(level.RewardItems),
		CanPurchase:           level.CanPurchase,
		SortOrder:             level.SortOrder,
	}
}

func vipRewardItemsToProto(items []ledger.VipRewardItem) []*walletv1.VipRewardItem {
	resp := make([]*walletv1.VipRewardItem, 0, len(items))
	for _, item := range items {
		resp = append(resp, &walletv1.VipRewardItem{
			ResourceId:   item.ResourceID,
			ResourceCode: item.ResourceCode,
			ResourceType: item.ResourceType,
			Name:         item.Name,
			Quantity:     item.Quantity,
			ExpiresAtMs:  item.ExpiresAtMS,
		})
	}
	return resp
}

// CreditTaskReward 处理 activity-service 任务奖励金币入账。
func (s *Server) CreditTaskReward(ctx context.Context, req *walletv1.CreditTaskRewardRequest) (*walletv1.CreditTaskRewardResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	receipt, err := s.svc.CreditTaskReward(ctx, ledger.TaskRewardCommand{
		AppCode:      req.GetAppCode(),
		CommandID:    req.GetCommandId(),
		TargetUserID: req.GetTargetUserId(),
		Amount:       req.GetAmount(),
		TaskType:     req.GetTaskType(),
		TaskID:       req.GetTaskId(),
		CycleKey:     req.GetCycleKey(),
		Reason:       req.GetReason(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &walletv1.CreditTaskRewardResponse{
		TransactionId: receipt.TransactionID,
		Amount:        receipt.Amount,
		GrantedAtMs:   receipt.GrantedAtMS,
		Balance: &walletv1.AssetBalance{
			AppCode:         receipt.Balance.AppCode,
			AssetType:       receipt.Balance.AssetType,
			AvailableAmount: receipt.Balance.AvailableAmount,
			FrozenAmount:    receipt.Balance.FrozenAmount,
			Version:         receipt.Balance.Version,
		},
	}, nil
}

// ApplyGameCoinChange 处理 game-service 发起的游戏专用金币改账。
func (s *Server) ApplyGameCoinChange(ctx context.Context, req *walletv1.ApplyGameCoinChangeRequest) (*walletv1.ApplyGameCoinChangeResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	receipt, err := s.svc.ApplyGameCoinChange(ctx, ledger.GameCoinChangeCommand{
		AppCode:         req.GetAppCode(),
		CommandID:       req.GetCommandId(),
		UserID:          req.GetUserId(),
		PlatformCode:    req.GetPlatformCode(),
		GameID:          req.GetGameId(),
		ProviderOrderID: req.GetProviderOrderId(),
		ProviderRoundID: req.GetProviderRoundId(),
		OpType:          req.GetOpType(),
		CoinAmount:      req.GetCoinAmount(),
		RoomID:          req.GetRoomId(),
		RequestHash:     req.GetRequestHash(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.ApplyGameCoinChangeResponse{
		WalletTransactionId: receipt.TransactionID,
		BalanceAfter:        receipt.BalanceAfter,
		IdempotentReplay:    receipt.IdempotentReplay,
	}, nil
}

// AdminCreditCoinSellerStock 处理后台币商进货或金币补偿，币商身份由 admin-server 先行校验。
func (s *Server) AdminCreditCoinSellerStock(ctx context.Context, req *walletv1.AdminCreditCoinSellerStockRequest) (*walletv1.AdminCreditCoinSellerStockResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	receipt, err := s.svc.AdminCreditCoinSellerStock(ctx, ledger.CoinSellerStockCreditCommand{
		AppCode:          req.GetAppCode(),
		CommandID:        req.GetCommandId(),
		SellerUserID:     req.GetSellerUserId(),
		StockType:        req.GetStockType(),
		CoinAmount:       req.GetCoinAmount(),
		PaidCurrencyCode: req.GetPaidCurrencyCode(),
		PaidAmountMicro:  req.GetPaidAmountMicro(),
		PaymentRef:       req.GetPaymentRef(),
		EvidenceRef:      req.GetEvidenceRef(),
		OperatorUserID:   req.GetOperatorUserId(),
		Reason:           req.GetReason(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &walletv1.AdminCreditCoinSellerStockResponse{
		TransactionId:          receipt.TransactionID,
		SellerUserId:           receipt.SellerUserID,
		StockType:              receipt.StockType,
		CoinAmount:             receipt.CoinAmount,
		PaidCurrencyCode:       receipt.PaidCurrencyCode,
		PaidAmountMicro:        receipt.PaidAmountMicro,
		CountsAsSellerRecharge: receipt.CountsAsSellerRecharge,
		BalanceAfter:           receipt.BalanceAfter,
		CreatedAtMs:            receipt.CreatedAtMS,
	}, nil
}

// TransferCoinFromSeller 处理币商转玩家金币，调用方必须先完成币商身份校验。
func (s *Server) TransferCoinFromSeller(ctx context.Context, req *walletv1.TransferCoinFromSellerRequest) (*walletv1.TransferCoinFromSellerResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	receipt, err := s.svc.TransferCoinFromSeller(ctx, ledger.CoinSellerTransferCommand{
		AppCode:        req.GetAppCode(),
		CommandID:      req.GetCommandId(),
		SellerUserID:   req.GetSellerUserId(),
		TargetUserID:   req.GetTargetUserId(),
		SellerRegionID: req.GetSellerRegionId(),
		TargetRegionID: req.GetTargetRegionId(),
		Amount:         req.GetAmount(),
		Reason:         req.GetReason(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}

	return &walletv1.TransferCoinFromSellerResponse{
		TransactionId:                receipt.TransactionID,
		SellerBalanceAfter:           receipt.SellerBalanceAfter,
		TargetBalanceAfter:           receipt.TargetBalanceAfter,
		Amount:                       receipt.Amount,
		RechargeUsdMinor:             receipt.RechargeUSDMinor,
		RechargeCurrencyCode:         receipt.RechargeCurrencyCode,
		RechargePolicyId:             receipt.RechargePolicyID,
		RechargePolicyVersion:        receipt.RechargePolicyVersion,
		RechargePolicyCoinAmount:     receipt.RechargePolicyCoinAmount,
		RechargePolicyUsdMinorAmount: receipt.RechargePolicyUSDMinorUnit,
	}, nil
}
