package binance

import (
	"regexp"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"github.com/c9s/bbgo/pkg/fixedpoint"
	"github.com/c9s/bbgo/pkg/types"
)

var jsCommentTrimmer = regexp.MustCompile("(?m)//.*$")

func TestMarginResponseParsing(t *testing.T) {
	type testcase struct {
		input string
	}

	var testcases = []testcase{
		{
			input: `{
			  "e": "executionReport",
			  "E": 1608545107403,
			  "s": "BNBUSDT",
			  "c": "ios_0de017ca7ceb4102b3baa664feb46e65",
			  "S": "BUY",
			  "o": "MARKET",
			  "f": "GTC",
			  "q": "0.50000000",
			  "p": "0.00000000",
			  "P": "0.00000000",
			  "F": "0.00000000",
			  "g": -1,
			  "C": "",
			  "x": "NEW",
			  "X": "NEW",
			  "r": "NONE",
			  "i": 1147335332,
			  "l": "0.00000000",
			  "z": "0.00000000",
			  "L": "0.00000000",
			  "n": "0",
			  "N": null,
			  "T": 1608545107401,
			  "t": -1,
			  "I": 2387303818,
			  "w": true,
			  "m": false,
			  "M": false,
			  "O": 1608545107401,
			  "Z": "0.00000000",
			  "Y": "0.00000000",
			  "Q": "0.00000000"
			}`,
		},
		{
			input: `{
			  "e": "executionReport",
			  "E": 1608545107403,
			  "s": "BNBUSDT",
			  "c": "ios_0de017ca7ceb4102b3baa664feb46e65",
			  "S": "BUY",
			  "o": "MARKET",
			  "f": "GTC",
			  "q": "0.50000000",
			  "p": "0.00000000",
			  "P": "0.00000000",
			  "F": "0.00000000",
			  "g": -1,
			  "C": "",
			  "x": "TRADE",
			  "X": "FILLED",
			  "r": "NONE",
			  "i": 1147335332,
			  "l": "0.50000000",
			  "z": "0.50000000",
			  "L": "33.85710000",
			  "n": "0.00037500",
			  "N": "BNB",
			  "T": 1608545107401,
			  "t": 98414801,
			  "I": 2387303819,
			  "w": false,
			  "m": false,
			  "M": true,
			  "O": 1608545107401,
			  "Z": "16.92855000",
			  "Y": "16.92855000",
			  "Q": "0.00000000"
			}`,
		},
		{
			input: `{
			  "e": "outboundAccountInfo",
			  "E": 1608545107403,
			  "m": 9,
			  "t": 10,
			  "b": 0,
			  "s": 0,
			  "T": true,
			  "W": true,
			  "D": true,
			  "u": 1608545107401,
			  "B": [
				{
				  "a": "BNB",
				  "f": "89.99345616",
				  "l": "12.00000000"
				},
				{
				  "a": "USDT",
				  "f": "0.16410063",
				  "l": "0.00000000"
				}
			  ],
			  "P": []
			}`,
		},
		{
			input: `{
				"e":"outboundAccountPosition",
				"E":1608545107403,
				"u":1608545107401,
				"B":[{"a":"BNB","f":"89.99345616","l":"12.00000000"},{"a":"USDT","f":"0.16410063","l":"0.00000000"}]
			}`,
		},
	}

	for _, testcase := range testcases {
		payload := testcase.input
		payload = jsCommentTrimmer.ReplaceAllLiteralString(payload, "")
		event, err := parseWebSocketEvent([]byte(payload))
		assert.NoError(t, err)
		assert.NotNil(t, event)
	}
}

