package bybit

import (
	"fmt"
	"strconv"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"

	"github.com/c9s/bbgo/pkg/exchange/bybit/bybitapi"
	v3 "github.com/c9s/bbgo/pkg/exchange/bybit/bybitapi/v3"
	"github.com/c9s/bbgo/pkg/fixedpoint"
	"github.com/c9s/bbgo/pkg/types"
)

func TestToGlobalMarket(t *testing.T) {
	// sample:
	//{
	//	"Symbol": "BTCUSDT",
	//	"BaseCoin": "BTC",
	//	"QuoteCoin": "USDT",
	//	"Innovation": 0,
	//	"Status": "Trading",
	//	"MarginTrading": "both",
	//	"LotSizeFilter": {
	//	"BasePrecision": 0.000001,
	//		"QuotePrecision": 0.00000001,
	//		"MinOrderQty": 0.000048,
	//		"MaxOrderQty": 71.73956243,
	//		"MinOrderAmt": 1,
	//		"MaxOrderAmt": 2000000
	//	},
	//	"PriceFilter": {
	//		"TickSize": 0.01
	//	}
	//}
	inst := bybitapi.Instrument{
		Symbol:        "BTCUSDT",
		BaseCoin:      "BTC",
		QuoteCoin:     "USDT",
		Innovation:    "0",
		Status:        bybitapi.StatusTrading,
		MarginTrading: "both",
		LotSizeFilter: struct {
			BasePrecision  fixedpoint.Value `json:"basePrecision"`
			QuotePrecision fixedpoint.Value `json:"quotePrecision"`
			MinOrderQty    fixedpoint.Value `json:"minOrderQty"`
			MaxOrderQty    fixedpoint.Value `json:"maxOrderQty"`
			MinOrderAmt    fixedpoint.Value `json:"minOrderAmt"`
			MaxOrderAmt    fixedpoint.Value `json:"maxOrderAmt"`
		}{
			BasePrecision:  fixedpoint.NewFromFloat(0.000001),
			QuotePrecision: fixedpoint.NewFromFloat(0.00000001),
			MinOrderQty:    fixedpoint.NewFromFloat(0.000048),
			MaxOrderQty:    fixedpoint.NewFromFloat(71.73956243),
			MinOrderAmt:    fixedpoint.NewFromInt(1),
			MaxOrderAmt:    fixedpoint.NewFromInt(2000000),
		},
		PriceFilter: struct {
			TickSize fixedpoint.Value `json:"tickSize"`
		}{
			TickSize: fixedpoint.NewFromFloat(0.01),
		},
	}

	exp := types.Market{
		Exchange:        types.ExchangeBybit,
		Symbol:          inst.Symbol,
		LocalSymbol:     inst.Symbol,
		PricePrecision:  8,
		VolumePrecision: 6,
		QuoteCurrency:   inst.QuoteCoin,
		BaseCurrency:    inst.BaseCoin,
		MinNotional:     inst.LotSizeFilter.MinOrderAmt,
		MinAmount:       inst.LotSizeFilter.MinOrderAmt,
		MinQuantity:     inst.LotSizeFilter.MinOrderQty,
		MaxQuantity:     inst.LotSizeFilter.MaxOrderQty,
		StepSize:        inst.LotSizeFilter.BasePrecision,
		MinPrice:        inst.LotSizeFilter.MinOrderAmt,
		MaxPrice:        inst.LotSizeFilter.MaxOrderAmt,
		TickSize:        inst.PriceFilter.TickSize,
	}

	assert.Equal(t, toGlobalMarket(inst), exp)
}

