package currency

import (
	"strconv"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/thrasher-corp/gocryptotrader/encoding/json"
)

const (
	defaultPair           = "BTCUSD"
	defaultPairWDelimiter = "BTC-USD"
)

func TestLower(t *testing.T) {
	t.Parallel()
	pair, err := NewPairFromString(defaultPair)
	if err != nil {
		t.Fatal(err)
	}
	expected, err := NewPairFromString(defaultPair)
	if err != nil {
		t.Fatal(err)
	}
	if actual := pair.Lower(); actual.String() != expected.Lower().String() {
		t.Errorf("Lower(): %s was not equal to expected value: %s",
			actual,
			expected.Lower())
	}
}

func TestUpper(t *testing.T) {
	t.Parallel()
	pair, err := NewPairFromString(defaultPair)
	if err != nil {
		t.Fatal(err)
	}
	expected, err := NewPairFromString(defaultPair)
	if err != nil {
		t.Fatal(err)
	}
	if actual := pair.Upper(); actual.String() != expected.String() {
		t.Errorf("Upper(): %s was not equal to expected value: %s",
			actual, expected)
	}
}

func TestPairUnmarshalJSON(t *testing.T) {
	var p Pair
	assert.NoError(t, p.UnmarshalJSON([]byte(`"btc_usd"`)), "UnmarshalJSON should not error")
	assert.Equal(t, "btc", p.Base.String(), "Base should be correct")
	assert.Equal(t, "usd", p.Quote.String(), "Quote should be correct")
	assert.Equal(t, "_", p.Delimiter, "Delimiter should be correct")

	assert.ErrorIs(t, p.UnmarshalJSON([]byte(`"btcusd"`)), errCannotCreatePair, "UnmarshalJSON with no delimiter should error")

	assert.NoError(t, p.UnmarshalJSON([]byte(`""`)), "UnmarshalJSON should not error on empty value")
	assert.Equal(t, EMPTYPAIR, p, "UnmarshalJSON empty value should give EMPTYPAIR")
	assert.NoError(t, p.UnmarshalJSON([]byte(`null`)), "UnmarshalJSON should not error on empty value")
	assert.Equal(t, EMPTYPAIR, p, "UnmarshalJSON null value should give EMPTYPAIR")
}

func TestPairMarshalJSON(t *testing.T) {
	quickstruct := struct {
		Pair *Pair `json:"superPair"`
	}{
		&Pair{Base: BTC, Quote: USD, Delimiter: "-"},
	}

	encoded, err := json.Marshal(quickstruct)
	if err != nil {
		t.Fatal("Pair MarshalJSON() error", err)
	}

	expected := `{"superPair":"BTC-USD"}`
	if string(encoded) != expected {
		t.Errorf("Pair MarshalJSON() error expected %s but received %s",
			expected, string(encoded))
	}
}

func TestIsCryptoPair(t *testing.T) {
	if !NewPair(BTC, LTC).IsCryptoPair() {
		t.Error("TestIsCryptoPair. Expected true result")
	}

	if NewBTCUSD().IsCryptoPair() {
		t.Error("TestIsCryptoPair. Expected false result")
	}
}

func TestIsCryptoFiatPair(t *testing.T) {
	if !NewBTCUSD().IsCryptoFiatPair() {
		t.Error("TestIsCryptoPair. Expected true result")
	}

	if NewPair(BTC, LTC).IsCryptoFiatPair() {
		t.Error("TestIsCryptoPair. Expected false result")
	}
}

func TestIsFiatPair(t *testing.T) {
	if !NewPair(AUD, USD).IsFiatPair() {
		t.Error("TestIsFiatPair. Expected true result")
	}

	if NewPair(BTC, AUD).IsFiatPair() {
		t.Error("TestIsFiatPair. Expected false result")
	}
}

