// Code scaffolded by goctl. Safe to edit.
// goctl 1.9.2

package wallet

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

	"juwan-backend/app/wallet/rpc/pb"
	"juwan-backend/common/utils/contextj"

	"juwan-backend/app/wallet/api/internal/svc"
	"juwan-backend/app/wallet/api/internal/types"

	"github.com/shopspring/decimal"
	"github.com/zeromicro/go-zero/core/logx"
)

type TopupLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

// 充值
func NewTopupLogic(ctx context.Context, svcCtx *svc.ServiceContext) *TopupLogic {
	return &TopupLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *TopupLogic) Topup(req *types.TopupReq) (resp *types.EmptyResp, err error) {
	amount, err := decimal.NewFromString(req.Amount)
	if err != nil {
		return nil, errors.New("invalid amount")
	}
	if !amount.GreaterThan(decimal.Zero) {
		return nil, errors.New("amount must be greater than 0")
	}

	userID, err := contextj.UserIDFrom(l.ctx)
	if err != nil {
		return nil, err
	}

	walletResp, err := l.svcCtx.WalletRpc.GetWalletsById(l.ctx, &pb.GetWalletsByIdReq{Id: userID})
	if err != nil {
		return nil, err
	}

	if walletResp.GetWallets() == nil {
		zeroAmount := "0"
		_, err = l.svcCtx.WalletRpc.AddWallets(l.ctx, &pb.AddWalletsReq{
			UserId:        userID,
			Balance:       zeroAmount,
			FrozenBalance: zeroAmount,
			UpdatedAt:     time.Now().Unix(),
		})
		if err != nil {
			return nil, err
		}
		walletResp, err = l.svcCtx.WalletRpc.GetWalletsById(l.ctx, &pb.GetWalletsByIdReq{Id: userID})
		if err != nil {
			return nil, err
		}
		if walletResp.GetWallets() == nil {
			return nil, errors.New("wallet not found")
		}
	}

	const maxRetries = 3
	for i := 0; i < maxRetries; i++ {
		currentBalance, perr := decimal.NewFromString(walletResp.Wallets.Balance)
		if perr != nil {
			return nil, errors.New("invalid wallet balance")
		}
		frozenBalance, perr := decimal.NewFromString(walletResp.Wallets.FrozenBalance)
		if perr != nil {
			return nil, errors.New("invalid wallet frozen balance")
		}
		expectedVersion := walletResp.Wallets.Version

		newBalance := currentBalance.Add(amount)
		newBalanceStr := newBalance.String()
		frozenBalanceStr := frozenBalance.String()
		updatedAt := time.Now().Unix()
		_, uerr := l.svcCtx.WalletRpc.UpdateWallets(l.ctx, &pb.UpdateWalletsReq{
			UserId:        userID,
			Balance:       &newBalanceStr,
			FrozenBalance: &frozenBalanceStr,
			UpdatedAt:     &updatedAt,
			Version:       &expectedVersion,
		})
		if uerr == nil {
			desc := fmt.Sprintf("topup via %s", req.Method)
			_, err = l.svcCtx.WalletRpc.AddWalletTransactions(l.ctx, &pb.AddWalletTransactionsReq{
				UserId:       userID,
				Type:         "topup",
				Amount:       amount.String(),
				BalanceAfter: newBalanceStr,
				Description:  desc,
				CreatedAt:    updatedAt,
			})
			if err != nil {
				return nil, err
			}
			return &types.EmptyResp{}, nil
		}
		if !strings.Contains(uerr.Error(), "conflict") {
			return nil, uerr
		}
		walletResp, err = l.svcCtx.WalletRpc.GetWalletsById(l.ctx, &pb.GetWalletsByIdReq{Id: userID})
		if err != nil {
			return nil, err
		}
	}

	return nil, errors.New("wallet update conflict, please retry")
}