func TestToGlobalTicker(t *testing.T) {
	// sample
	//{
	// 	  "symbol": "BTCUSDT",
	//    "bid1Price": "28995.98",
	//    "bid1Size": "4.741552",
	//    "ask1Price": "28995.99",
	//    "ask1Size": "0.16075",
	//    "lastPrice": "28994",
	//    "prevPrice24h": "29900",
	//    "price24hPcnt": "-0.0303",
	//    "highPrice24h": "30344.78",
	//    "lowPrice24h": "28948.87",
	//    "turnover24h": "184705500.13172874",
	//    "volume24h": "6240.807096",
	//    "usdIndexPrice": "28977.82001643"
	//}
	ticker := bybitapi.Ticker{
		Symbol:        "BTCUSDT",
		Bid1Price:     fixedpoint.NewFromFloat(28995.98),
		Bid1Size:      fixedpoint.NewFromFloat(4.741552),
		Ask1Price:     fixedpoint.NewFromFloat(28995.99),
		Ask1Size:      fixedpoint.NewFromFloat(0.16075),
		LastPrice:     fixedpoint.NewFromFloat(28994),
		PrevPrice24H:  fixedpoint.NewFromFloat(29900),
		Price24HPcnt:  fixedpoint.NewFromFloat(-0.0303),
		HighPrice24H:  fixedpoint.NewFromFloat(30344.78),
		LowPrice24H:   fixedpoint.NewFromFloat(28948.87),
		Turnover24H:   fixedpoint.NewFromFloat(184705500.13172874),
		Volume24H:     fixedpoint.NewFromFloat(6240.807096),
		UsdIndexPrice: fixedpoint.NewFromFloat(28977.82001643),
	}

	timeNow := time.Now()

	exp := types.Ticker{
		Time:   timeNow,
		Volume: ticker.Volume24H,
		Last:   ticker.LastPrice,
		Open:   ticker.PrevPrice24H,
		High:   ticker.HighPrice24H,
		Low:    ticker.LowPrice24H,
		Buy:    ticker.Bid1Price,
		Sell:   ticker.Ask1Price,
	}

	assert.Equal(t, toGlobalTicker(ticker, timeNow), exp)
}

