package binanceus

import (
	"log"
	"os"
	reflects "reflect"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/thrasher-corp/gocryptotrader/core"
	"github.com/thrasher-corp/gocryptotrader/currency"
	"github.com/thrasher-corp/gocryptotrader/encoding/json"
	exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
	"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
	"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
	"github.com/thrasher-corp/gocryptotrader/exchanges/order"
	"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
	"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
	testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange"
	"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)

// Please supply your own keys here to test authenticated endpoints
const (
	apiKey                  = ""
	apiSecret               = ""
	canManipulateRealOrders = false
)

var (
	e               = &Exchange{}
	testPairMapping = currency.NewBTCUSDT()
	// this lock guards against orderbook tests race
	binanceusOrderBookLock = &sync.Mutex{}
)

func TestMain(m *testing.M) {
	e = new(Exchange)
	if err := testexch.Setup(e); err != nil {
		log.Fatalf("Binanceus Setup error: %s", err)
	}

	if apiKey != "" && apiSecret != "" {
		e.API.AuthenticatedSupport = true
		e.API.AuthenticatedWebsocketSupport = true
		e.SetCredentials(apiKey, apiSecret, "", "", "", "")
	}

	e.WebsocketResponseMaxLimit = exchange.DefaultWebsocketResponseMaxLimit
	os.Exit(m.Run())
}

func TestServerTime(t *testing.T) {
	t.Parallel()
	if _, er := e.GetServerTime(t.Context(), asset.Spot); er != nil {
		t.Error("Binanceus SystemTime() error", er)
	}
}

func TestServerStatus(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	if _, er := e.GetSystemStatus(t.Context()); er != nil {
		t.Error("Binanceus GetSystemStatus() error", er)
	}
}

func TestGetExchangeInfo(t *testing.T) {
	t.Parallel()
	_, err := e.GetExchangeInfo(t.Context())
	if err != nil {
		t.Error("Binanceus GetExchangeInfo() error", err)
	}
}

func TestUpdateTicker(t *testing.T) {
	t.Parallel()
	r, err := e.UpdateTicker(t.Context(), testPairMapping, asset.Spot)
	if err != nil {
		t.Error(err)
	}
	if r.Pair.Base != currency.BTC && r.Pair.Quote != currency.USDT {
		t.Error("Binanceus UpdateTicker() invalid pair values")
	}
}

func TestUpdateTickers(t *testing.T) {
	t.Parallel()
	err := e.UpdateTickers(t.Context(), asset.Spot)
	if err != nil {
		t.Error(err)
	}
}

func TestUpdateOrderBook(t *testing.T) {
	t.Parallel()
	_, er := e.UpdateOrderbook(t.Context(), testPairMapping, asset.Spot)
	if er != nil {
		t.Error("Binanceus UpdateOrderBook() error", er)
	}
}

func TestFetchTradablePairs(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, err := e.FetchTradablePairs(t.Context(), asset.Spot)
	if err != nil {
		t.Error("Binanceus FetchTradablePairs() error", err)
	}
}

func TestUpdateTradablePairs(t *testing.T) {
	t.Parallel()
	err := e.UpdateTradablePairs(t.Context())
	if err != nil {
		t.Error("Binanceus UpdateTradablePairs() error", err)
	}
}

func TestUpdateAccountBalances(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, err := e.UpdateAccountBalances(t.Context(), asset.Spot)
	require.NoError(t, err)
}

func TestGetRecentTrades(t *testing.T) {
	t.Parallel()
	pair := currency.Pair{Base: currency.BTC, Quote: currency.USD}
	_, err := e.GetRecentTrades(t.Context(), pair, asset.Spot)
	if err != nil {
		t.Error("Binanceus GetRecentTrades() error", err)
	}
}

func TestGetHistoricTrades(t *testing.T) {
	t.Parallel()
	p := currency.NewBTCUSDT()
	start := time.Now().Add(-time.Hour * 24 * 90).Truncate(time.Minute) // 3 months ago
	end := start.Add(15 * time.Minute)
	result, err := e.GetHistoricTrades(t.Context(), p, asset.Spot, start, end)
	require.NoError(t, err, "GetHistoricTrades must not error")
	assert.NotEmpty(t, result, "GetHistoricTrades should have trades")
	for _, r := range result {
		require.WithinRange(t, r.Timestamp, start, end, "All trades must be within time range")
	}
}

func TestGetFeeByType(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	if _, er := e.GetFeeByType(t.Context(), &exchange.FeeBuilder{
		IsMaker: true,
		Pair:    currency.NewPair(currency.USD, currency.BTC),
		FeeType: exchange.CryptocurrencyTradeFee,
	}); er != nil {
		t.Error("Binanceus GetFeeByType() error", er)
	}
	if _, er := e.GetFeeByType(t.Context(), &exchange.FeeBuilder{
		IsMaker: true,
		Pair:    currency.NewPair(currency.USD, currency.BTC),
		FeeType: exchange.CryptocurrencyWithdrawalFee,
	}); er != nil {
		t.Error("Binanceus GetFeeByType() error", er)
	}
}

func TestSubmitOrder(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCannotManipulateOrders(t, e, canManipulateRealOrders)
	orderSubmission := &order.Submit{
		Pair: currency.Pair{
			Base:  currency.XRP,
			Quote: currency.USD,
		},
		AssetType: asset.Spot,
		Side:      order.Sell,
		Type:      order.Limit,
		Price:     1000,
		Amount:    20,
		ClientID:  "binanceSamOrder",
		Exchange:  e.Name,
	}
	response, err := e.SubmitOrder(t.Context(), orderSubmission)
	switch {
	case sharedtestvalues.AreAPICredentialsSet(e) && err != nil && strings.Contains(err.Error(), "{\"code\":-1013,\"msg\":\"Market is closed.\""):
		t.Skip("Binanceus SubmitOrder() Market is Closed")
	case sharedtestvalues.AreAPICredentialsSet(e) && err != nil:
		t.Errorf("Binanceus SubmitOrder() Could not place order: %v", err)
	case sharedtestvalues.AreAPICredentialsSet(e) && response.Status != order.Filled:
		t.Error("Binanceus SubmitOrder() Order not placed")
	case !sharedtestvalues.AreAPICredentialsSet(e) && err == nil:
		t.Error("Binanceus SubmitOrder() Expecting an error when no keys are set")
	}
}

func TestCancelOrder(t *testing.T) {
	t.Parallel()

	pair := currency.NewBTCUSD()
	err := e.CancelOrder(t.Context(), &order.Cancel{
		AssetType: asset.Spot,
		OrderID:   "1337",
	})
	require.ErrorIs(t, err, errMissingCurrencySymbol)
	err = e.CancelOrder(t.Context(), &order.Cancel{
		AssetType: asset.Futures,
		OrderID:   "69",
		Pair:      pair,
	})
	require.ErrorIs(t, err, asset.ErrNotSupported)
	err = e.CancelOrder(t.Context(), &order.Cancel{
		AssetType: asset.Spot,
		OrderID:   "",
		Pair:      pair,
	})
	require.ErrorIs(t, err, order.ErrOrderIDNotSet)

	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
	cancellationOrder := &order.Cancel{
		OrderID:   "1",
		Pair:      pair,
		AssetType: asset.Spot,
	}
	err = e.CancelOrder(t.Context(), cancellationOrder)
	assert.ErrorContains(t, err, "Unknown order sent.")
}

func TestCancelAllOrders(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
	orderCancellation := &order.Cancel{
		Pair:      currency.NewPair(currency.LTC, currency.BTC),
		AssetType: asset.Spot,
	}
	if _, err := e.CancelAllOrders(t.Context(), orderCancellation); err != nil {
		t.Error("Binanceus CancelAllOrders() error", err)
	}
}

func TestGetOrderInfo(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	tradablePairs, err := e.FetchTradablePairs(t.Context(),
		asset.Spot)
	if err != nil {
		t.Error(err)
	}
	if len(tradablePairs) == 0 {
		t.Fatal("Binanceus GetOrderInfo() no tradable pairs")
	}
	_, err = e.GetOrderInfo(t.Context(),
		"123",
		tradablePairs[0],
		asset.Spot)
	if !strings.Contains(err.Error(), "Order does not exist.") {
		t.Error("Binanceus GetOrderInfo() error", err)
	}
}

func TestGetDepositAddress(t *testing.T) {
	t.Parallel()
	_, err := e.GetDepositAddress(t.Context(), currency.EMPTYCODE, "", currency.BNB.String())
	assert.ErrorIs(t, err, errMissingRequiredArgumentCoin)
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, err = e.GetDepositAddress(t.Context(), currency.USDT, "", currency.BNB.String())
	assert.NoError(t, err)
}

func TestGetWithdrawalHistory(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCannotManipulateOrders(t, e, canManipulateRealOrders)
	_, err := e.GetWithdrawalsHistory(t.Context(), currency.ETH, asset.Spot)
	switch {
	case sharedtestvalues.AreAPICredentialsSet(e) && err != nil:
		t.Error("Binanceus GetWithdrawalsHistory() error", err)
	case !sharedtestvalues.AreAPICredentialsSet(e) && err == nil:
		t.Error("Binanceus GetWithdrawalsHistory() expecting an error when no keys are set")
	}
}

func TestWithdrawFiat(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
	if _, er := e.WithdrawFiat(t.Context(), &WithdrawFiatRequestParams{
		PaymentChannel: "SILVERGATE",
		PaymentAccount: "myaccount",
		PaymentMethod:  "SEN",
		Amount:         1,
	}); er != nil && !strings.Contains(er.Error(), "You are not authorized to execute this request.") {
		t.Error("Binanceus WithdrawFiat() error", er)
	}
}

func TestGetActiveOrders(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	getOrdersRequest := order.MultiOrderRequest{
		Type:      order.AnyType,
		AssetType: asset.Spot,
		Side:      order.AnySide,
	}
	_, err := e.GetActiveOrders(t.Context(), &getOrdersRequest)
	if err != nil {
		t.Error("Binanceus GetActiveOrders() error", err)
	}
}

func TestWithdraw(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
	withdrawCryptoRequest := withdraw.Request{
		Exchange:    e.Name,
		Amount:      -1,
		Currency:    currency.BTC,
		Description: "WITHDRAW IT ALL",
		Crypto: withdraw.CryptoRequest{
			Address: core.BitcoinDonationAddress,
			Chain:   "BSC",
		},
	}
	_, err := e.WithdrawCryptocurrencyFunds(t.Context(), &withdrawCryptoRequest)
	if err != nil && !strings.EqualFold(errAmountValueMustBeGreaterThan0.Error(), err.Error()) {
		t.Errorf("Binanceus Withdraw() expecting %v, but found %v", errAmountValueMustBeGreaterThan0, err)
	} else if !sharedtestvalues.AreAPICredentialsSet(e) && err == nil {
		t.Error("Binanceus Withdraw() expecting an error when no keys are set")
	}
	withdrawCryptoRequest.Amount = 1
	_, err = e.WithdrawCryptocurrencyFunds(t.Context(), &withdrawCryptoRequest)
	if err != nil && !strings.Contains(err.Error(), "You are not authorized to execute this request.") {
		t.Error("Binanceus WithdrawCryptocurrencyFunds() error", err)
	}
}

func TestGetFee(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	feeBuilder := &exchange.FeeBuilder{
		Amount:        1,
		FeeType:       exchange.CryptocurrencyTradeFee,
		Pair:          currency.NewPair(currency.BTC, currency.LTC),
		PurchasePrice: 1,
	}
	_, er := e.GetFeeByType(t.Context(), feeBuilder)
	if er != nil {
		t.Fatal("Binanceus GetFeeByType() error", er)
	}
	withdrawalFeeBuilder := &exchange.FeeBuilder{
		Amount:        1,
		FeeType:       exchange.CryptocurrencyWithdrawalFee,
		Pair:          currency.NewPair(currency.BTC, currency.LTC),
		PurchasePrice: 1,
	}
	_, er = e.GetFeeByType(t.Context(), withdrawalFeeBuilder)
	if er != nil {
		t.Fatal("Binanceus GetFeeByType() error", er)
	}
	offlineFeeTradeBuilder := &exchange.FeeBuilder{
		Amount:        1,
		FeeType:       exchange.OfflineTradeFee,
		Pair:          currency.NewPair(currency.BTC, currency.LTC),
		PurchasePrice: 1,
	}
	_, er = e.GetFeeByType(t.Context(), offlineFeeTradeBuilder)
	if er != nil {
		t.Fatal("Binanceus GetFeeByType() error", er)
	}
}

func TestGetHistoricCandles(t *testing.T) {
	t.Parallel()
	pair := currency.NewBTCUSDT()
	startTime := time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC)
	endTime := time.Date(2021, 2, 15, 0, 0, 0, 0, time.UTC)

	_, err := e.GetHistoricCandles(t.Context(), pair, asset.Spot, kline.Interval(time.Hour*5), startTime, endTime)
	require.ErrorIs(t, err, kline.ErrRequestExceedsExchangeLimits)

	_, err = e.GetHistoricCandles(t.Context(), pair, asset.Spot, kline.OneDay, startTime, endTime)
	if err != nil {
		t.Error("Binanceus GetHistoricCandles() error", err)
	}
}