func TestIsCryptoStablePair(t *testing.T) {
	if !NewBTCUSDT().IsCryptoStablePair() {
		t.Error("TestIsCryptoStablePair. Expected true result")
	}

	if !NewPair(DAI, USDT).IsCryptoStablePair() {
		t.Error("TestIsCryptoStablePair. Expected true result")
	}

	if NewPair(AUD, USDT).IsCryptoStablePair() {
		t.Error("TestIsCryptoStablePair. Expected false result")
	}
}

func TestIsStablePair(t *testing.T) {
	if !NewPair(USDT, DAI).IsStablePair() {
		t.Error("TestIsStablePair. Expected true result")
	}

	if NewPair(USDT, AUD).IsStablePair() {
		t.Error("TestIsStablePair. Expected false result")
	}

	if NewPair(USDT, LTC).IsStablePair() {
		t.Error("TestIsStablePair. Expected false result")
	}
}

func TestString(t *testing.T) {
	t.Parallel()
	pair := NewBTCUSD()
	if actual, expected := defaultPair, pair.String(); actual != expected {
		t.Errorf("String(): %s was not equal to expected value: %s",
			actual, expected)
	}
}

func TestFirstCurrency(t *testing.T) {
	t.Parallel()
	pair := NewBTCUSD()
	if actual, expected := pair.Base, BTC; !actual.Equal(expected) {
		t.Errorf(
			"GetFirstCurrency(): %s was not equal to expected value: %s",
			actual, expected,
		)
	}
}

func TestSecondCurrency(t *testing.T) {
	t.Parallel()
	pair := NewBTCUSD()
	if actual, expected := pair.Quote, USD; !actual.Equal(expected) {
		t.Errorf(
			"GetSecondCurrency(): %s was not equal to expected value: %s",
			actual, expected,
		)
	}
}

func TestPair(t *testing.T) {
	t.Parallel()
	pair := NewBTCUSD()
	if actual, expected := pair.String(), defaultPair; actual != expected {
		t.Errorf(
			"Pair(): %s was not equal to expected value: %s",
			actual, expected,
		)
	}
}

func TestDisplay(t *testing.T) {
	t.Parallel()
	_, err := NewPairDelimiter(defaultPairWDelimiter, "wow")
	if err == nil {
		t.Fatal("error cannot be nil")
	}
	pair, err := NewPairDelimiter(defaultPairWDelimiter, "-")
	if err != nil {
		t.Fatal(err)
	}
	actual := pair.String()
	expected := defaultPairWDelimiter
	if actual != expected {
		t.Errorf(
			"Pair(): %s was not equal to expected value: %s",
			actual, expected,
		)
	}

	actual = EMPTYFORMAT.Format(pair)
	expected = "btcusd"
	if actual != expected {
		t.Errorf(
			"Pair(): %s was not equal to expected value: %s",
			actual, expected,
		)
	}

	actual = pair.Format(PairFormat{Delimiter: "~", Uppercase: true}).String()
	expected = "BTC~USD"
	if actual != expected {
		t.Errorf(
			"Pair(): %s was not equal to expected value: %s",
			actual, expected,
		)
	}
}

func TestEquall(t *testing.T) {
	t.Parallel()
	pair := NewBTCUSD()
	secondPair := NewBTCUSD()
	actual := pair.Equal(secondPair)
	expected := true
	if actual != expected {
		t.Errorf(
			"Equal(): %v was not equal to expected value: %v",
			actual, expected,
		)
	}

	secondPair.Quote = ETH
	actual = pair.Equal(secondPair)
	expected = false
	if actual != expected {
		t.Errorf(
			"Equal(): %v was not equal to expected value: %v",
			actual, expected,
		)
	}

	secondPair = NewPair(USD, BTC)
	actual = pair.Equal(secondPair)
	expected = false
	if actual != expected {
		t.Errorf(
			"Equal(): %v was not equal to expected value: %v",
			actual, expected,
		)
	}
}