func Test_processMarketBuyQuantity(t *testing.T) {
	t.Run("websocket event", func(t *testing.T) {
		t.Run("Market/Buy/OrderStatusPartiallyFilled", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:          fixedpoint.NewFromFloat(5),
				OrderType:    bybitapi.OrderTypeMarket,
				Side:         bybitapi.SideBuy,
				CumExecValue: fixedpoint.NewFromFloat(200),
				CumExecQty:   fixedpoint.NewFromFloat(2),
				OrderStatus:  bybitapi.OrderStatusPartiallyFilled,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.Qty.Div(o.CumExecValue.Div(o.CumExecQty)), res)
		})

		t.Run("Market/Buy/OrderStatusPartiallyFilledCanceled", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:          fixedpoint.NewFromFloat(5),
				OrderType:    bybitapi.OrderTypeMarket,
				Side:         bybitapi.SideBuy,
				CumExecValue: fixedpoint.NewFromFloat(200),
				CumExecQty:   fixedpoint.NewFromFloat(2),
				OrderStatus:  bybitapi.OrderStatusPartiallyFilled,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.Qty.Div(o.CumExecValue.Div(o.CumExecQty)), res)
		})

		t.Run("Market/Buy/OrderStatusFilled", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:          fixedpoint.NewFromFloat(5),
				OrderType:    bybitapi.OrderTypeMarket,
				Side:         bybitapi.SideBuy,
				CumExecValue: fixedpoint.NewFromFloat(200),
				CumExecQty:   fixedpoint.NewFromFloat(2),
				OrderStatus:  bybitapi.OrderStatusFilled,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.CumExecQty, res)
		})

		t.Run("Market/Buy/OrderStatusCreated", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:          fixedpoint.NewFromFloat(5),
				OrderType:    bybitapi.OrderTypeMarket,
				Side:         bybitapi.SideBuy,
				CumExecValue: fixedpoint.NewFromFloat(200),
				CumExecQty:   fixedpoint.NewFromFloat(2),
				OrderStatus:  bybitapi.OrderStatusCreated,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, fixedpoint.Zero, res)
		})

		t.Run("Market/Buy/OrderStatusNew", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:          fixedpoint.NewFromFloat(5),
				OrderType:    bybitapi.OrderTypeMarket,
				Side:         bybitapi.SideBuy,
				CumExecValue: fixedpoint.NewFromFloat(200),
				CumExecQty:   fixedpoint.NewFromFloat(2),
				OrderStatus:  bybitapi.OrderStatusNew,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, fixedpoint.Zero, res)
		})

		t.Run("Market/Buy/OrderStatusRejected", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:          fixedpoint.NewFromFloat(5),
				OrderType:    bybitapi.OrderTypeMarket,
				Side:         bybitapi.SideBuy,
				CumExecValue: fixedpoint.NewFromFloat(200),
				CumExecQty:   fixedpoint.NewFromFloat(2),
				OrderStatus:  bybitapi.OrderStatusRejected,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, fixedpoint.Zero, res)
		})

		t.Run("Market/Buy/OrderStatusCanceled", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:          fixedpoint.NewFromFloat(5),
				OrderType:    bybitapi.OrderTypeMarket,
				Side:         bybitapi.SideBuy,
				CumExecValue: fixedpoint.NewFromFloat(200),
				CumExecQty:   fixedpoint.NewFromFloat(2),
				OrderStatus:  bybitapi.OrderStatusCancelled,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.Qty, res)
		})

		t.Run("Market/Buy/Unexpected status", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:          fixedpoint.NewFromFloat(5),
				OrderType:    bybitapi.OrderTypeMarket,
				Side:         bybitapi.SideBuy,
				CumExecValue: fixedpoint.NewFromFloat(200),
				CumExecQty:   fixedpoint.NewFromFloat(2),
				OrderStatus:  bybitapi.OrderStatus("unexpected"),
			}
			res, err := processMarketBuyQuantity(o)
			assert.Error(t, err)
			assert.Equal(t, fmt.Errorf("unexpected order status: %s", o.OrderStatus), err)
			assert.Equal(t, fixedpoint.Zero, res)
		})

		t.Run("Market/Buy/CumExecQty zero", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:          fixedpoint.NewFromFloat(5),
				OrderType:    bybitapi.OrderTypeMarket,
				Side:         bybitapi.SideBuy,
				CumExecValue: fixedpoint.NewFromFloat(200),
				CumExecQty:   fixedpoint.Zero,
				OrderStatus:  bybitapi.OrderStatusPartiallyFilled,
			}
			res, err := processMarketBuyQuantity(o)
			assert.Error(t, err)
			assert.Equal(t, fmt.Errorf("CumExecQty shouldn't be zero"), err)
			assert.Equal(t, fixedpoint.Zero, res)
		})

		t.Run("Market/Sell", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:       fixedpoint.NewFromFloat(5.55),
				OrderType: bybitapi.OrderTypeMarket,
				Side:      bybitapi.SideSell,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.Qty, res)
		})

		t.Run("Limit/Buy", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:       fixedpoint.NewFromFloat(5.55),
				OrderType: bybitapi.OrderTypeLimit,
				Side:      bybitapi.SideBuy,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.Qty, res)
		})

		t.Run("Limit/Sell", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:       fixedpoint.NewFromFloat(5.55),
				OrderType: bybitapi.OrderTypeLimit,
				Side:      bybitapi.SideSell,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.Qty, res)
		})
	})

	t.Run("Restful API", func(t *testing.T) {
		t.Run("Market/Buy/OrderStatusPartiallyFilled", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:         fixedpoint.NewFromFloat(200),
				OrderType:   bybitapi.OrderTypeMarket,
				Side:        bybitapi.SideBuy,
				AvgPrice:    fixedpoint.NewFromFloat(25000),
				OrderStatus: bybitapi.OrderStatusPartiallyFilled,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.Qty.Div(o.AvgPrice), res)
		})

		t.Run("Market/Buy/OrderStatusPartiallyFilledCanceled", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:         fixedpoint.NewFromFloat(200),
				OrderType:   bybitapi.OrderTypeMarket,
				Side:        bybitapi.SideBuy,
				AvgPrice:    fixedpoint.NewFromFloat(25000),
				OrderStatus: bybitapi.OrderStatusPartiallyFilledCanceled,
				CumExecQty:  fixedpoint.NewFromFloat(0.002),
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.CumExecQty, res)
		})

		t.Run("Market/Buy/OrderStatusFilled", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:         fixedpoint.NewFromFloat(200),
				OrderType:   bybitapi.OrderTypeMarket,
				Side:        bybitapi.SideBuy,
				AvgPrice:    fixedpoint.NewFromFloat(25000),
				OrderStatus: bybitapi.OrderStatusFilled,
				CumExecQty:  fixedpoint.NewFromFloat(0.002),
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.CumExecQty, res)
		})

		t.Run("Market/Buy/OrderStatusCreated", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:         fixedpoint.NewFromFloat(200),
				OrderType:   bybitapi.OrderTypeMarket,
				Side:        bybitapi.SideBuy,
				AvgPrice:    fixedpoint.NewFromFloat(25000),
				OrderStatus: bybitapi.OrderStatusCreated,
				CumExecQty:  fixedpoint.NewFromFloat(0.002),
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, fixedpoint.Zero, res)
		})

		t.Run("Market/Buy/OrderStatusNew", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:         fixedpoint.NewFromFloat(200),
				OrderType:   bybitapi.OrderTypeMarket,
				Side:        bybitapi.SideBuy,
				AvgPrice:    fixedpoint.NewFromFloat(25000),
				OrderStatus: bybitapi.OrderStatusNew,
				CumExecQty:  fixedpoint.NewFromFloat(0.002),
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, fixedpoint.Zero, res)
		})

		t.Run("Market/Buy/OrderStatusRejected", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:         fixedpoint.NewFromFloat(200),
				OrderType:   bybitapi.OrderTypeMarket,
				Side:        bybitapi.SideBuy,
				AvgPrice:    fixedpoint.NewFromFloat(25000),
				OrderStatus: bybitapi.OrderStatusRejected,
				CumExecQty:  fixedpoint.NewFromFloat(0.002),
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, fixedpoint.Zero, res)
		})

		t.Run("Market/Buy/OrderStatusCanceled", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:         fixedpoint.NewFromFloat(200),
				OrderType:   bybitapi.OrderTypeMarket,
				Side:        bybitapi.SideBuy,
				AvgPrice:    fixedpoint.NewFromFloat(25000),
				OrderStatus: bybitapi.OrderStatusCancelled,
				CumExecQty:  fixedpoint.NewFromFloat(0.002),
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.Qty, res)
		})

		t.Run("Market/Buy/AvgPrice zero", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:         fixedpoint.NewFromFloat(200),
				OrderType:   bybitapi.OrderTypeMarket,
				Side:        bybitapi.SideBuy,
				AvgPrice:    fixedpoint.Zero,
				OrderStatus: bybitapi.OrderStatusPartiallyFilled,
				CumExecQty:  fixedpoint.NewFromFloat(0.002),
			}
			res, err := processMarketBuyQuantity(o)
			assert.Error(t, err)
			assert.Equal(t, fmt.Errorf("AvgPrice shouldn't be zero"), err)
			assert.Equal(t, fixedpoint.Zero, res)
		})

		t.Run("Market/Sell", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:       fixedpoint.NewFromFloat(5.55),
				OrderType: bybitapi.OrderTypeMarket,
				Side:      bybitapi.SideSell,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.Qty, res)
		})

		t.Run("Limit/Buy", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:       fixedpoint.NewFromFloat(5.55),
				OrderType: bybitapi.OrderTypeLimit,
				Side:      bybitapi.SideBuy,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.Qty, res)
		})

		t.Run("Limit/Sell", func(t *testing.T) {
			o := bybitapi.Order{
				Qty:       fixedpoint.NewFromFloat(5.55),
				OrderType: bybitapi.OrderTypeLimit,
				Side:      bybitapi.SideSell,
			}
			res, err := processMarketBuyQuantity(o)
			assert.NoError(t, err)
			assert.Equal(t, o.Qty, res)
		})
	})
}