func TestGetHistoricCandlesExtended(t *testing.T) {
	t.Parallel()
	pair := currency.NewBTCUSDT()
	startTime := time.Date(2020, 9, 1, 0, 0, 0, 0, time.UTC)
	endTime := time.Date(2021, 2, 15, 0, 0, 0, 0, time.UTC)

	_, err := e.GetHistoricCandlesExtended(t.Context(), pair, asset.Spot, kline.OneDay, startTime, endTime)
	if err != nil {
		t.Fatal(err)
	}

	startTime = time.Now().Add(-time.Hour * 30)
	endTime = time.Now()

	_, err = e.GetHistoricCandlesExtended(t.Context(), pair, asset.Spot, kline.FourHour, startTime, endTime)
	if err != nil {
		t.Error("Binanceus GetHistoricCandlesExtended() error", err)
	}
}

/************************************************************************/

// TestGetMostRecentTrades -- test most recent trades end-point
func TestGetMostRecentTrades(t *testing.T) {
	t.Parallel()
	_, err := e.GetMostRecentTrades(t.Context(), RecentTradeRequestParams{
		Symbol: currency.NewBTCUSDT(),
		Limit:  15,
	})
	if err != nil {
		t.Error("Binanceus GetMostRecentTrades() error", err)
	}
}

func TestGetHistoricalTrades(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, err := e.GetHistoricalTrades(t.Context(), HistoricalTradeParams{
		Symbol: "BTCUSDT",
		Limit:  5,
		FromID: 0,
	})
	if err != nil {
		t.Errorf("Binanceus GetHistoricalTrades() error: %v", err)
	}
}

func TestGetAggregateTrades(t *testing.T) {
	t.Parallel()
	_, err := e.GetAggregateTrades(t.Context(),
		&AggregatedTradeRequestParams{
			Symbol: currency.NewBTCUSDT(),
			Limit:  5,
		})
	if err != nil {
		t.Error("Binanceus GetAggregateTrades() error", err)
	}
}

func TestGetOrderBookDepth(t *testing.T) {
	t.Parallel()
	_, err := e.GetOrderBookDepth(t.Context(), currency.NewBTCUSDT(), 1000)
	assert.NoError(t, err)
}

func TestGetCandlestickData(t *testing.T) {
	t.Parallel()
	_, er := e.GetSpotKline(t.Context(), &KlinesRequestParams{
		Symbol:    currency.NewBTCUSDT(),
		Interval:  kline.FiveMin.Short(),
		Limit:     24,
		StartTime: time.Unix(1577836800, 0),
		EndTime:   time.Unix(1580515200, 0),
	})
	if er != nil {
		t.Error("Binanceus GetSpotKline() error", er)
	}
}

