package excservice

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"net/url"
	"strconv"
	"strings"
	"time"

	"go.uber.org/zap"

	"go-admin/common/global"
	"go-admin/common/helper"
	"go-admin/models"
	"go-admin/pkg/httputils"
	"go-admin/pkg/utility"

	"github.com/bytedance/sonic"
	log "github.com/go-admin-team/go-admin-core/logger"
)

const (
	TICKER_URI             = "/api/v3/ticker/24hr?symbol=%s"
	TICKERS_URI            = "ticker/allBookTickers"
	DEPTH_URI              = "/api/v3/depth?symbol=%s&limit=%d"
	ACCOUNT_URI            = "/api/v3/account?"
	ORDER_URI              = "/api/v3/order"
	UNFINISHED_ORDERS_INFO = "openOrders?"
	KLINE_URI              = "klines"
	SERVER_TIME_URL        = "/api/v3/time"
)

var (
	apiUrl = "https://api.binance.com"
	//如果上面的baseURL访问有性能问题，请访问下面的API集群:
	//https://api1.binance.com
	//https://api2.binance.com
	//https://api3.binance.com
)

func init() {
	//err := setTimeOffsetPro()
	//if err != nil {
	//	fmt.Println("setTimeOffsetPro,err:", err)
	//}
}

func buildParamsSigned(postForm *url.Values, secretKey string) {
	postForm.Set("recvWindow", "60000")
	tonce := strconv.FormatInt(time.Now().UnixNano()+timeOffset, 10)[0:13]
	postForm.Set("timestamp", tonce)
	payload := postForm.Encode()
	sign, _ := GetHmacSHA256Sign(secretKey, payload)
	postForm.Set("signature", sign)
}
func GetHmacSHA256Sign(secret, params string) (string, error) {
	mac := hmac.New(sha256.New, []byte(secret))
	_, err := mac.Write([]byte(params))
	if err != nil {
		return "", err
	}
	return hex.EncodeToString(mac.Sum(nil)), nil
}

// 获取服务器时间
func setTimeOffsetPro() error {
	respData, err := httputils.NewHttpRequestWithFasthttp("GET", apiUrl+SERVER_TIME_URL, "", nil)
	if err != nil {
		return err
	}
	var bodyDataMap map[string]interface{}
	err = json.Unmarshal(respData, &bodyDataMap)
	if err != nil {
		log.Error(string(respData))
		return err
	}
	stime := int64(utility.ToInt(bodyDataMap["serverTime"]))
	st := time.Unix(stime/1000, 1000000*(stime%1000))
	lt := time.Now()
	offset := st.Sub(lt).Nanoseconds()
	timeOffset = offset
	return nil
}

// GetTicker 获取24小时行情
func GetTicker(coin, currency string) (models.Ticker24, error) {
	par := strings.ToUpper(coin + currency)
	tickerUri := apiUrl + fmt.Sprintf(TICKER_URI, par)
	var ticker models.Ticker24
	respData, err := httputils.NewHttpRequestWithFasthttp("GET", tickerUri, "", nil)
	if err != nil {
		log.Error("GetTicker", zap.Error(err))
		return ticker, err
	}
	var tickerMap map[string]interface{}
	err = json.Unmarshal(respData, &tickerMap)
	if err != nil {
		log.Error("GetTicker", zap.ByteString("respData", respData), zap.Error(err))
		return ticker, err
	}

	ticker.LastPrice = tickerMap["lastPrice"].(string)
	ticker.LowPrice = tickerMap["lowPrice"].(string)
	ticker.HighPrice = tickerMap["highPrice"].(string)
	ticker.Volume = tickerMap["volume"].(string)
	ticker.QuoteVolume = tickerMap["quoteVolume"].(string)
	ticker.ChangePercent = tickerMap["priceChangePercent"].(string)
	ticker.OpenPrice = tickerMap["openPrice"].(string)
	return ticker, nil
}

// GetTickerBySymbols 获取24小时行情 symbols symbols参数可接受的格式： ["BTCUSDT","BNBUSDT"]
func GetTickerBySymbols(symbols string) ([]models.Ticker24, error) {
	tickerUri := apiUrl + "/api/v3/ticker/24hr"

	respData, err := httputils.NewHttpRequestWithFasthttp("GET", tickerUri, "", nil)
	if err != nil {
		log.Error("GetTicker", zap.Error(err))
		return nil, err
	}
	var tickerList []interface{}
	err = json.Unmarshal(respData, &tickerList)
	if err != nil {
		log.Error("GetTickerBySymbols", zap.ByteString("respData", respData), zap.Error(err))
		return nil, err
	}
	list := make([]models.Ticker24, 0, len(tickerList))
	for _, t := range tickerList {
		tickerMap := t.(map[string]interface{})
		if tickerMap == nil {
			continue
		}
		var ticker models.Ticker24
		ticker.LastPrice = tickerMap["lastPrice"].(string)
		ticker.LowPrice = tickerMap["lowPrice"].(string)
		ticker.HighPrice = tickerMap["highPrice"].(string)
		ticker.Volume = tickerMap["volume"].(string)
		ticker.QuoteVolume = tickerMap["quoteVolume"].(string)
		ticker.ChangePercent = tickerMap["priceChangePercent"].(string)
		ticker.OpenPrice = tickerMap["openPrice"].(string)
		ticker.Symbol = tickerMap["symbol"].(string)
		list = append(list, ticker)
	}

	return list, nil
}