func TestToGlobalOrder(t *testing.T) {
	// sample: partialFilled
	//{
	//  "OrderId": 1472539279335923200,
	//  "OrderLinkId": 1690276361150,
	//  "BlockTradeId": null,
	//  "Symbol": "DOTUSDT",
	//  "Price": 7.278,
	//  "Qty": 0.8,
	//  "Side": "Sell",
	//  "IsLeverage": 0,
	//  "PositionIdx": 0,
	//  "OrderStatus": "PartiallyFilled",
	//  "CancelType": "UNKNOWN",
	//  "RejectReason": null,
	//  "AvgPrice": 7.278,
	//  "LeavesQty": 0,
	//  "LeavesValue": 0,
	//  "CumExecQty": 0.5,
	//  "CumExecValue": 0,
	//  "CumExecFee": 0,
	//  "TimeInForce": "GTC",
	//  "OrderType": "Limit",
	//  "StopOrderType": null,
	//  "OrderIv": null,
	//  "TriggerPrice": 0,
	//  "TakeProfit": 0,
	//  "StopLoss": 0,
	//  "TpTriggerBy": null,
	//  "SlTriggerBy": null,
	//  "TriggerDirection": 0,
	//  "TriggerBy": null,
	//  "LastPriceOnCreated": null,
	//  "ReduceOnly": false,
	//  "CloseOnTrigger": false,
	//  "SmpType": "None",
	//  "SmpGroup": 0,
	//  "SmpOrderId": null,
	//  "TpslMode": null,
	//  "TpLimitPrice": null,
	//  "SlLimitPrice": null,
	//  "PlaceType": null,
	//  "CreatedTime": "2023-07-25 17:12:41.325 +0800 CST",
	//  "UpdatedTime": "2023-07-25 17:12:57.868 +0800 CST"
	//}
	timeNow := time.Now()
	openOrder := bybitapi.Order{
		OrderId:            "1472539279335923200",
		OrderLinkId:        "1690276361150",
		BlockTradeId:       "",
		Symbol:             "DOTUSDT",
		Price:              fixedpoint.NewFromFloat(7.278),
		Qty:                fixedpoint.NewFromFloat(0.8),
		Side:               bybitapi.SideSell,
		IsLeverage:         "0",
		PositionIdx:        0,
		OrderStatus:        bybitapi.OrderStatusPartiallyFilled,
		CancelType:         "UNKNOWN",
		RejectReason:       "",
		AvgPrice:           fixedpoint.NewFromFloat(7.728),
		LeavesQty:          fixedpoint.NewFromFloat(0),
		LeavesValue:        fixedpoint.NewFromFloat(0),
		CumExecQty:         fixedpoint.NewFromFloat(0.5),
		CumExecValue:       fixedpoint.NewFromFloat(0),
		CumExecFee:         fixedpoint.NewFromFloat(0),
		TimeInForce:        "GTC",
		OrderType:          bybitapi.OrderTypeLimit,
		StopOrderType:      "",
		OrderIv:            "",
		TriggerPrice:       fixedpoint.NewFromFloat(0),
		TakeProfit:         fixedpoint.NewFromFloat(0),
		StopLoss:           fixedpoint.NewFromFloat(0),
		TpTriggerBy:        "",
		SlTriggerBy:        "",
		TriggerDirection:   0,
		TriggerBy:          "",
		LastPriceOnCreated: "",
		ReduceOnly:         false,
		CloseOnTrigger:     false,
		SmpType:            "None",
		SmpGroup:           0,
		SmpOrderId:         "",
		TpslMode:           "",
		TpLimitPrice:       "",
		SlLimitPrice:       "",
		PlaceType:          "",
		CreatedTime:        types.MillisecondTimestamp(timeNow),
		UpdatedTime:        types.MillisecondTimestamp(timeNow),
	}
	side, err := toGlobalSideType(openOrder.Side)
	assert.NoError(t, err)
	orderType, err := toGlobalOrderType(openOrder.OrderType)
	assert.NoError(t, err)
	tif, err := toGlobalTimeInForce(openOrder.TimeInForce)
	assert.NoError(t, err)
	status, err := toGlobalOrderStatus(openOrder.OrderStatus, openOrder.Side, openOrder.OrderType)
	assert.NoError(t, err)
	orderIdNum, err := strconv.ParseUint(openOrder.OrderId, 10, 64)
	assert.NoError(t, err)

	exp := types.Order{
		SubmitOrder: types.SubmitOrder{
			ClientOrderID: openOrder.OrderLinkId,
			Symbol:        openOrder.Symbol,
			Side:          side,
			Type:          orderType,
			Quantity:      openOrder.Qty,
			Price:         openOrder.Price,
			TimeInForce:   tif,
		},
		Exchange:         types.ExchangeBybit,
		OrderID:          orderIdNum,
		Status:           status,
		ExecutedQuantity: openOrder.CumExecQty,
		IsWorking:        status == types.OrderStatusNew || status == types.OrderStatusPartiallyFilled,
		CreationTime:     types.Time(openOrder.CreatedTime),
		UpdateTime:       types.Time(openOrder.UpdatedTime),
		IsFutures:        false,
		IsMargin:         false,
		IsIsolated:       false,
	}
	res, err := toGlobalOrder(openOrder)
	assert.NoError(t, err)
	assert.Equal(t, res, &exp)
}