func TestGetPriceDatas(t *testing.T) {
	t.Parallel()
	_, er := e.GetPriceDatas(t.Context())
	if er != nil {
		t.Error("Binanceus GetPriceDatas() error", er)
	}
}

func TestGetSinglePriceData(t *testing.T) {
	t.Parallel()
	_, er := e.GetSinglePriceData(t.Context(), currency.Pair{
		Base:  currency.BTC,
		Quote: currency.USDT,
	})
	if er != nil {
		t.Error("Binanceus GetSinglePriceData() error", er)
	}
}

func TestGetAveragePrice(t *testing.T) {
	t.Parallel()
	_, err := e.GetAveragePrice(t.Context(), currency.NewBTCUSDT())
	if err != nil {
		t.Error("Binance GetAveragePrice() error", err)
	}
}

func TestGetBestPrice(t *testing.T) {
	t.Parallel()
	_, err := e.GetBestPrice(t.Context(), currency.NewBTCUSDT())
	if err != nil {
		t.Error("Binanceus GetBestPrice() error", err)
	}
}

func TestGetPriceChangeStats(t *testing.T) {
	t.Parallel()
	_, err := e.GetPriceChangeStats(t.Context(), currency.NewBTCUSDT())
	if err != nil {
		t.Error("Binance GetPriceChangeStats() error", err)
	}
}

func TestGetTickers(t *testing.T) {
	t.Parallel()
	_, err := e.GetTickers(t.Context())
	if err != nil {
		t.Error("Binance TestGetTickers error", err)
	}
}

func TestGetAccount(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.GetAccount(t.Context())
	if er != nil {
		t.Error("Binanceus GetAccount() error", er)
	}
}

func TestGetUserAccountStatus(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.GetUserAccountStatus(t.Context(), 3000)
	if er != nil {
		t.Error("Binanceus GetUserAccountStatus() error", er)
	}
}

func TestGetUserAPITradingStatus(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.GetUserAPITradingStatus(t.Context(), 3000)
	if er != nil {
		t.Error("Binanceus GetUserAPITradingStatus() error", er)
	}
}

func TestGetTradeFee(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.GetTradeFee(t.Context(), 3000, "BTC-USDT")
	if er != nil {
		t.Error("Binanceus GetTradeFee() error", er)
	}
}

func TestGetAssetDistributionHistory(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.GetAssetDistributionHistory(t.Context(), "", 0, 0, 3000)
	if er != nil {
		t.Error("Binanceus GetAssetDistributionHistory() error", er)
	}
}

func TestGetMasterAccountTotalUSDValue(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	if _, er := e.GetMasterAccountTotalUSDValue(t.Context(), "", 0, 0); er != nil && !strings.Contains(er.Error(), "Sub-account function is not enabled.") {
		t.Errorf("Binanceus GetMasterAccountTotalUSDValue() expecting %s, but found %v", "Sub-account function is not enabled.", er)
	}
}

func TestGetSubaccountStatusList(t *testing.T) {
	t.Parallel()
	_, err := e.GetSubaccountStatusList(t.Context(), "")
	assert.ErrorIs(t, err, errMissingSubAccountEmail)

	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, err = e.GetSubaccountStatusList(t.Context(), "someone@thrasher.corp")
	assert.ErrorContains(t, err, "Sub-account function is not enabled.")
}

func TestGetSubAccountDepositAddress(t *testing.T) {
	t.Parallel()
	_, err := e.GetSubAccountDepositAddress(t.Context(), SubAccountDepositAddressRequestParams{})
	assert.ErrorIs(t, err, errMissingSubAccountEmail)
	_, err = e.GetSubAccountDepositAddress(t.Context(), SubAccountDepositAddressRequestParams{
		Email: "someone@thrasher.io",
	})
	assert.ErrorIs(t, err, errMissingCurrencyCoin)

	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)

	_, err = e.GetSubAccountDepositAddress(t.Context(), SubAccountDepositAddressRequestParams{
		Email: "someone@thrasher.io",
		Coin:  currency.BTC,
	})
	assert.ErrorContains(t, err, "This parent sub have no relation")
}

var subAccountDepositHistoryItemJSON = `{
	"amount": "9.9749",
	"coin": "BTC", 
	"network": "btc",
	"status": 4, 
	"address": "bc1qxurvdd7tzn09agdvg3j8xpm3f7e978y07wg83s",
	"addressTag": "",
	"txId": "0x1b4b8c8090d15e3c1b0476b1c19118b1f00066e01de567cd7bc5b6e9c100193f",
	"insertTime": 1652942429211,
	"transferType": 0,
	"confirmTimes": "0/0"
}`

func TestGetSubAccountDepositHistory(t *testing.T) {
	t.Parallel()
	var resp SubAccountDepositItem
	require.NoError(t, json.Unmarshal([]byte(subAccountDepositHistoryItemJSON), &resp))
	_, err := e.GetSubAccountDepositHistory(t.Context(), "", currency.BTC, 1, time.Time{}, time.Time{}, 0, 0)
	assert.ErrorIs(t, err, errMissingSubAccountEmail)

	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)

	_, err = e.GetSubAccountDepositHistory(t.Context(), "someone@thrasher.io", currency.BTC, 1, time.Time{}, time.Time{}, 0, 0)
	assert.ErrorContains(t, err, "This parent sub have no relation")
}

var subaccountItemJSON = `{
	"email": "123@test.com",
	"status": "enabled",
	"activated": true,
	"mobile": "91605290",
	"gAuth": true,
	"createTime": 1544433328000
}`

func TestGetSubaccountInformation(t *testing.T) {
	t.Parallel()
	var resp SubAccount
	if er := json.Unmarshal([]byte(subaccountItemJSON), &resp); er != nil {
		t.Error("Binanceus decerializing to SubAccount error", er)
	}
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.GetSubaccountInformation(t.Context(), 1, 100, "", "")
	if er != nil && !strings.Contains(er.Error(), "Sub-account function is not enabled.") {
		t.Error("Binanceus GetSubaccountInformation() error", er)
	}
}

var referalRewardHistoryResponse = `{
    "total": 1,
    "rows": [
        {
            "userId": 350991652,
            "rewardAmount": "8",
            "receiveDateTime": 1651131084091,
            "rewardType": "USD"
        }
    ]
}`

func TestGetReferralRewardHistory(t *testing.T) {
	t.Parallel()
	var resp ReferralRewardHistoryResponse
	require.NoError(t, json.Unmarshal([]byte(referalRewardHistoryResponse), &resp))
	_, err := e.GetReferralRewardHistory(t.Context(), 9, 5, 50)
	assert.ErrorIs(t, err, errInvalidUserBusinessType)
	_, err = e.GetReferralRewardHistory(t.Context(), 1, 0, 50)
	assert.ErrorIs(t, err, errMissingPageNumber)
	_, err = e.GetReferralRewardHistory(t.Context(), 1, 5, 0)
	assert.ErrorIs(t, err, errInvalidRowNumber)
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, err = e.GetReferralRewardHistory(t.Context(), 1, 5, 50)
	assert.NoError(t, err)
}

func TestGetSubaccountTransferHistory(t *testing.T) {
	t.Parallel()

	_, err := e.GetSubaccountTransferHistory(t.Context(), "", 0, 0, 0, 0)
	assert.ErrorIs(t, err, errNotValidEmailAddress)

	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)

	_, err = e.GetSubaccountTransferHistory(t.Context(), "example@golang.org", 0, 0, 0, 0)
	assert.Error(t, err, "GetSubaccountTransferHistory should return an error on a bogus email")
}

func TestExecuteSubAccountTransfer(t *testing.T) {
	t.Parallel()
	_, err := e.ExecuteSubAccountTransfer(t.Context(), &SubAccountTransferRequestParams{})
	assert.ErrorIs(t, err, errUnacceptableSenderEmail)

	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
	_, err = e.ExecuteSubAccountTransfer(t.Context(), &SubAccountTransferRequestParams{
		FromEmail: "fromemail@thrasher.io",
		ToEmail:   "toemail@thrasher.io",
		Asset:     "BTC",
		Amount:    0.000005,
	})
	assert.ErrorContains(t, err, "You are not authorized to execute this request.")
}