func TestEqualIncludeReciprocal(t *testing.T) {
	t.Parallel()
	pair := NewBTCUSD()
	secondPair := NewBTCUSD()
	actual := pair.EqualIncludeReciprocal(secondPair)
	expected := true
	if actual != expected {
		t.Errorf(
			"Equal(): %v was not equal to expected value: %v",
			actual, expected,
		)
	}

	secondPair.Quote = ETH
	actual = pair.EqualIncludeReciprocal(secondPair)
	expected = false
	if actual != expected {
		t.Errorf(
			"Equal(): %v was not equal to expected value: %v",
			actual, expected,
		)
	}

	secondPair = NewPair(USD, BTC)
	actual = pair.EqualIncludeReciprocal(secondPair)
	expected = true
	if actual != expected {
		t.Errorf(
			"Equal(): %v was not equal to expected value: %v",
			actual, expected,
		)
	}
}

func TestSwap(t *testing.T) {
	t.Parallel()
	pair := NewBTCUSD()
	if actual, expected := pair.Swap().String(), "USDBTC"; actual != expected {
		t.Errorf(
			"TestSwap: %s was not equal to expected value: %s",
			actual, expected,
		)
	}
}

func TestEmpty(t *testing.T) {
	t.Parallel()
	pair := NewBTCUSD()
	if pair.IsEmpty() {
		t.Error("Empty() returned true when the pair was initialised")
	}

	p := NewPair(NewCode(""), NewCode(""))
	if !p.IsEmpty() {
		t.Error("Empty() returned true when the pair wasn't initialised")
	}
}

func TestNewPair(t *testing.T) {
	t.Parallel()
	pair := NewBTCUSD()
	if expected, actual := defaultPair, pair.String(); actual != expected {
		t.Errorf(
			"Pair(): %s was not equal to expected value: %s",
			actual, expected,
		)
	}
}

func TestNewPairWithDelimiter(t *testing.T) {
	t.Parallel()
	pair := NewPairWithDelimiter("BTC", "USD", "-test-")
	actual := pair.String()
	expected := "BTC-test-USD"
	if actual != expected {
		t.Errorf(
			"Pair(): %s was not equal to expected value: %s",
			actual, expected,
		)
	}

	pair = NewPairWithDelimiter("BTC", "USD", "")
	actual = pair.String()
	expected = defaultPair
	if actual != expected {
		t.Errorf(
			"Pair(): %s was not equal to expected value: %s",
			actual, expected,
		)
	}
}

func TestNewPairDelimiter(t *testing.T) {
	t.Parallel()
	_, err := NewPairDelimiter("", "")
	require.ErrorIs(t, err, errEmptyPairString)

	_, err = NewPairDelimiter("BTC_USD", "")
	require.ErrorIs(t, err, errDelimiterCannotBeEmpty)

	_, err = NewPairDelimiter("BTC_USD", "wow")
	require.ErrorIs(t, err, errDelimiterNotFound)

	_, err = NewPairDelimiter("BTC_USD", " ")
	require.ErrorIs(t, err, errDelimiterNotFound)

	pair, err := NewPairDelimiter(defaultPairWDelimiter, "-")
	require.NoError(t, err)
	assert.Equal(t, defaultPairWDelimiter, pair.String())
	assert.Equal(t, "-", pair.Delimiter)

	pair, err = NewPairDelimiter("BTC-MOVE-0626", "-")
	require.NoError(t, err)
	assert.Equal(t, "BTC-MOVE-0626", pair.String())

	pair, err = NewPairDelimiter("sETH-USDT", "-")
	require.NoError(t, err)
	assert.Equal(t, "SETH-USDT", pair.String(), "If any upper case is found in set this forces the pair to be uppercase")
}