func TestParseOrderUpdate(t *testing.T) {
	payload := `{
  "e": "executionReport",        // Event type
  "E": 1499405658658,            // Event time
  "s": "ETHBTC",                 // Symbol
  "c": "mUvoqJxFIILMdfAW5iGSOW", // Client order ID
  "S": "BUY",                    // Side
  "o": "LIMIT",                  // Order type
  "f": "GTC",                    // Time in force
  "q": "1.00000000",             // Order quantity
  "p": "0.10264410",             // Order price
  "P": "0.222",                  // Stop price
  "F": "0.00000000",             // Iceberg quantity
  "g": -1,                       // OrderListId
  "C": null,                     // Original client order ID; This is the ID of the order being canceled
  "x": "NEW",                    // Current execution type
  "X": "NEW",                    // Current order status
  "r": "NONE",                   // Order reject reason; will be an error code.
  "i": 4293153,                  // Order ID
  "l": "0.00000000",             // Last executed quantity
  "z": "0.00000000",             // Cumulative filled quantity
  "L": "0.00000001",             // Last executed price
  "n": "0",                      // Commission amount
  "N": null,                     // Commission asset
  "T": 1499405658657,            // Transaction time
  "t": -1,                       // Trade ID
  "I": 8641984,                  // Ignore
  "w": true,                     // Is the order on the book?
  "m": false,                    // Is this trade the maker side?
  "M": true,                     // Ignore
  "O": 1499405658657,            // Order creation time
  "Z": "0.1",                    // Cumulative quote asset transacted quantity
  "Y": "0.00000000",             // Last quote asset transacted quantity (i.e. lastPrice * lastQty)
  "Q": "2.0"                     // Quote Order Quantity
}`

	payload = jsCommentTrimmer.ReplaceAllLiteralString(payload, "")

	event, err := parseWebSocketEvent([]byte(payload))
	assert.NoError(t, err)
	assert.NotNil(t, event)

	executionReport, ok := event.(*ExecutionReportEvent)
	assert.True(t, ok)
	assert.NotNil(t, executionReport)

	assert.Equal(t, executionReport.Symbol, "ETHBTC")
	assert.Equal(t, executionReport.Side, "BUY")
	assert.Equal(t, executionReport.ClientOrderID, "mUvoqJxFIILMdfAW5iGSOW")
	assert.Equal(t, executionReport.OriginalClientOrderID, "")
	assert.Equal(t, executionReport.OrderType, "LIMIT")
	assert.Equal(t, int64(1499405658657), executionReport.OrderCreationTime.Time().UnixMilli())
	assert.Equal(t, executionReport.TimeInForce, "GTC")
	assert.Equal(t, executionReport.IcebergQuantity, fixedpoint.MustNewFromString("0.00000000"))
	assert.Equal(t, executionReport.OrderQuantity, fixedpoint.MustNewFromString("1.00000000"))
	assert.Equal(t, executionReport.QuoteOrderQuantity, fixedpoint.MustNewFromString("2.0"))
	assert.Equal(t, executionReport.OrderPrice, fixedpoint.MustNewFromString("0.10264410"))
	assert.Equal(t, executionReport.StopPrice, fixedpoint.MustNewFromString("0.222"))
	assert.Equal(t, executionReport.IsOnBook, true)
	assert.Equal(t, executionReport.IsMaker, false)
	assert.Equal(t, executionReport.Ignore, true)
	assert.Equal(t, executionReport.CommissionAmount, fixedpoint.MustNewFromString("0"))
	assert.Equal(t, executionReport.CommissionAsset, "")
	assert.Equal(t, executionReport.CurrentExecutionType, "NEW")
	assert.Equal(t, executionReport.CurrentOrderStatus, "NEW")
	assert.Equal(t, executionReport.OrderID, int64(4293153))
	assert.Equal(t, executionReport.Ignored, int64(8641984))
	assert.Equal(t, executionReport.TradeID, int64(-1))
	assert.Equal(t, int64(1499405658657), executionReport.TransactionTime.Time().UnixMilli())
	assert.Equal(t, fixedpoint.MustNewFromString("0.00000000"), executionReport.LastExecutedQuantity)
	assert.Equal(t, fixedpoint.MustNewFromString("0.00000001"), executionReport.LastExecutedPrice)
	assert.Equal(t, fixedpoint.MustNewFromString("0.00000000"), executionReport.CumulativeFilledQuantity)
	assert.Equal(t, executionReport.CumulativeQuoteAssetTransactedQuantity, fixedpoint.MustNewFromString("0.1"))
	assert.Equal(t, executionReport.LastQuoteAssetTransactedQuantity, fixedpoint.MustNewFromString("0.00000000"))

	orderUpdate, err := executionReport.Order()
	assert.NoError(t, err)
	assert.NotNil(t, orderUpdate)
}

func TestFuturesResponseParsing(t *testing.T) {
	type testcase struct {
		input string
	}

	var testcases = []testcase{
		{
			input: `{
			  "e": "ORDER_TRADE_UPDATE",
			  "T": 1639933384755,
			  "E": 1639933384763,
			  "o": {
				"s": "BTCUSDT",
				"c": "x-NSUYEBKMe60cf610-f5c7-49a4-9c1",
				"S": "SELL",
				"o": "MARKET",
				"f": "GTC",
				"q": "0.001",
				"p": "0",
				"ap": "0",
				"sp": "0",
				"x": "NEW",
				"X": "NEW",
				"i": 38541728873,
				"l": "0",
				"z": "0",
				"L": "0",
				"T": 1639933384755,
				"t": 0,
				"b": "0",
				"a": "0",
				"m": false,
				"R": false,
				"wt": "CONTRACT_PRICE",
				"ot": "MARKET",
				"ps": "BOTH",
				"cp": false,
				"rp": "0",
				"pP": false,
				"si": 0,
				"ss": 0
			  }
			}`,
		},
		{
			input: `{
			  "e": "ACCOUNT_UPDATE",
			  "T": 1639933384755,
			  "E": 1639933384763,
			  "a": {
				"B": [
				  {
					"a": "USDT",
					"wb": "86.94966888",
					"cw": "86.94966888",
					"bc": "0"
				  }
				],
				"P": [
				  {
					"s": "BTCUSDT",
					"pa": "-0.001",
					"ep": "47202.40000",
					"cr": "7.78107001",
					"up": "-0.00233523",
					"mt": "cross",
					"iw": "0",
					"ps": "BOTH",
					"ma": "USDT"
				  }
				],
				"m": "ORDER"
			  }
			}`,
		},
		{
			input: `{
			  "e": "ORDER_TRADE_UPDATE",
			  "T": 1639933384755,
			  "E": 1639933384763,
			  "o": {
				"s": "BTCUSDT",
				"c": "x-NSUYEBKMe60cf610-f5c7-49a4-9c1",
				"S": "SELL",
				"o": "MARKET",
				"f": "GTC",
				"q": "0.001",
				"p": "0",
				"ap": "47202.40000",
				"sp": "0",
				"x": "TRADE",
				"X": "FILLED",
				"i": 38541728873,
				"l": "0.001",
				"z": "0.001",
				"L": "47202.40",
				"n": "0.01888095",
				"N": "USDT",
				"T": 1639933384755,
				"t": 1741505949,
				"b": "0",
				"a": "0",
				"m": false,
				"R": false,
				"wt": "CONTRACT_PRICE",
				"ot": "MARKET",
				"ps": "BOTH",
				"cp": false,
				"rp": "0",
				"pP": false,
				"si": 0,
				"ss": 0
			  }
			}`,
		},
		{
			input: `{
				"e": "TRADE_LITE",
				"E": 1721895408092,
				"T": 1721895408214,
				"s": "BTCUSDT",
				"q": "0.001",
				"p": "0",
				"m": false,
				"c": "z8hcUoOsqEdKMeKPSABslD",
				"S": "BUY",
				"L": "64089.20",
				"l": "0.040",
				"t": 109100866,
				"i": 8886774
			}`,
		},
	}

	for _, testcase := range testcases {
		payload := testcase.input
		payload = jsCommentTrimmer.ReplaceAllLiteralString(payload, "")
		event, err := parseWebSocketEvent([]byte(payload))
		assert.NoError(t, err)
		assert.NotNil(t, event)
	}
}