func TestGetSubaccountAssets(t *testing.T) {
	t.Parallel()
	_, err := e.GetSubaccountAssets(t.Context(), "")
	assert.ErrorIs(t, err, errNotValidEmailAddress)
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, err = e.GetSubaccountAssets(t.Context(), "subaccount@thrasher.io")
	assert.ErrorContains(t, err, "This account does not exist.")
}

func TestGetOrderRateLimits(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.GetOrderRateLimits(t.Context(), 0)
	if er != nil {
		t.Error("Binanceus GetOrderRateLimits() error", er)
	}
}

var testNewOrderResponseJSON = `{
	"symbol": "BTCUSDT",
	"orderId": 28,
	"orderListId": -1,
	"clientOrderId": "6gCrw2kRUAF9CvJDGP16IP",
	"transactTime": 1507725176595,
	"price": "0.00000000",
	"origQty": "10.00000000",
	"executedQty": "10.00000000",
	"cummulativeQuoteQty": "10.00000000",
	"status": "FILLED",
	"timeInForce": "GTC",
	"type": "MARKET",
	"side": "SELL"
  }`

func TestNewOrderTest(t *testing.T) {
	t.Parallel()
	var resp NewOrderResponse
	if er := json.Unmarshal([]byte(testNewOrderResponseJSON), &resp); er != nil {
		t.Error("Binanceus decerializing to Order error", er)
	}
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
	req := &NewOrderRequest{
		Symbol:      currency.NewPair(currency.LTC, currency.BTC),
		Side:        order.Buy.String(),
		TradeType:   BinanceRequestParamsOrderLimit,
		Price:       0.0025,
		Quantity:    100000,
		TimeInForce: order.GoodTillCancel.String(),
	}
	_, err := e.NewOrderTest(t.Context(), req)
	if err != nil {
		t.Error("Binanceus NewOrderTest() error", err)
	}
	req = &NewOrderRequest{
		Symbol:        currency.NewPair(currency.LTC, currency.BTC),
		Side:          order.Sell.String(),
		TradeType:     BinanceRequestParamsOrderMarket,
		Price:         0.0045,
		QuoteOrderQty: 10,
	}
	_, err = e.NewOrderTest(t.Context(), req)
	if err != nil {
		t.Error("NewOrderTest() error", err)
	}
}

func TestNewOrder(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
	req := &NewOrderRequest{
		Symbol:      currency.NewPair(currency.LTC, currency.BTC),
		Side:        order.Buy.String(),
		TradeType:   BinanceRequestParamsOrderLimit,
		Price:       0.0025,
		Quantity:    100000,
		TimeInForce: order.GoodTillCancel.String(),
	}
	if _, err := e.NewOrder(t.Context(), req); err != nil && !strings.Contains(err.Error(), "Account has insufficient balance for requested action") {
		t.Error("Binanceus NewOrder() error", err)
	}
}

func TestGetOrder(t *testing.T) {
	t.Parallel()
	_, err := e.GetOrder(t.Context(), &OrderRequestParams{})
	assert.ErrorIs(t, err, errIncompleteArguments)
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, err = e.GetOrder(t.Context(), &OrderRequestParams{
		Symbol:            "BTCUSDT",
		OrigClientOrderID: "something",
	})
	// You can check the existence of an order using a valid Symbol and OrigClient Order ID
	assert.ErrorContains(t, err, "Order does not exist.")
}

var openOrdersItemJSON = `{
    "symbol": "LTCBTC",
    "orderId": 1,
    "orderListId": -1,
    "clientOrderId": "myOrder1",
    "price": "0.1",
    "origQty": "1.0",
    "executedQty": "0.0",
    "cummulativeQuoteQty": "0.0",
    "status": "NEW",
    "timeInForce": "GTC",
    "type": "LIMIT",
    "side": "BUY",
    "stopPrice": "0.0",
    "icebergQty": "0.0",
    "time": 1499827319559,
    "updateTime": 1499827319559,
    "isWorking": true,
    "origQuoteOrderQty": "0.000000"
  }`

func TestGetAllOpenOrders(t *testing.T) {
	t.Parallel()
	var resp Order
	if er := json.Unmarshal([]byte(openOrdersItemJSON), &resp); er != nil {
		t.Error("Binanceus decerializing to Order error", er)
	}
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)

	_, er := e.GetAllOpenOrders(t.Context(), "")
	if er != nil {
		t.Error("Binanceus GetAllOpenOrders() error", er)
	}
}

func TestCancelExistingOrder(t *testing.T) {
	t.Parallel()

	_, err := e.CancelExistingOrder(t.Context(), &CancelOrderRequestParams{})
	assert.ErrorIs(t, err, errMissingCurrencySymbol)

	_, err = e.CancelExistingOrder(t.Context(), &CancelOrderRequestParams{
		Symbol: currency.NewBTCUSDT(),
	})
	assert.ErrorIs(t, err, errEitherOrderIDOrClientOrderIDIsRequired)

	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
	_, err = e.CancelExistingOrder(t.Context(), &CancelOrderRequestParams{
		Symbol:                currency.NewBTCUSDT(),
		ClientSuppliedOrderID: "1234",
	})
	assert.ErrorContains(t, err, "Unknown order sent.")
}

func TestCancelOpenOrdersForSymbol(t *testing.T) {
	t.Parallel()
	_, err := e.CancelOpenOrdersForSymbol(t.Context(), "")
	assert.ErrorIs(t, err, errMissingCurrencySymbol)

	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)

	_, err = e.CancelOpenOrdersForSymbol(t.Context(), "BTCUSDT")
	assert.NoError(t, err)
}

// TestGetTrades test for fetching the list of
// trades attached with this account.
func TestGetTrades(t *testing.T) {
	t.Parallel()
	_, err := e.GetTrades(t.Context(), &GetTradesParams{})
	assert.ErrorIs(t, err, errIncompleteArguments)

	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)

	_, err = e.GetTrades(t.Context(), &GetTradesParams{Symbol: "BTCUSDT"})
	assert.NoError(t, err)
}

func TestCreateNewOCOOrder(t *testing.T) {
	t.Parallel()
	_, err := e.CreateNewOCOOrder(t.Context(),
		&OCOOrderInputParams{
			StopPrice: 1000,
			Side:      order.Buy.String(),
			Quantity:  0.0000001,
			Price:     1232334.00,
		})
	assert.ErrorIs(t, err, errIncompleteArguments)

	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)

	_, err = e.CreateNewOCOOrder(t.Context(),
		&OCOOrderInputParams{
			Symbol:               "XTZUSD",
			Price:                100,
			StopPrice:            3,
			StopLimitPrice:       2.5,
			Side:                 order.Buy.String(),
			Quantity:             1,
			StopLimitTimeInForce: "GTC",
			RecvWindow:           6000,
		})
	assert.ErrorContains(t, err, "Precision is over the maximum defined for this asset.")
}

var ocoOrderJSON = `{
	"orderListId": 27,
	"contingencyType": "OCO",
	"listStatusType": "EXEC_STARTED",
	"listOrderStatus": "EXECUTING",
	"listClientOrderId": "h2USkA5YQpaXHPIrkd96xE",
	"transactionTime": 1565245656253,
	"symbol": "LTCBTC",
	"orders": [
	  {
		"symbol": "LTCBTC",
		"orderId": 4,
		"clientOrderId": "qD1gy3kc3Gx0rihm9Y3xwS"
	  },
	  {
		"symbol": "LTCBTC",
		"orderId": 5,
		"clientOrderId": "ARzZ9I00CPM8i3NhmU9Ega"
	  }
	]
  }`

func TestGetOCOOrder(t *testing.T) {
	t.Parallel()
	var resp OCOOrderResponse
	require.NoError(t, json.Unmarshal([]byte(ocoOrderJSON), &resp))
	_, err := e.GetOCOOrder(t.Context(), &GetOCOOrderRequestParams{})
	assert.ErrorIs(t, err, errIncompleteArguments)

	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)

	_, err = e.GetOCOOrder(t.Context(), &GetOCOOrderRequestParams{
		OrderListID: "123445",
	})
	assert.ErrorContains(t, err, "Order list does not exist.")
}