func TestNewPairFromString(t *testing.T) {
	t.Parallel()
	pairStr := defaultPairWDelimiter
	pair, err := NewPairFromString(pairStr)
	if err != nil {
		t.Fatal(err)
	}
	actual := pair.String()
	expected := defaultPairWDelimiter
	if actual != expected {
		t.Errorf(
			"Pair(): %s was not equal to expected value: %s",
			actual, expected,
		)
	}

	pairStr = defaultPair
	pair, err = NewPairFromString(pairStr)
	if err != nil {
		t.Fatal(err)
	}
	actual = pair.String()
	expected = defaultPair
	if actual != expected {
		t.Errorf(
			"Pair(): %s was not equal to expected value: %s",
			actual, expected,
		)
	}
	pairMap := map[string]Pair{
		"BTC_USDT-20230630-45000-C": {Base: NewCode("BTC"), Delimiter: UnderscoreDelimiter, Quote: NewCode("USDT-20230630-45000-C")},
		"BTC-USD-221007":            {Base: NewCode("BTC"), Delimiter: DashDelimiter, Quote: NewCode("USD-221007")},
		"IHT_ETH":                   {Base: NewCode("IHT"), Delimiter: UnderscoreDelimiter, Quote: NewCode("ETH")},
		"BTC-USD-220930-30000-P":    {Base: NewCode("BTC"), Delimiter: DashDelimiter, Quote: NewCode("USD-220930-30000-P")},
		"XBTUSDTM":                  {Base: NewCode("XBT"), Delimiter: "", Quote: NewCode("USDTM")},
		"BTC-PERPETUAL":             {Base: NewCode("BTC"), Delimiter: DashDelimiter, Quote: NewCode("PERPETUAL")},
		"SOL-21OCT22-20-C":          {Base: NewCode("SOL"), Delimiter: DashDelimiter, Quote: NewCode("21OCT22-20-C")},
		"SOL-FS-30DEC22_28OCT22":    {Base: NewCode("SOL"), Delimiter: DashDelimiter, Quote: NewCode("FS-30DEC22_28OCT22")},
	}
	for key, expectedPair := range pairMap {
		pair, err = NewPairFromString(key)
		if err != nil {
			t.Fatal(err)
		}
		if !pair.Equal(expectedPair) || pair.Delimiter != expectedPair.Delimiter {
			t.Errorf("Pair(): %s was not equal to expected value: %s", pair.String(), expectedPair.String())
		}
	}
}

func TestNewPairFromFormattedPairs(t *testing.T) {
	t.Parallel()
	p1, err := NewPairDelimiter("BTC-USDT", "-")
	if err != nil {
		t.Fatal(err)
	}
	p2, err := NewPairDelimiter("LTC-USD", "-")
	if err != nil {
		t.Fatal(err)
	}
	pairs := Pairs{
		p1,
		p2,
	}

	p, err := NewPairFromFormattedPairs("BTCUSDT", pairs, PairFormat{
		Uppercase: true,
	})
	if err != nil {
		t.Fatal(err)
	}

	if p.String() != "BTC-USDT" {
		t.Error("TestNewPairFromFormattedPairs: Expected currency was not found")
	}

	p, err = NewPairFromFormattedPairs("btcusdt", pairs, PairFormat{Uppercase: false})
	if err != nil {
		t.Fatal(err)
	}

	if p.String() != "BTC-USDT" {
		t.Error("TestNewPairFromFormattedPairs: Expected currency was not found")
	}

	// Now a wrong one, will default to NewPairFromString
	p, err = NewPairFromFormattedPairs("ethusdt", pairs, EMPTYFORMAT)
	if err != nil {
		t.Fatal(err)
	}

	if p.String() != "ethusdt" && p.Base.String() != "eth" {
		t.Error("TestNewPairFromFormattedPairs: Expected currency was not found")
	}
}

func TestContainsCurrency(t *testing.T) {
	p := NewBTCUSD()

	if !p.Contains(BTC) {
		t.Error("TestContains: Expected currency was not found")
	}

	if p.Contains(ETH) {
		t.Error("TestContains: Non-existent currency was found")
	}
}