func TestToGlobalSideType(t *testing.T) {
	res, err := toGlobalSideType(bybitapi.SideBuy)
	assert.NoError(t, err)
	assert.Equal(t, types.SideTypeBuy, res)

	res, err = toGlobalSideType(bybitapi.SideSell)
	assert.NoError(t, err)
	assert.Equal(t, types.SideTypeSell, res)

	res, err = toGlobalSideType("GG")
	assert.Error(t, err)
}

func TestToGlobalOrderType(t *testing.T) {
	res, err := toGlobalOrderType(bybitapi.OrderTypeMarket)
	assert.NoError(t, err)
	assert.Equal(t, types.OrderTypeMarket, res)

	res, err = toGlobalOrderType(bybitapi.OrderTypeLimit)
	assert.NoError(t, err)
	assert.Equal(t, types.OrderTypeLimit, res)

	res, err = toGlobalOrderType("GG")
	assert.Error(t, err)
}

func TestToGlobalTimeInForce(t *testing.T) {
	res, err := toGlobalTimeInForce(bybitapi.TimeInForceGTC)
	assert.NoError(t, err)
	assert.Equal(t, types.TimeInForceGTC, res)

	res, err = toGlobalTimeInForce(bybitapi.TimeInForceIOC)
	assert.NoError(t, err)
	assert.Equal(t, types.TimeInForceIOC, res)

	res, err = toGlobalTimeInForce(bybitapi.TimeInForceFOK)
	assert.NoError(t, err)
	assert.Equal(t, types.TimeInForceFOK, res)

	res, err = toGlobalTimeInForce("GG")
	assert.Error(t, err)
}