func TestGetAllOCOOrder(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.GetAllOCOOrder(t.Context(), &OCOOrdersRequestParams{})
	if er != nil {
		t.Error("Binanceus GetAllOCOOrder() error", er)
	}
}

func TestGetOpenOCOOrders(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.GetOpenOCOOrders(t.Context(), 0)
	if er != nil {
		t.Error("Binanceus GetOpenOCOOrders() error", er)
	}
}

func TestCancelOCOOrder(t *testing.T) {
	t.Parallel()
	_, err := e.CancelOCOOrder(t.Context(), &OCOOrdersDeleteRequestParams{})
	assert.ErrorIs(t, err, errIncompleteArguments)
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
	_, err = e.CancelOCOOrder(t.Context(), &OCOOrdersDeleteRequestParams{
		Symbol:      "BTCUSDT",
		OrderListID: 123456,
	})
	assert.NoError(t, err)
}

// OTC end Points test code.
func TestGetSupportedCoinPairs(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.GetSupportedCoinPairs(t.Context(), currency.Pair{Base: currency.BTC, Quote: currency.USDT})
	if er != nil {
		t.Error("Binanceus GetSupportedCoinPairs() error", er)
	}
}

func TestRequestForQuote(t *testing.T) {
	t.Parallel()
	_, err := e.RequestForQuote(t.Context(), &RequestQuoteParams{ToCoin: "BTC", RequestCoin: "USDT", RequestAmount: 1})
	assert.ErrorIs(t, err, errMissingFromCoinName)
	_, err = e.RequestForQuote(t.Context(), &RequestQuoteParams{FromCoin: "ETH", RequestCoin: "USDT", RequestAmount: 1})
	assert.ErrorIs(t, err, errMissingToCoinName)
	_, err = e.RequestForQuote(t.Context(), &RequestQuoteParams{FromCoin: "ETH", ToCoin: "BTC", RequestCoin: "USDT"})
	assert.ErrorIs(t, err, errMissingRequestAmount)
	_, err = e.RequestForQuote(t.Context(), &RequestQuoteParams{FromCoin: "ETH", ToCoin: "BTC", RequestAmount: 1})
	assert.ErrorIs(t, err, errMissingRequestCoin)

	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)

	_, err = e.RequestForQuote(t.Context(), &RequestQuoteParams{FromCoin: "BTC", ToCoin: "USDT", RequestCoin: "BTC", RequestAmount: 1})
	assert.NoError(t, err)
}

var testPlaceOTCTradeOrderJSON = `{
    "orderId": "10002349",
    "createTime": 1641906714,
    "orderStatus": "PROCESS"
}`

func TestPlaceOTCTradeOrder(t *testing.T) {
	t.Parallel()
	var resp OTCTradeOrderResponse
	require.NoError(t, json.Unmarshal([]byte(testPlaceOTCTradeOrderJSON), &resp))
	_, err := e.PlaceOTCTradeOrder(t.Context(), "")
	assert.ErrorIs(t, err, errMissingQuoteID)
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
	_, err = e.PlaceOTCTradeOrder(t.Context(), "15848701022")
	assert.ErrorContains(t, err, "-9000")
}

var testGetOTCTradeOrderJSON = `{
    "quoteId": "4e5446f2cc6f44ab86ab02abf19a2fd2",
    "orderId": "10002349", 
    "orderStatus": "SUCCESS",
    "fromCoin": "BTC",
    "fromAmount": 1,
    "toCoin": "USDT",
    "toAmount": 50550.26,
    "ratio": 50550.26,
    "inverseRatio": 0.00001978,
    "createTime": 1641806714
}`

func TestGetOTCTradeOrder(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	var val OTCTradeOrder
	er := json.Unmarshal([]byte(testGetOTCTradeOrderJSON), &val)
	if er != nil {
		t.Error("Binanceus JSON GetOTCTradeOrder() error", er)
	}
	_, er = e.GetOTCTradeOrder(t.Context(), 10002349)
	if er != nil && !strings.Contains(er.Error(), "status code: 400") {
		t.Error("Binanceus GetOTCTradeOrder() error ", er)
	}
}

var getAllOTCTradeOrders = `[
    {
        "quoteId": "4e5446f2cc6f44ab86ab02abf19a2fd2",
        "orderId": "10002349", 
        "orderStatus": "SUCCESS",
        "fromCoin": "BTC",
        "fromAmount": 1,
        "toCoin": "USDT",
        "toAmount": 50550.26,
        "ratio": 50550.26,
        "inverseRatio": 0.00001978,
        "createTime": 1641806714
    },
    {
        "quoteId": "15848645308",
        "orderId": "10002380", 
        "orderStatus": "PROCESS",
        "fromCoin": "SHIB",
        "fromAmount": 10000,
        "toCoin": "KSHIB",
        "toAmount": 10,
        "ratio": 0.001,
        "inverseRatio": 1000,
        "createTime": 1641916714
    }
]
`

func TestGetAllOTCTradeOrders(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	var orders []OTCTradeOrder
	er := json.Unmarshal([]byte(getAllOTCTradeOrders), &orders)
	if er != nil {
		t.Error(er)
	}
	_, er = e.GetAllOTCTradeOrders(t.Context(), &OTCTradeOrderRequestParams{})
	if er != nil {
		t.Error("Binanceus GetAllOTCTradeOrders() error", er)
	}
}

var ocbsTradeOrderJSON = `
{
  "quoteId": "4e5446f2cc6f44ab86ab02abf19abvd",
  "orderId": "1000238000", 
  "orderStatus": "FAIL",
  "fromCoin": "USD",
  "fromAmount": 1000.5,
  "toCoin": "ETH",
  "toAmount": 0.5,
  "feeCoin": "USD",
  "feeAmount": 0.5,
  "ratio": 2000,
  "createTime": 1641916714
}`

func TestGetAllOCBSTradeOrders(t *testing.T) {
	t.Parallel()
	var orderDetail OCBSOrder
	if er := json.Unmarshal([]byte(ocbsTradeOrderJSON), &orderDetail); er != nil {
		t.Error("Binanceus decerializing to OCBSOrder error", er)
	}
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	if _, er := e.GetAllOCBSTradeOrders(t.Context(), OCBSOrderRequestParams{}); er != nil {
		t.Error("Binanceus GetAllOCBSTradeOrders() error", er)
	}
}

func TestGetAssetFeesAndWalletStatus(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.GetAssetFeesAndWalletStatus(t.Context())
	if er != nil {
		t.Error("Binanceus GetAssetFeesAndWalletStatus()  error", er)
	}
}

func TestWithdrawCrypto(t *testing.T) {
	t.Parallel()

	_, err := e.WithdrawCrypto(t.Context(), &withdraw.Request{})
	assert.ErrorIs(t, err, errMissingRequiredArgumentCoin)
	_, err = e.WithdrawCrypto(t.Context(), &withdraw.Request{
		Currency: currency.BTC,
	})
	assert.ErrorIs(t, err, errMissingRequiredArgumentNetwork)
	params := &withdraw.Request{
		Currency: currency.BTC,
		Crypto: withdraw.CryptoRequest{
			Chain: "BSC",
		},
	}
	_, err = e.WithdrawCrypto(t.Context(), params)
	assert.ErrorIs(t, err, errMissingRequiredParameterAddress)
	params.Crypto.Address = "1234567"
	_, err = e.WithdrawCrypto(t.Context(), params)
	assert.ErrorIs(t, err, errAmountValueMustBeGreaterThan0)
	params.Amount = 1
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e, canManipulateRealOrders)
	_, err = e.WithdrawCrypto(t.Context(), params)
	assert.ErrorContains(t, err, "You are not authorized to execute this request.")
}

func TestFiatWithdrawalHistory(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.FiatWithdrawalHistory(t.Context(), &FiatWithdrawalRequestParams{
		FiatCurrency: "USDT",
	})
	if er != nil {
		t.Errorf("%s FiatWithdrawalHistory() error %v", e.Name, er)
	}
}