func TestFormatPairs(t *testing.T) {
	_, err := FormatPairs([]string{""}, "-")
	assert.ErrorIs(t, err, errEmptyPairString, "Should error on empty string")

	_, err = FormatPairs([]string{"NO"}, "")
	assert.ErrorIs(t, err, errNoDelimiter, "Should error on a small string with no delimiter")

	newP, err := FormatPairs([]string{defaultPairWDelimiter}, "-")
	assert.NoError(t, err)
	require.NotEmpty(t, newP)
	assert.Equal(t, defaultPairWDelimiter, newP[0].String(), "Pair should format correctly")

	newP, err = FormatPairs([]string{defaultPair}, "")
	assert.NoError(t, err)
	require.NotEmpty(t, newP)
	assert.Equal(t, defaultPair, newP[0].String(), "Pair should format correctly")

	newP, err = FormatPairs([]string{"ETHUSD"}, "")
	assert.NoError(t, err)
	require.NotEmpty(t, newP)
	assert.Equal(t, "ETHUSD", newP[0].String(), "Pair should format correctly")
}

func TestCopyPairFormat(t *testing.T) {
	pairOne := NewBTCUSD()
	pairOne.Delimiter = "-"

	var pairs []Pair
	pairs = append(pairs, pairOne, NewPair(LTC, USD))

	testPair := NewBTCUSD()
	testPair.Delimiter = "~"

	result := CopyPairFormat(testPair, pairs, false)
	if result.String() != defaultPairWDelimiter {
		t.Error("TestCopyPairFormat: Expected pair was not found")
	}

	np := NewPair(ETH, USD)
	result = CopyPairFormat(np, pairs, true)
	if result.String() != "" {
		t.Error("TestCopyPairFormat: Unexpected non empty pair returned")
	}
}

func TestPairsToStringArray(t *testing.T) {
	var pairs Pairs
	pairs = append(pairs, NewBTCUSD())

	expected := []string{defaultPair}
	actual := pairs.Strings()

	if actual[0] != expected[0] {
		t.Error("TestPairsToStringArray: Unexpected values")
	}
}

func TestRandomPairFromPairs(t *testing.T) {
	// Test that an empty pairs array returns an empty currency pair
	var emptyPairs Pairs
	result, err := emptyPairs.GetRandomPair()
	require.ErrorIs(t, err, ErrCurrencyPairsEmpty)

	if !result.IsEmpty() {
		t.Error("TestRandomPairFromPairs: Unexpected values")
	}

	// Test that a populated pairs array returns a non-empty currency pair
	var pairs Pairs
	pairs = append(pairs, NewBTCUSD())
	result, err = pairs.GetRandomPair()
	require.NoError(t, err)

	if result.IsEmpty() {
		t.Error("TestRandomPairFromPairs: Unexpected values")
	}

	// Test that a populated pairs array over a number of attempts returns ALL
	// currency pairs
	pairs = append(pairs, NewPair(ETH, USD))
	expectedResults := make(map[string]bool)
	for range 50 {
		result, err = pairs.GetRandomPair()
		require.NoError(t, err)

		expectedResults[result.String()] = true
	}

	for x := range pairs {
		if !expectedResults[pairs[x].String()] {
			t.Error("TestRandomPairFromPairs: Unexpected values")
		}
	}
}

func TestIsInvalid(t *testing.T) {
	p := NewPair(LTC, LTC)
	if !p.IsInvalid() {
		t.Error("IsInvalid() error expect true but received false")
	}
}