func TestParseOrderFuturesUpdate(t *testing.T) {
	payload := `{
				  "e": "ORDER_TRADE_UPDATE",
				  "T": 1639933384755,
				  "E": 1639933384763,
				  "o": {
					"s": "BTCUSDT",
					"c": "x-NSUYEBKMe60cf610-f5c7-49a4-9c1",
					"S": "SELL",
					"o": "MARKET",
					"f": "GTC",
					"q": "0.001",
					"p": "0",
					"ap": "47202.40000",
					"sp": "0",
					"x": "TRADE",
					"X": "FILLED",
					"i": 38541728873,
					"l": "0.001",
					"z": "0.001",
					"L": "47202.40",
					"n": "0.01888095",
					"N": "USDT",
					"T": 1639933384755,
					"t": 1741505949,
					"b": "0",
					"a": "0",
					"m": false,
					"R": false,
					"wt": "CONTRACT_PRICE",
					"ot": "MARKET",
					"ps": "BOTH",
					"cp": false,
					"rp": "0",
					"pP": false,
					"si": 0,
					"ss": 0
				  }
				}`

	payload = jsCommentTrimmer.ReplaceAllLiteralString(payload, "")

	event, err := parseWebSocketEvent([]byte(payload))
	assert.NoError(t, err)
	assert.NotNil(t, event)

	orderTradeEvent, ok := event.(*OrderTradeUpdateEvent)
	assert.True(t, ok)
	assert.NotNil(t, orderTradeEvent)

	assert.Equal(t, "BTCUSDT", orderTradeEvent.OrderTrade.Symbol)
	assert.Equal(t, "SELL", orderTradeEvent.OrderTrade.Side)
	assert.Equal(t, "x-NSUYEBKMe60cf610-f5c7-49a4-9c1", orderTradeEvent.OrderTrade.ClientOrderID)
	assert.Equal(t, "MARKET", orderTradeEvent.OrderTrade.OrderType)
	assert.Equal(t, types.NewMillisecondTimestampFromInt(1639933384763), orderTradeEvent.Time)
	assert.Equal(t, types.MillisecondTimestamp(time.UnixMilli(1639933384755)), orderTradeEvent.OrderTrade.OrderTradeTime)
	assert.Equal(t, fixedpoint.MustNewFromString("0.001"), orderTradeEvent.OrderTrade.OriginalQuantity)
	assert.Equal(t, fixedpoint.MustNewFromString("0.001"), orderTradeEvent.OrderTrade.OrderLastFilledQuantity)
	assert.Equal(t, fixedpoint.MustNewFromString("0.001"), orderTradeEvent.OrderTrade.OrderFilledAccumulatedQuantity)
	assert.Equal(t, "TRADE", orderTradeEvent.OrderTrade.CurrentExecutionType)
	assert.Equal(t, "FILLED", orderTradeEvent.OrderTrade.CurrentOrderStatus)
	assert.Equal(t, fixedpoint.MustNewFromString("47202.40"), orderTradeEvent.OrderTrade.LastFilledPrice)
	assert.Equal(t, int64(38541728873), orderTradeEvent.OrderTrade.OrderId)
	assert.Equal(t, int64(1741505949), orderTradeEvent.OrderTrade.TradeId)

	orderUpdate, err := orderTradeEvent.OrderFutures()
	assert.NoError(t, err)
	assert.NotNil(t, orderUpdate)
}