func TestDepositHistory(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.DepositHistory(t.Context(), currency.USD, 1, time.Time{}, time.Time{}, 0, 100)
	if er != nil {
		t.Error("Binanceus DepositHistory() error", er)
	}
}

func TestFiatDepositHistory(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.FiatDepositHistory(t.Context(), &FiatWithdrawalRequestParams{})
	if er != nil {
		t.Error("Binanceus FiatDepositHistory() error", er)
	}
}

// WEBSOCKET support testing
// Since both binance and Binance US has same websocket functions,
// the tests functions are also similar

// TestWebsocketStreamKey  this test mmethod handles the
// creating, updating, and deleting of user stream key or "listenKey"
// all the three methods in one test methods.
func TestWebsocketStreamKey(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	_, er := e.GetWsAuthStreamKey(t.Context())
	if er != nil {
		t.Error("Binanceus GetWsAuthStreamKey() error", er)
	}
	er = e.MaintainWsAuthStreamKey(t.Context())
	if er != nil {
		t.Error("Binanceus MaintainWsAuthStreamKey() error", er)
	}
	er = e.CloseUserDataStream(t.Context())
	if er != nil {
		t.Error("Binanceus CloseUserDataStream() error", er)
	}
}

var subscriptionRequestString = `{
	"method": "SUBSCRIBE",
	"params": [
	  "btcusdt@aggTrade",
	  "btcusdt@depth"
	],
	"id": 1
  }`

func TestWebsocketSubscriptionHandling(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	rawData := []byte(subscriptionRequestString)
	err := e.wsHandleData(rawData)
	if err != nil {
		t.Error("Binanceus wsHandleData() error", err)
	}
}

func TestWebsocketUnsubscriptionHandling(t *testing.T) {
	pressXToJSON := []byte(`{
	"method": "UNSUBSCRIBE",
	"params": [
		"btcusdt@depth"
	],
	"id": 312
	}`)
	err := e.wsHandleData(pressXToJSON)
	if err != nil {
		t.Error(err)
	}
}

func TestGetSubscriptions(t *testing.T) {
	t.Parallel()
	if _, err := e.GetSubscriptions(); err != nil {
		t.Error("Binanceus GetSubscriptions() error", err)
	}
}

var ticker24hourChangeStream = `{
	"stream":"btcusdt@ticker",
	"data" :{
		"e": "24hrTicker",  
		"E": 1234567891,     
		"s": "BNBBTC",      
		"p": "0.0015",      
		"P": "250.00",      
		"w": "0.0018",      
		"x": "0.0009",      
		"c": "0.0025",      
		"Q": "10",          
		"b": "0.0024",       
		"B": "10",           
		"a": "0.0026",       
		"A": "100",          
		"o": "0.0010",      
		"h": "0.0025",      
		"l": "0.0010",      
		"v": "10000",        
		"q": "18",           
		"O": 0,             
		"C": 8640000011,      
		"F": 0,             
		"L": 18150,         
		"n": 18151           
  }
}`

func TestWebsocketTickerUpdate(t *testing.T) {
	t.Parallel()
	if err := e.wsHandleData([]byte(ticker24hourChangeStream)); err != nil {
		t.Error("Binanceus wsHandleData() for Ticker 24h Change Stream", err)
	}
}

func TestWebsocketKlineUpdate(t *testing.T) {
	t.Parallel()
	pressXToJSON := []byte(`
	{
		"stream":"btcusdt@kline_1m",
		"data":{
			"e": "kline",     
			"E": 1234567891,   
			"s": "BNBBTC",    
			"k": {
				"t": 1234000001, 
				"T": 1234600001, 
				"s": "BNBBTC",  
				"i": "1m",      
				"f": 100,       
				"L": 200,       
				"o": "0.0010",  
				"c": "0.0020",  
				"h": "0.0025",  
				"l": "0.0015",  
				"v": "1000",    
				"n": 100,       
				"x": false,     
				"q": "1.0000",  
				"V": "500",     
				"Q": "0.500",   
				"B": "123456"   
	  			}
			}
		}`)
	if err := e.wsHandleData(pressXToJSON); err != nil {
		t.Error("Binanceus wsHandleData() btcusdt@kline_1m stream data conversion ", err)
	}
}

func TestWebsocketStreamTradeUpdate(t *testing.T) {
	t.Parallel()
	pressXToJSON := []byte(`{"stream":"btcusdt@trade","data":{
	  "e": "trade",     
	  "E": 123456789,   
	  "s": "BNBBTC",    
	  "t": 12345,       
	  "p": "0.001",     
	  "q": "100",
	  "b": 88,        
	  "a": 50,          
	  "T": 123456785,
	  "m": true,        
	  "M": true         
	}}`)
	if err := e.wsHandleData(pressXToJSON); err != nil {
		t.Error("Binanceus wsHandleData() error", err)
	}
}

// TestWsDepthUpdate copied from the Binance Test
func TestWebsocketOrderBookDepthDiffStream(t *testing.T) {
	binanceusOrderBookLock.Lock()
	defer binanceusOrderBookLock.Unlock()
	e.setupOrderbookManager(t.Context())
	seedLastUpdateID := int64(161)
	book := OrderBook{
		Asks: []orderbook.Level{
			{Price: 6621.80000000, Amount: 0.00198100},
			{Price: 6622.14000000, Amount: 4.00000000},
			{Price: 6622.46000000, Amount: 2.30000000},
			{Price: 6622.47000000, Amount: 1.18633300},
			{Price: 6622.64000000, Amount: 4.00000000},
			{Price: 6622.73000000, Amount: 0.02900000},
			{Price: 6622.76000000, Amount: 0.12557700},
			{Price: 6622.81000000, Amount: 2.08994200},
			{Price: 6622.82000000, Amount: 0.01500000},
			{Price: 6623.17000000, Amount: 0.16831300},
		},
		Bids: []orderbook.Level{
			{Price: 6621.55000000, Amount: 0.16356700},
			{Price: 6621.45000000, Amount: 0.16352600},
			{Price: 6621.41000000, Amount: 0.86091200},
			{Price: 6621.25000000, Amount: 0.16914100},
			{Price: 6621.23000000, Amount: 0.09193600},
			{Price: 6621.22000000, Amount: 0.00755100},
			{Price: 6621.13000000, Amount: 0.08432000},
			{Price: 6621.03000000, Amount: 0.00172000},
			{Price: 6620.94000000, Amount: 0.30506700},
			{Price: 6620.93000000, Amount: 0.00200000},
		},
		LastUpdateID: seedLastUpdateID,
	}
	update1 := []byte(`{"stream":"btcusdt@depth","data":{
	  "e": "depthUpdate", 
	  "E": 1234567891,     
	  "s": "BTCUSDT",      
	  "U": 157,           
	  "u": 160,           
	  "b": [              
		["6621.45", "0.3"]
	  ],
	  "a": [              
		["6622.46", "1.5"]
	  ]
	}}`)

	p := currency.NewPairWithDelimiter("BTC", "USDT", "-")
	if err := e.SeedLocalCacheWithBook(p, &book); err != nil {
		t.Fatal(err)
	}
	if err := e.wsHandleData(update1); err != nil {
		t.Fatal(err)
	}
	e.obm.state[currency.BTC][currency.USDT][asset.Spot].fetchingBook = false
	ob, err := e.Websocket.Orderbook.GetOrderbook(p, asset.Spot)
	if err != nil {
		t.Fatal(err)
	}
	if exp, got := seedLastUpdateID, ob.LastUpdateID; got != exp {
		t.Fatalf("Unexpected Last update id of orderbook for old update. Exp: %d, got: %d", exp, got)
	}
	if exp, got := 2.3, ob.Asks[2].Amount; got != exp {
		t.Fatalf("Ask altered by outdated update. Exp: %f, got %f", exp, got)
	}
	if exp, got := 0.163526, ob.Bids[1].Amount; got != exp {
		t.Fatalf("Bid altered by outdated update. Exp: %f, got %f", exp, got)
	}
	update2 := []byte(`{
		"stream":"btcusdt@depth","data":{
			"e": "depthUpdate", 
			"E": 1234567892,     
			"s": "BTCUSDT",      
			"U": 161,           
			"u": 165,           
			"b": [           
				["6621.45", "0.163526"]
			],
			"a": [             
				["6622.46", "2.3"], 
				["6622.47", "1.9"]
			]
		}
	}`)
	if err = e.wsHandleData(update2); err != nil {
		t.Error("Binanceus wshandlerData error", err)
	}
	ob, err = e.Websocket.Orderbook.GetOrderbook(p, asset.Spot)
	if err != nil {
		t.Fatal("Binanceus GetOrderBook error", err)
	}
	if exp, got := int64(165), ob.LastUpdateID; got != exp {
		t.Fatalf("Binanceus Unexpected Last update id of orderbook for new update. Exp: %d, got: %d", exp, got)
	}
	if exp, got := 2.3, ob.Asks[2].Amount; got != exp {
		t.Fatalf("Binanceus Unexpected Ask amount. Exp: %f, got %f", exp, got)
	}
	if exp, got := 1.9, ob.Asks[3].Amount; got != exp {
		t.Fatalf("Binanceus Unexpected Ask amount. Exp: %f, got %f", exp, got)
	}
	if exp, got := 0.163526, ob.Bids[1].Amount; got != exp {
		t.Fatalf("Binanceus Unexpected Bid amount. Exp: %f, got %f", exp, got)
	}
	e.obm.state[currency.BTC][currency.USDT][asset.Spot].lastUpdateID = 0
}