func TestMatchPairsWithNoDelimiter(t *testing.T) {
	p1, err := NewPairDelimiter("BTC-USDT", "-")
	if err != nil {
		t.Fatal(err)
	}
	p2, err := NewPairDelimiter("LTC-USD", "-")
	if err != nil {
		t.Fatal(err)
	}
	p3, err := NewPairFromStrings("EQUAD", "BTC")
	if err != nil {
		t.Fatal(err)
	}
	p4, err := NewPairFromStrings("HTDF", "USDT")
	if err != nil {
		t.Fatal(err)
	}
	p5, err := NewPairFromStrings("BETHER", "ETH")
	if err != nil {
		t.Fatal(err)
	}
	pairs := Pairs{
		p1,
		p2,
		p3,
		p4,
		p5,
	}

	p, err := MatchPairsWithNoDelimiter("BTCUSDT", pairs, PairFormat{
		Uppercase: true,
	})
	if err != nil {
		t.Fatal(err)
	}
	if p.Quote.String() != "USDT" && p.Base.String() != "BTC" {
		t.Error("unexpected response")
	}

	p, err = MatchPairsWithNoDelimiter("EQUADBTC", pairs, PairFormat{
		Uppercase: true,
	})
	if err != nil {
		t.Fatal(err)
	}
	if p.Base.String() != "EQUAD" && p.Quote.String() != "BTC" {
		t.Errorf("unexpected response base: %v quote: %v", p.Base.String(), p.Quote.String())
	}

	p, err = MatchPairsWithNoDelimiter("EQUADBTC", pairs, PairFormat{
		Uppercase: true,
		Delimiter: "/",
	})
	if err != nil {
		t.Fatal(err)
	}
	if p.Base.String() != "EQUAD" && p.Quote.String() != "BTC" {
		t.Errorf("unexpected response base: %v quote: %v", p.Base.String(), p.Quote.String())
	}

	p, err = MatchPairsWithNoDelimiter("HTDFUSDT", pairs, PairFormat{
		Uppercase: true,
		Delimiter: "/",
	})
	if err != nil {
		t.Fatal(err)
	}
	if p.Base.String() != "HTDF" && p.Quote.String() != "USDT" {
		t.Errorf("unexpected response base: %v quote: %v", p.Base.String(), p.Quote.String())
	}

	p, err = MatchPairsWithNoDelimiter("BETHERETH", pairs, PairFormat{
		Uppercase: true,
		Delimiter: "/",
	})
	if err != nil {
		t.Fatal(err)
	}
	if p.Base.String() != "BETHER" && p.Quote.String() != "ETH" {
		t.Errorf("unexpected response base: %v quote: %v", p.Base.String(), p.Quote.String())
	}
}