func Test_toGlobalOrderStatus(t *testing.T) {
	t.Run("market/buy", func(t *testing.T) {
		res, err := toGlobalOrderStatus(bybitapi.OrderStatusPartiallyFilledCanceled, bybitapi.SideBuy, bybitapi.OrderTypeMarket)
		assert.NoError(t, err)
		assert.Equal(t, types.OrderStatusFilled, res)
	})

	t.Run("limit/buy", func(t *testing.T) {
		res, err := toGlobalOrderStatus(bybitapi.OrderStatusPartiallyFilledCanceled, bybitapi.SideBuy, bybitapi.OrderTypeLimit)
		assert.NoError(t, err)
		assert.Equal(t, types.OrderStatusCanceled, res)
	})

	t.Run("limit/sell", func(t *testing.T) {
		res, err := toGlobalOrderStatus(bybitapi.OrderStatusPartiallyFilledCanceled, bybitapi.SideSell, bybitapi.OrderTypeLimit)
		assert.NoError(t, err)
		assert.Equal(t, types.OrderStatusCanceled, res)
	})
}

func Test_processOtherOrderStatus(t *testing.T) {
	t.Run("New", func(t *testing.T) {
		res, err := processOtherOrderStatus(bybitapi.OrderStatusNew)
		assert.NoError(t, err)
		assert.Equal(t, types.OrderStatusNew, res)

		res, err = processOtherOrderStatus(bybitapi.OrderStatusActive)
		assert.NoError(t, err)
		assert.Equal(t, types.OrderStatusNew, res)
	})

	t.Run("Filled", func(t *testing.T) {
		res, err := processOtherOrderStatus(bybitapi.OrderStatusFilled)
		assert.NoError(t, err)
		assert.Equal(t, types.OrderStatusFilled, res)
	})

	t.Run("PartiallyFilled", func(t *testing.T) {
		res, err := processOtherOrderStatus(bybitapi.OrderStatusPartiallyFilled)
		assert.NoError(t, err)
		assert.Equal(t, types.OrderStatusPartiallyFilled, res)
	})

	t.Run("OrderStatusCanceled", func(t *testing.T) {
		res, err := processOtherOrderStatus(bybitapi.OrderStatusCancelled)
		assert.NoError(t, err)
		assert.Equal(t, types.OrderStatusCanceled, res)

		res, err = processOtherOrderStatus(bybitapi.OrderStatusDeactivated)
		assert.NoError(t, err)
		assert.Equal(t, types.OrderStatusCanceled, res)
	})

	t.Run("OrderStatusRejected", func(t *testing.T) {
		res, err := processOtherOrderStatus(bybitapi.OrderStatusRejected)
		assert.NoError(t, err)
		assert.Equal(t, types.OrderStatusRejected, res)
	})

	t.Run("OrderStatusPartiallyFilledCanceled", func(t *testing.T) {
		res, err := processOtherOrderStatus(bybitapi.OrderStatusPartiallyFilledCanceled)
		assert.Equal(t, types.OrderStatus(bybitapi.OrderStatusPartiallyFilledCanceled), res)
		assert.Error(t, err)
		assert.Equal(t, fmt.Errorf("unexpected order status: %s", bybitapi.OrderStatusPartiallyFilledCanceled), err)
	})
}