// GetKlinePro 获取k线--现货行情接口
func GetKlinePro(coin, currency string, period string, size int) ([]models.Kline, error) {
	par := strings.ToUpper(coin + currency)
	periodS := period //, isOk := INERNAL_KLINE_PERIOD_CONVERTER[period]
	//if isOk != true {
	//	periodS = "M1"
	//}
	key := fmt.Sprintf("%s:%s:%s", global.K_SPOT, par, period)

	//获取缓存
	klineStrs, err := helper.DefaultRedis.GetAllSortSet(key)

	if err != nil {
		return nil, err
	}

	if len(klineStrs) > 0 && len(klineStrs) >= 500 {
		klines := make([]models.Kline, 0)

		for _, item := range klineStrs {
			var kline models.Kline
			err := sonic.Unmarshal([]byte(item), &kline)

			if err == nil {
				klines = append(klines, kline)
			}
		}

		return klines, nil
	}

	//没有缓存 重新获取

	urlKline := apiUrl + "/api/v3/klines?symbol=" + par + "&interval=" + periodS + "&limit=" + utility.IntTostring(size)
	respData, err := httputils.NewHttpRequestWithFasthttp("GET", urlKline, "", nil)
	if err != nil {
		return nil, err
	}
	var bodyDataMap []interface{}
	err = json.Unmarshal(respData, &bodyDataMap)
	if err != nil {
		log.Error("GetKlinePro", zap.ByteString("respData", respData), zap.Error(err))
		return nil, err
	}
	var klines []models.Kline
	for _, _record := range bodyDataMap {
		r := models.Kline{}
		record := _record.([]interface{})
		times := utility.ToFloat64(record[0]) //to unix timestramp

		// 超出10位的 处理为
		if times > 9999999999 {
			r.Timestamp = int64(times) / 1000
		} else {
			r.Timestamp = int64(times)
		}

		r.Open = record[1].(string)
		r.High = record[2].(string)
		r.Low = record[3].(string)
		r.Close = record[4].(string)
		r.Vol = record[5].(string)
		r.QuoteVolume = record[7].(string)

		klines = append(klines, r)

		member, err := sonic.Marshal(r)

		if err == nil {
			err = helper.DefaultRedis.SignelAdd(key, float64(r.Timestamp), string(member))

			if err != nil {
				log.Error("保存k线数据失败:", key, err)
			}
		}
	}
	return klines, nil
}

// GetTrades 非个人，整个交易所的交易记录
// 注意：since is fromId
func GetTrades(coin, currency string) ([]models.NewDealPush, error) {
	param := url.Values{}
	param.Set("symbol", strings.ToUpper(coin+currency))
	param.Set("limit", "50")
	//if since > 0 {
	//	param.Set("fromId", strconv.Itoa(int(since)))
	//}
	urlTrade := apiUrl + "/api/v3/trades?" + param.Encode()
	resp, err := httputils.NewHttpRequestWithFasthttp("GET", urlTrade, "", nil)
	if err != nil {
		return nil, err
	}
	var bodyDataMap []interface{}
	err = json.Unmarshal(resp, &bodyDataMap)
	if err != nil {
		log.Error("GetTrades", zap.ByteString("respData", resp), zap.Error(err))
		return nil, err
	}
	var trades []models.NewDealPush
	for _, v := range bodyDataMap {
		m := v.(map[string]interface{})
		ty := 2
		if m["isBuyerMaker"].(bool) {
			ty = 1
		}
		trades = append(trades, models.NewDealPush{
			DealId:     utility.ToInt64(m["id"]),
			Type:       ty,
			Num:        utility.ToFloat64(m["qty"]),
			Price:      utility.ToFloat64(m["price"]),
			CreateTime: utility.ToInt64(m["time"]),
		})
	}
	return trades, nil
}

// GetDepth 获取深度
func GetDepth(size int, coin, currency string) (models.DepthBin, error) {
	if size <= 5 {
		size = 5
	} else if size <= 10 {
		size = 10
	} else if size <= 20 {
		size = 20
	} else if size <= 50 {
		size = 50
	} else if size <= 100 {
		size = 100
	} else if size <= 500 {
		size = 500
	} else {
		size = 1000
	}
	urlDep := fmt.Sprintf(apiUrl+DEPTH_URI, strings.ToUpper(coin+currency), size)
	respFive, err := httputils.NewHttpRequestWithFasthttp("GET", urlDep, "", nil)
	if err != nil {
		return models.DepthBin{}, err
	}
	d := models.DepthBin{}
	err = sonic.Unmarshal(respFive, &d)
	if err != nil {
		fmt.Println("GetDepth json unmarshal error for ", string(respFive), zap.Error(err))
		return models.DepthBin{}, err
	}
	return d, nil

}