func TestPairFormat_Format(t *testing.T) {
	type fields struct {
		Uppercase bool
		Delimiter string
		Separator string
	}
	tests := []struct {
		name   string
		fields fields
		arg    Pair
		want   string
	}{
		{
			name:   "empty",
			fields: fields{},
			arg:    EMPTYPAIR,
			want:   "",
		},
		{
			name:   "empty format",
			fields: fields{},
			arg: Pair{
				Delimiter: "<>",
				Base:      AAA,
				Quote:     BTC,
			},
			want: "aaabtc",
		},
		{
			name: "format",
			fields: fields{
				Uppercase: true,
				Delimiter: "!!!",
			},
			arg: Pair{
				Delimiter: "<>",
				Base:      AAA,
				Quote:     BTC,
			},
			want: "AAA!!!BTC",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			f := &PairFormat{
				Uppercase: tt.fields.Uppercase,
				Delimiter: tt.fields.Delimiter,
				Separator: tt.fields.Separator,
			}
			if got := f.Format(tt.arg); got != tt.want {
				t.Errorf("PairFormat.Format() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestOther(t *testing.T) {
	received, err := NewPair(DAI, XRP).Other(DAI)
	if err != nil {
		t.Fatal(err)
	}
	if !received.Equal(XRP) {
		t.Fatal("unexpected value")
	}
	received, err = NewPair(DAI, XRP).Other(XRP)
	if err != nil {
		t.Fatal(err)
	}
	if !received.Equal(DAI) {
		t.Fatal("unexpected value")
	}
	_, err = NewPair(DAI, XRP).Other(BTC)
	require.ErrorIs(t, err, ErrCurrencyCodeEmpty)
}

func TestIsPopulated(t *testing.T) {
	if receiver := NewBTCUSDT().IsPopulated(); !receiver {
		t.Fatal("unexpected value")
	}
	if receiver := NewPair(BTC, NewCode("USD-1245")).IsPopulated(); !receiver {
		t.Fatal("unexpected value")
	}
	if receiver := NewPair(BTC, EMPTYCODE).IsPopulated(); receiver {
		t.Fatal("unexpected value")
	}
	if receiver := NewPair(EMPTYCODE, EMPTYCODE).IsPopulated(); receiver {
		t.Fatal("unexpected value")
	}
}

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

	p := NewBTCUSDT()
	testCases := []struct {
		Pair           Pair
		currency       Code
		market         bool
		selling        bool
		expectedParams *OrderParameters
		expectedError  error
	}{
		{expectedError: ErrCurrencyPairEmpty},
		{Pair: p, expectedError: ErrCurrencyCodeEmpty},
		{Pair: p, currency: XRP, selling: true, market: true, expectedError: ErrCurrencyNotAssociatedWithPair},

		{Pair: p, currency: BTC, selling: true, market: true, expectedParams: &OrderParameters{SellingCurrency: BTC, PurchasingCurrency: USDT, IsBuySide: false, IsAskLiquidity: false, Pair: p}},
		{Pair: p, currency: BTC, selling: false, market: true, expectedParams: &OrderParameters{SellingCurrency: USDT, PurchasingCurrency: BTC, IsBuySide: true, IsAskLiquidity: true, Pair: p}},
		{Pair: p, currency: BTC, selling: true, market: false, expectedParams: &OrderParameters{SellingCurrency: BTC, PurchasingCurrency: USDT, IsBuySide: false, IsAskLiquidity: true, Pair: p}},
		{Pair: p, currency: BTC, selling: false, market: false, expectedParams: &OrderParameters{SellingCurrency: USDT, PurchasingCurrency: BTC, IsBuySide: true, IsAskLiquidity: false, Pair: p}},

		{Pair: p, currency: USDT, selling: true, market: true, expectedParams: &OrderParameters{SellingCurrency: USDT, PurchasingCurrency: BTC, IsBuySide: true, IsAskLiquidity: true, Pair: p}},
		{Pair: p, currency: USDT, selling: false, market: true, expectedParams: &OrderParameters{SellingCurrency: BTC, PurchasingCurrency: USDT, IsBuySide: false, IsAskLiquidity: false, Pair: p}},
		{Pair: p, currency: USDT, selling: true, market: false, expectedParams: &OrderParameters{SellingCurrency: USDT, PurchasingCurrency: BTC, IsBuySide: true, IsAskLiquidity: false, Pair: p}},
		{Pair: p, currency: USDT, selling: false, market: false, expectedParams: &OrderParameters{SellingCurrency: BTC, PurchasingCurrency: USDT, IsBuySide: false, IsAskLiquidity: true, Pair: p}},
	}

	for i, tc := range testCases {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			t.Parallel()
			var resp *OrderParameters
			var err error
			switch {
			case tc.market && tc.selling:
				resp, err = tc.Pair.MarketSellOrderParameters(tc.currency)
			case tc.market && !tc.selling:
				resp, err = tc.Pair.MarketBuyOrderParameters(tc.currency)
			case !tc.market && tc.selling:
				resp, err = tc.Pair.LimitSellOrderParameters(tc.currency)
			case !tc.market && !tc.selling:
				resp, err = tc.Pair.LimitBuyOrderParameters(tc.currency)
			}
			require.ErrorIs(t, err, tc.expectedError)

			if tc.expectedParams == nil {
				if resp != nil {
					t.Fatalf("received %v, expected nil", resp)
				}
				return
			}

			if resp.SellingCurrency != tc.expectedParams.SellingCurrency {
				t.Fatalf("SellingCurrency received %v, expected %v", resp.SellingCurrency, tc.expectedParams.SellingCurrency)
			}

			if resp.PurchasingCurrency != tc.expectedParams.PurchasingCurrency {
				t.Fatalf("PurchasingCurrency received %v, expected %v", resp.PurchasingCurrency, tc.expectedParams.PurchasingCurrency)
			}

			if resp.IsBuySide != tc.expectedParams.IsBuySide {
				t.Fatalf("BuySide received %v, expected %v", resp.IsBuySide, tc.expectedParams.IsBuySide)
			}

			if resp.IsAskLiquidity != tc.expectedParams.IsAskLiquidity {
				t.Fatalf("AskLiquidity received %v, expected %v", resp.IsAskLiquidity, tc.expectedParams.IsAskLiquidity)
			}

			if resp.Pair != tc.expectedParams.Pair {
				t.Fatalf("Pair received %v, expected %v", resp.Pair, tc.expectedParams.Pair)
			}
		})
	}
}

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

	testCases := []struct {
		Pair           Pair
		associate      Pair
		expectedResult bool
	}{
		{Pair: NewBTCUSDT(), associate: NewBTCUSDT(), expectedResult: true},
		{Pair: NewPair(USDT, BTC), associate: NewBTCUSDT(), expectedResult: true},
		{Pair: NewBTCUSDT(), associate: NewPair(USDT, BTC), expectedResult: true},
		{Pair: NewBTCUSDT(), associate: NewPair(XRP, USDT), expectedResult: true},
		{Pair: NewPair(BTC, LTC), associate: NewPair(XRP, USDT), expectedResult: false},
		{Pair: NewPair(MA, LTC), associate: NewPair(LTC, USDT), expectedResult: true},
	}

	for x := range testCases {
		t.Run(strconv.Itoa(x), func(t *testing.T) {
			t.Parallel()
			if testCases[x].Pair.IsAssociated(testCases[x].associate) != testCases[x].expectedResult {
				t.Fatalf("Test %d failed. Expected %v, received %v", x, testCases[x].expectedResult, testCases[x].Pair.IsAssociated(testCases[x].associate))
			}
		})
	}
}