// TestWebsocketPartialOrderBookDepthStream copied from the Binance Test
func TestWebsocketPartialOrderBookDepthStream(t *testing.T) {
	t.Parallel()
	update1 := []byte(`{"stream":"btcusdt@depth5","data":
	{
		"lastUpdateId": 160,
		"bids": [           
		  [
			"0.0024",       
			"10"            
		  ]
		],
		"asks": [           
		  [
			"0.0026",       
			"100"           
		  ]
		]
	  }}`)
	var err error
	if err = e.wsHandleData(update1); err != nil {
		t.Error("Binanceus Partial Order Book Depth Sream error", err)
	}
	update2 := []byte(`{
		"stream":"btcusdt@depth10",
		"data":{
			"lastUpdateId": 160, 
			"bids": [            
					[
						"0.0024",        
						"10"             
					]
			],
			"asks": [            
				[
					"0.0026",        
					"100"            
				]
			]
		}
	  }`)
	if err = e.wsHandleData(update2); err != nil {
		t.Error("Binanceus Partial Order Book Depth Sream error", err)
	}
}

func TestWebsocketBookTicker(t *testing.T) {
	t.Parallel()
	bookTickerJSON := []byte(
		`{
		"stream": "btcusdt@bookTicker",
		"data": {
			"u":400900217,   
			"s":"BNBUSDT",  
			"b":"25.35190000",
			"B":"31.21000000",
			"a":"25.36520000",
			"A":"40.66000000" 
		}
	  }`)
	if err := e.wsHandleData(bookTickerJSON); err != nil {
		t.Error("Binanceus Book Ticker error", err)
	}
	bookTickerForAllSymbols := []byte(`
	{
		"stream" : "!bookTicker",
		"data":{
			"u":400900217,    
			"s":"BNBUSDT",    
			"b":"25.35190000",
			"B":"31.21000000",
			"a":"25.36520000",
			"A":"40.66000000" 
		}
	}`)
	if err := e.wsHandleData(bookTickerForAllSymbols); err != nil {
		t.Error("Binanceus Web socket Book ticker for all symbols error", err)
	}
}

func TestWebsocketAggTrade(t *testing.T) {
	t.Parallel()
	aggTradejson := []byte(
		`{  
			"stream":"btcusdt@aggTrade", 
			"data": {
				"e": "aggTrade",  
				"E": 1672515782136,   
				"s": "BNBBTC",
				"a": 12345,       
				"p": "0.001",     
				"q": "100",   
				"f": 100,     
				"l": 105,   
				"T": 1672515782136,
				"m": true,
				"M": true         
			}
	   }`)
	if err := e.wsHandleData(aggTradejson); err != nil {
		t.Error("Binanceus Aggregated Trade Order Json() error", err)
	}
}

var balanceUpdateInputJSON = `
{
	"stream":"jTfvpakT2yT0hVIo5gYWVihZhdM2PrBgJUZ5PyfZ4EVpCkx4Uoxk5timcrQc",
	"data":{
		"e": "balanceUpdate",         
		"E": 1573200697110,           
		"a": "BTC",                   
		"d": "100.00000000",          
		"T": 1573200697068}}`

func TestWebsocketBalanceUpdate(t *testing.T) {
	t.Parallel()
	thejson := []byte(balanceUpdateInputJSON)
	if err := e.wsHandleData(thejson); err != nil {
		t.Error(err)
	}
}

var listStatusUserDataStreamPayload = `
{
	"stream":"jTfvpakT2yT0hVIo5gYWVihZhdM2PrBgJUZ5PyfZ4EVpCkx4Uoxk5timcrQc",
	"data":{
		"e": "listStatus",                
		"E": 1564035303637,               
		"s": "ETHBTC",                    
		"g": 2,                           
		"c": "OCO",                       
		"l": "EXEC_STARTED",              
		"L": "EXECUTING",                 
		"r": "NONE",                      
		"C": "F4QN4G8DlFATFlIUQ0cjdD",    
		"T": 1564035303625,               
		"O": [                            
			{
				"s": "ETHBTC",                
				"i": 17,                      
				"c": "AJYsMjErWJesZvqlJCTUgL" 
			},
			{
				"s": "ETHBTC",
				"i": 18,
				"c": "bfYPSQdLoqAJeNrOr9adzq"
			}
		]
	}
}`

func TestWebsocketListStatus(t *testing.T) {
	t.Parallel()
	if err := e.wsHandleData([]byte(listStatusUserDataStreamPayload)); err != nil {
		t.Error(err)
	}
}

func TestExecutionTypeToOrderStatus(t *testing.T) {
	type TestCases struct {
		Case   string
		Result order.Status
	}
	testCases := []TestCases{
		{Case: "NEW", Result: order.New},
		{Case: "PARTIALLY_FILLED", Result: order.PartiallyFilled},
		{Case: "FILLED", Result: order.Filled},
		{Case: "CANCELED", Result: order.Cancelled},
		{Case: "PENDING_CANCEL", Result: order.PendingCancel},
		{Case: "REJECTED", Result: order.Rejected},
		{Case: "EXPIRED", Result: order.Expired},
		{Case: "LOL", Result: order.UnknownStatus},
	}
	for i := range testCases {
		result, _ := stringToOrderStatus(testCases[i].Case)
		if result != testCases[i].Result {
			t.Errorf("Binanceus expected: %v, received: %v", testCases[i].Result, result)
		}
	}
}

var websocketDepthUpdate = []byte(
	`{
		"e": "depthUpdate",
		"E": 12345678911,    
		"s": "BNBBTC",     
		"U": 157,          
		"u": 160,          
		"b": [             
		  [
			"0.0024",      
			"10"           
		  ]
		],
		"a": [             
		  [
			"0.0026",      
			"100"          
		  ]
		]
	  }
	`)

func TestProcessUpdate(t *testing.T) {
	t.Parallel()
	binanceusOrderBookLock.Lock()
	defer binanceusOrderBookLock.Unlock()
	e.setupOrderbookManager(t.Context())
	p := currency.NewBTCUSDT()
	var depth WebsocketDepthStream
	err := json.Unmarshal(websocketDepthUpdate, &depth)
	if err != nil {
		t.Fatal(err)
	}
	err = e.obm.stageWsUpdate(&depth, p, asset.Spot)
	if err != nil {
		t.Fatal(err)
	}
	err = e.obm.fetchBookViaREST(p)
	if err != nil {
		t.Fatal(err)
	}
	err = e.obm.cleanup(p)
	if err != nil {
		t.Fatal(err)
	}
	e.obm.state[currency.BTC][currency.USDT][asset.Spot].lastUpdateID = 0
}