func Test_toLocalOrderType(t *testing.T) {
	orderType, err := toLocalOrderType(types.OrderTypeLimit)
	assert.NoError(t, err)
	assert.Equal(t, bybitapi.OrderTypeLimit, orderType)

	orderType, err = toLocalOrderType(types.OrderTypeMarket)
	assert.NoError(t, err)
	assert.Equal(t, bybitapi.OrderTypeMarket, orderType)

	orderType, err = toLocalOrderType("wrong type")
	assert.Equal(t, fmt.Errorf("order type wrong type not supported"), err)
	assert.Equal(t, bybitapi.OrderType(""), orderType)
}

func Test_toLocalSide(t *testing.T) {
	side, err := toLocalSide(types.SideTypeSell)
	assert.NoError(t, err)
	assert.Equal(t, bybitapi.SideSell, side)

	side, err = toLocalSide(types.SideTypeBuy)
	assert.NoError(t, err)
	assert.Equal(t, bybitapi.SideBuy, side)

	side, err = toLocalSide("wrong side")
	assert.Equal(t, fmt.Errorf("side type %s not supported", "wrong side"), err)
	assert.Equal(t, bybitapi.Side(""), side)
}

func Test_toGlobalTrade(t *testing.T) {
	/* sample: trade
	{
		"Symbol":"BTCUSDT",
		"Id":"1474200510090276864",
		"OrderId":"1474200270671015936",
		"TradeId":"2100000000031181772",
		"OrderPrice":"27628",
		"OrderQty":"0.007959",
		"ExecFee":"0.21989125",
		"FeeTokenId":"USDT",
		"CreatTime":"2023-07-28 00:13:15.457 +0800 CST",
		"IsBuyer":"1",
		"IsMaker":"0",
		"MatchOrderId":"5760912963729109504",
		"MakerRebate":"0",
		"ExecutionTime":"2023-07-28 00:13:15.463 +0800 CST",
		"BlockTradeId": "",
	}
	*/
	timeNow := time.Now()
	trade := v3.Trade{
		Symbol:        "DOTUSDT",
		Id:            "1474200510090276864",
		OrderId:       "1474200270671015936",
		TradeId:       "2100000000031181772",
		OrderPrice:    fixedpoint.NewFromFloat(27628),
		OrderQty:      fixedpoint.NewFromFloat(0.007959),
		ExecFee:       fixedpoint.NewFromFloat(0.21989125),
		FeeTokenId:    "USDT",
		CreatTime:     types.MillisecondTimestamp(timeNow),
		IsBuyer:       "0",
		IsMaker:       "0",
		MatchOrderId:  "5760912963729109504",
		MakerRebate:   fixedpoint.NewFromFloat(0),
		ExecutionTime: types.MillisecondTimestamp(timeNow),
		BlockTradeId:  "",
	}

	s, err := toV3Buyer(trade.IsBuyer)
	assert.NoError(t, err)
	m, err := toV3Maker(trade.IsMaker)
	assert.NoError(t, err)
	orderIdNum, err := strconv.ParseUint(trade.OrderId, 10, 64)
	assert.NoError(t, err)
	tradeId, err := strconv.ParseUint(trade.TradeId, 10, 64)
	assert.NoError(t, err)

	exp := types.Trade{
		ID:            tradeId,
		OrderID:       orderIdNum,
		Exchange:      types.ExchangeBybit,
		Price:         trade.OrderPrice,
		Quantity:      trade.OrderQty,
		QuoteQuantity: trade.OrderPrice.Mul(trade.OrderQty),
		Symbol:        trade.Symbol,
		Side:          s,
		IsBuyer:       s == types.SideTypeBuy,
		IsMaker:       m,
		Time:          types.Time(timeNow),
		Fee:           trade.ExecFee,
		FeeCurrency:   trade.FeeTokenId,
		IsMargin:      false,
		IsFutures:     false,
		IsIsolated:    false,
	}
	res, err := v3ToGlobalTrade(trade)
	assert.NoError(t, err)
	assert.Equal(t, res, &exp)
}