func TestPair_GetFormatting(t *testing.T) {
	t.Parallel()
	pFmt, err := NewBTCUSDT().GetFormatting()
	require.NoError(t, err)
	assert.True(t, pFmt.Uppercase)
	assert.Empty(t, pFmt.Delimiter)

	pFmt, err = NewPairWithDelimiter("eth", "usdt", "/").GetFormatting()
	require.NoError(t, err)
	assert.False(t, pFmt.Uppercase)
	assert.Equal(t, "/", pFmt.Delimiter)

	_, err = NewPairWithDelimiter("eth", "USDT", "/").GetFormatting()
	require.ErrorIs(t, err, errPairFormattingInconsistent)

	pFmt, err = EMPTYPAIR.GetFormatting()
	require.NoError(t, err)
	assert.Equal(t, EMPTYFORMAT, pFmt)

	pFmt, err = NewPairWithDelimiter("eth", "420", "/").GetFormatting()
	require.NoError(t, err)
	assert.False(t, pFmt.Uppercase)

	pFmt, err = NewPairWithDelimiter("ETH", "420", "/").GetFormatting()
	require.NoError(t, err)
	assert.True(t, pFmt.Uppercase)

	pFmt, err = NewPairWithDelimiter("420", "eth", "/").GetFormatting()
	require.NoError(t, err)
	assert.False(t, pFmt.Uppercase)

	pFmt, err = NewPairWithDelimiter("420", "ETH", "/").GetFormatting()
	require.NoError(t, err)
	assert.True(t, pFmt.Uppercase)
}

func TestNewBTCUSD(t *testing.T) {
	t.Parallel()
	p := NewBTCUSD()
	if !p.Base.Equal(BTC) {
		t.Fatal("expected base BTC from function NewBTCUSD")
	}
	if !p.Quote.Equal(USD) {
		t.Fatal("expected quote USD from function NewBTCUSD")
	}
}

func TestNewBTCUSDT(t *testing.T) {
	t.Parallel()
	p := NewBTCUSDT()
	if !p.Base.Equal(BTC) {
		t.Fatal("expected base BTC from function NewBTCUSDT")
	}
	if !p.Quote.Equal(USDT) {
		t.Fatal("expected quote USDT from function NewBTCUSDT")
	}
}