func TestWebsocketOrderExecutionReport(t *testing.T) {
	payload := []byte(`{"stream":"jTfvpakT2yT0hVIo5gYWVihZhdM2PrBgJUZ5PyfZ4EVpCkx4Uoxk5timcrQc","data":{"e":"executionReport","E":1616627567900,"s":"BTCUSDT","c":"c4wyKsIhoAaittTYlIVLqk","S":"BUY","o":"LIMIT","f":"GTC","q":"0.00028400","p":"52789.10000000","P":"0.00000000","F":"0.00000000","g":-1,"C":"","x":"NEW","X":"NEW","r":"NONE","i":5340845958,"l":"0.00000000","z":"0.00000000","L":"0.00000000","n":"0","N":"BTC","T":1616627567900,"t":-1,"I":11388173160,"w":true,"m":false,"M":false,"O":1616627567900,"Z":"0.00000000","Y":"0.00000000","Q":"0.00000000"}}`)
	expectedResult := order.Detail{
		Price:           52789.1,
		Amount:          0.00028400,
		RemainingAmount: 0.00028400,
		CostAsset:       currency.USDT,
		FeeAsset:        currency.BTC,
		Exchange:        "Binanceus",
		OrderID:         "5340845958",
		ClientOrderID:   "c4wyKsIhoAaittTYlIVLqk",
		Type:            order.Limit,
		Side:            order.Buy,
		Status:          order.New,
		AssetType:       asset.Spot,
		Date:            time.UnixMilli(1616627567900),
		LastUpdated:     time.UnixMilli(1616627567900),
		Pair:            currency.NewBTCUSDT(),
	}
	for len(e.Websocket.DataHandler) > 0 {
		<-e.Websocket.DataHandler
	}
	err := e.wsHandleData(payload)
	if err != nil {
		t.Fatal(err)
	}
	res := <-e.Websocket.DataHandler
	switch r := res.(type) {
	case *order.Detail:
		if !reflects.DeepEqual(expectedResult, *r) {
			t.Errorf("Binanceus Results do not match:\nexpected: %v\nreceived: %v", expectedResult, *r)
		}
	default:
		t.Fatalf("Binanceus expected type order.Detail, found %T", res)
	}
	payload = []byte(`{"stream":"jTfvpakT2yT0hVIo5gYWVihZhdM2PrBgJUZ5PyfZ4EVpCkx4Uoxk5timcrQc","data":{"e":"executionReport","E":1616633041556,"s":"BTCUSDT","c":"YeULctvPAnHj5HXCQo9Mob","S":"BUY","o":"LIMIT","f":"GTC","q":"0.00028600","p":"52436.85000000","P":"0.00000000","F":"0.00000000","g":-1,"C":"","x":"TRADE","X":"FILLED","r":"NONE","i":5341783271,"l":"0.00028600","z":"0.00028600","L":"52436.85000000","n":"0.00000029","N":"BTC","T":1616633041555,"t":726946523,"I":11390206312,"w":false,"m":false,"M":true,"O":1616633041555,"Z":"14.99693910","Y":"14.99693910","Q":"0.00000000"}}`)
	err = e.wsHandleData(payload)
	if err != nil {
		t.Fatal("Binanceus OrderExecutionReport json conversion error", err)
	}
}

func TestWebsocketOutboundAccountPosition(t *testing.T) {
	t.Parallel()
	payload := []byte(`{"stream":"jTfvpakT2yT0hVIo5gYWVihZhdM2PrBgJUZ5PyfZ4EVpCkx4Uoxk5timcrQc","data":{"e":"outboundAccountPosition","E":1616628815745,"u":1616628815745,"B":[{"a":"BTC","f":"0.00225109","l":"0.00123000"},{"a":"BNB","f":"0.00000000","l":"0.00000000"},{"a":"USDT","f":"54.43390661","l":"0.00000000"}]}}`)
	if err := e.wsHandleData(payload); err != nil {
		t.Fatal("Binanceus testing \"outboundAccountPosition\" data conversion error", err)
	}
}

func TestGetAvailableTransferChains(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	if _, er := e.GetAvailableTransferChains(t.Context(), currency.BTC); er != nil {
		t.Error("Binanceus GetAvailableTransferChains() error", er)
	}
}

func TestQuickEnableCryptoWithdrawal(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	if er := e.QuickEnableCryptoWithdrawal(t.Context()); er != nil && !strings.Contains(er.Error(), "unexpected end of JSON input") {
		t.Errorf("Binanceus QuickEnableCryptoWithdrawal() expecting %s, but found %v", "unexpected end of JSON input", er)
	}
}

func TestQuickDisableCryptoWithdrawal(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	if er := e.QuickDisableCryptoWithdrawal(t.Context()); er != nil && !strings.Contains(er.Error(), "unexpected end of JSON input") {
		t.Errorf("Binanceus QuickDisableCryptoWithdrawal() expecting %s, but found %v", "unexpected end of JSON input", er)
	}
}

func TestGetUsersSpotAssetSnapshot(t *testing.T) {
	t.Parallel()
	sharedtestvalues.SkipTestIfCredentialsUnset(t, e)
	if _, er := e.GetUsersSpotAssetSnapshot(t.Context(), time.Time{}, time.Time{}, 10, 6); er != nil {
		t.Error("Binanceus GetUsersSpotAssetSnapshot() error", er)
	}
}

func TestGetCurrencyTradeURL(t *testing.T) {
	t.Parallel()
	testexch.UpdatePairsOnce(t, e)
	for _, a := range e.GetAssetTypes(false) {
		pairs, err := e.CurrencyPairs.GetPairs(a, false)
		require.NoErrorf(t, err, "cannot get pairs for %s", a)
		require.NotEmptyf(t, pairs, "no pairs for %s", a)
		resp, err := e.GetCurrencyTradeURL(t.Context(), a, pairs[0])
		require.NoError(t, err)
		assert.NotEmpty(t, resp)
	}
}

// TestGetAggregatedTradesBatched exercises TestGetAggregatedTradesBatched to ensure our date and limit scanning works correctly
// This test is susceptible to failure if volumes change a lot, during wash trading or zero-fee periods
// In live tests, 6 hours is expected to return about 1000 records
func TestGetAggregatedTradesBatched(t *testing.T) {
	t.Parallel()
	type testCase struct {
		name    string
		args    *AggregatedTradeRequestParams
		expFunc func(*testing.T, []AggregatedTrade)
	}

	var tests []testCase
	start := time.Now().Add(-time.Hour * 24 * 90).Truncate(time.Minute) // 3 months ago
	tests = []testCase{
		{
			name: "batch with timerange",
			args: &AggregatedTradeRequestParams{StartTime: start, EndTime: start.Add(6 * time.Hour)},
			expFunc: func(t *testing.T, results []AggregatedTrade) {
				t.Helper()
				require.NotEmpty(t, results, "must have records")
				assert.Less(t, len(results), 10000, "should return a quantity below a sane threshold of records")
				assert.WithinDuration(t, results[len(results)-1].TimeStamp.Time(), start, 6*time.Hour, "last record should be within range of start time")
			},
		},
		{
			name: "custom limit with start time set, no end time",
			args: &AggregatedTradeRequestParams{StartTime: start, Limit: 2042},
			expFunc: func(t *testing.T, results []AggregatedTrade) {
				t.Helper()
				// 2000 records in was about 32 hours in 2025; Adjust if BinanceUS enters a phase of zero-fees or low-volume
				require.Equal(t, 2042, len(results), "must return exactly the limit number of records")
				assert.WithinDuration(t, results[len(results)-1].TimeStamp.Time(), start, 72*time.Hour, "last record should be within 72 hours of start time")
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			tt.args.Symbol = currency.NewBTCUSDT()
			results, err := e.GetAggregateTrades(t.Context(), tt.args)
			require.NoError(t, err)
			tt.expFunc(t, results)
		})
	}
}