func Test_toGlobalKLines(t *testing.T) {
	symbol := "BTCUSDT"
	interval := types.Interval15m

	resp := bybitapi.KLinesResponse{
		Symbol: symbol,
		List: []bybitapi.KLine{
			/*
				[
					{
						"StartTime": "2023-08-08 17:30:00 +0800 CST",
						"OpenPrice": 29045.3,
						"HighPrice": 29228.56,
						"LowPrice": 29045.3,
						"ClosePrice": 29228.56,
						"Volume": 9.265593,
						"TurnOver": 270447.43520753
					},
					{
						"StartTime": "2023-08-08 17:15:00 +0800 CST",
						"OpenPrice": 29167.33,
						"HighPrice": 29229.08,
						"LowPrice": 29000,
						"ClosePrice": 29045.3,
						"Volume": 9.295508,
						"TurnOver": 270816.87513775
					}
				]
			*/
			{
				StartTime: types.NewMillisecondTimestampFromInt(1691486100000),
				Open:      fixedpoint.NewFromFloat(29045.3),
				High:      fixedpoint.NewFromFloat(29228.56),
				Low:       fixedpoint.NewFromFloat(29045.3),
				Close:     fixedpoint.NewFromFloat(29228.56),
				Volume:    fixedpoint.NewFromFloat(9.265593),
				TurnOver:  fixedpoint.NewFromFloat(270447.43520753),
			},
			{
				StartTime: types.NewMillisecondTimestampFromInt(1691487000000),
				Open:      fixedpoint.NewFromFloat(29167.33),
				High:      fixedpoint.NewFromFloat(29229.08),
				Low:       fixedpoint.NewFromFloat(29000),
				Close:     fixedpoint.NewFromFloat(29045.3),
				Volume:    fixedpoint.NewFromFloat(9.295508),
				TurnOver:  fixedpoint.NewFromFloat(270816.87513775),
			},
		},
		Category: bybitapi.CategorySpot,
	}

	expKlines := []types.KLine{
		{
			Exchange:    types.ExchangeBybit,
			Symbol:      resp.Symbol,
			StartTime:   types.Time(resp.List[0].StartTime.Time()),
			EndTime:     types.Time(resp.List[0].StartTime.Time().Add(interval.Duration() - time.Millisecond)),
			Interval:    interval,
			Open:        fixedpoint.NewFromFloat(29045.3),
			Close:       fixedpoint.NewFromFloat(29228.56),
			High:        fixedpoint.NewFromFloat(29228.56),
			Low:         fixedpoint.NewFromFloat(29045.3),
			Volume:      fixedpoint.NewFromFloat(9.265593),
			QuoteVolume: fixedpoint.NewFromFloat(270447.43520753),
			Closed:      false,
		},
		{
			Exchange:    types.ExchangeBybit,
			Symbol:      resp.Symbol,
			StartTime:   types.Time(resp.List[1].StartTime.Time()),
			EndTime:     types.Time(resp.List[1].StartTime.Time().Add(interval.Duration() - time.Millisecond)),
			Interval:    interval,
			Open:        fixedpoint.NewFromFloat(29167.33),
			Close:       fixedpoint.NewFromFloat(29045.3),
			High:        fixedpoint.NewFromFloat(29229.08),
			Low:         fixedpoint.NewFromFloat(29000),
			Volume:      fixedpoint.NewFromFloat(9.295508),
			QuoteVolume: fixedpoint.NewFromFloat(270816.87513775),
			Closed:      false,
		},
	}

	assert.Equal(t, toGlobalKLines(symbol, interval, resp.List), expKlines)
}
