package binanceservice

import (
	"context"
	"errors"
	"fmt"
	"go-admin/app/admin/models"
	"go-admin/app/admin/service/dto"
	"go-admin/common/const/rediskey"
	"go-admin/common/global"
	"go-admin/common/helper"
	models2 "go-admin/models"
	"go-admin/pkg/utility"
	"go-admin/services/cacheservice"
	"strings"
	"time"

	"github.com/bytedance/sonic"
	"github.com/go-admin-team/go-admin-core/logger"
	"github.com/go-admin-team/go-admin-core/sdk/service"
	"github.com/shopspring/decimal"
	"gorm.io/gorm"
)

type BinanceStrategyOrderService struct {
	service.Service
	Debug bool
}

// 判断是否触发波段订单
func (e *BinanceStrategyOrderService) TriggerStrategyOrder(exchangeType string) {
	//现货
	orderStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategySpotOrderList, exchangeType))
	if len(orderStrs) > 0 {
		e.DoJudge(orderStrs, 1, exchangeType)
	}

	//合约
	futOrdedrStrs, _ := helper.DefaultRedis.GetAllList(fmt.Sprintf(rediskey.StrategyFutOrderList, exchangeType))

	if len(futOrdedrStrs) > 0 {
		e.DoJudge(futOrdedrStrs, 2, exchangeType)
	}
}

// 判断是否符合条件
func (e *BinanceStrategyOrderService) DoJudge(orderStrs []string, symbolType int, exchangeType string) {
	db := GetDBConnection()
	// setting, _ := cacheservice.GetSystemSetting(db)

	for _, orderStr := range orderStrs {
		var lockKey string
		orderItem := dto.StrategyOrderRedisList{}
		err := sonic.Unmarshal([]byte(orderStr), &orderItem)

		if err != nil || orderItem.Symbol == "" {
			continue
		}

		if symbolType == 1 {
			lockKey = rediskey.StrategySpotTriggerLock
		} else {
			lockKey = rediskey.StrategyFutTriggerLock
		}

		lock := helper.NewRedisLock(fmt.Sprintf(lockKey, orderItem.ApiId, orderItem.OrderSn), 60, 20, 300*time.Millisecond)

		if ok, err := lock.AcquireWait(context.Background()); err != nil {
			e.Log.Debug("获取锁失败", err)
			return
		} else if ok {
			defer lock.Release()

			//判断是否符合条件
			success, err := e.JudgeStrategy(orderItem, symbolType, exchangeType)

			if err != nil {
				e.Log.Errorf("order_id:%d err:%v", orderItem.Id, err)
			}

			if e.Debug || success {
				e.TriggerOrder(db, orderStr, orderItem, symbolType)
			}
		}
	}
}

// 判断是否符合触发条件
func (e *BinanceStrategyOrderService) JudgeStrategy(order dto.StrategyOrderRedisList, symbolType int, exchangeType string) (bool, error) {
	var symbolKey string
	result := false
	nowUtc := time.Now().UnixMilli()

	switch symbolType {
	case 1:
		symbolKey = fmt.Sprintf(rediskey.SpotTickerLastPrice, exchangeType, order.Symbol)
	case 2:
		symbolKey = fmt.Sprintf(rediskey.FutureTickerLastPrice, exchangeType, order.Symbol)
	}

	lastUtc := nowUtc - (int64(order.TimeSlotStart) * 60 * 1000)
	beforePrice, _ := helper.DefaultRedis.GetNextAfterScore(symbolKey, float64(lastUtc))
	lastPrices, _ := helper.DefaultRedis.GetLastSortSet(symbolKey)

	if beforePrice == "" || len(lastPrices) == 0 {
		return result, errors.New("获取交易对起止价格失败")
	}

	score := lastPrices[0].Score
	var startPrice decimal.Decimal
	var lastPrice decimal.Decimal
	startPriceArgs := strings.Split(beforePrice, ":")
	endPricecArgs := strings.Split(lastPrices[0].Member.(string), ":")

	if len(startPriceArgs) == 0 || len(endPricecArgs) == 0 {
		return result, errors.New("获取交易对起止价格失败")
	}
	startPrice = utility.StrToDecimal(startPriceArgs[len(startPriceArgs)-1])
	lastPrice = utility.StrToDecimal(endPricecArgs[len(endPricecArgs)-1])

	//时间差超过10s
	if (nowUtc-int64(score))/1000 > 10 {
		return result, fmt.Errorf("时间差超过 %ss", "10")
	}

	if startPrice.Cmp(decimal.Zero) == 0 || lastPrice.Cmp(decimal.Zero) == 0 {
		return result, errors.New("获取交易对起止价格有一个为0")
	}

	percentag := lastPrice.Div(startPrice).Sub(decimal.NewFromInt(1)).Truncate(6)
	logger.Infof("百分比:%s", percentag.Mul(decimal.NewFromInt(100)).String())
	//价格没有变动
	if percentag.Cmp(decimal.Zero) == 0 {
		return result, nil
	}

	//满足条件
	switch {
	//涨价格大于0.5% 跌价格小于-0.5%
	case order.CompareType == 1 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0,
		order.CompareType == 1 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0:
		result = percentag.Abs().Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) > 0

	case order.CompareType == 2 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0,
		order.CompareType == 2 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0:
		result = percentag.Abs().Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) >= 0

	case order.CompareType == 5 && order.Direction == 1 && percentag.Cmp(decimal.Zero) > 0,
		order.CompareType == 5 && order.Direction == 2 && percentag.Cmp(decimal.Zero) < 0:
		result = percentag.Abs().Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) == 0
	}

	return result, nil
}

// 触发委托单
func (e *BinanceStrategyOrderService) TriggerOrder(db *gorm.DB, cacheVal string, order dto.StrategyOrderRedisList, symbolType int) error {
	orders := make([]models.LinePreOrder, 0)
	if err := e.Orm.Model(&models.LinePreOrder{}).Where("main_id =? or id =?", order.Id, order.Id).Find(&orders).Error; err != nil {
		e.Log.Errorf("order_id:%d 获取委托单失败:%s", order.Id, err.Error())
		return err
	}

	setting, _ := cacheservice.GetSystemSetting(e.Orm)

	if setting.Id == 0 {
		return errors.New("获取系统设置失败")
	}

	var tradeSet models2.TradeSet

	switch symbolType {
	case 1:
		tradeSet, _ = cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 0)
	case 2:
		tradeSet, _ = cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, 1)
	default:
		return errors.New("获取交易对行情失败,交易对类型错误")
	}

	if tradeSet.Coin == "" {
		return errors.New("获取交易对行情失败")
	}

	lastPrice := utility.StrToDecimal(tradeSet.LastPrice)

	if lastPrice.Cmp(decimal.Zero) <= 0 {
		return errors.New("最新成交价小于等于0")
	}

	mainOrder := models.LinePreOrder{}

	for _, v := range orders {
		if v.MainId == 0 {
			mainOrder = v
			break
		}
	}

	if mainOrder.Id == 0 {
		return errors.New("获取主单失败")
	}

	GetOrderByPid(&mainOrder, orders, mainOrder.Id)

	e.RecalculateOrder(tradeSet, &mainOrder, setting)

	//事务保存
	err := e.Orm.Transaction(func(tx *gorm.DB) error {
		if err1 := tx.Save(&mainOrder).Error; err1 != nil {
			return err1
		}

		for _, v := range mainOrder.Childs {
			if err1 := tx.Save(&v).Error; err1 != nil {
				return err1
			}

			for _, v2 := range v.Childs {
				if err1 := tx.Save(&v2).Error; err1 != nil {
					return err1
				}
			}
		}

		return nil
	})

	if err != nil {
		e.Log.Errorf("order_id:%d 波段触发保存委托单失败:%s", mainOrder.Id, err.Error())
		return err
	}

	e.StrategyOrderPlace(db, cacheVal, mainOrder, tradeSet)

	return nil
}

// 策略触发订单
// cacheVal:缓存值
// mainOrder:主订单
// tradeSet:交易对行情
func (e *BinanceStrategyOrderService) StrategyOrderPlace(db *gorm.DB, cacheVal string, mainOrder models.LinePreOrder, tradeSet models2.TradeSet) {
	price := utility.StringToDecimal(mainOrder.Price).Truncate(int32(tradeSet.PriceDigit))
	num := utility.StringToDecimal(mainOrder.Num).Truncate(int32(tradeSet.AmountDigit))
	futApi := FutRestApi{}
	var key string

	switch mainOrder.SymbolType {
	case 1:
		key = fmt.Sprintf(rediskey.StrategySpotOrderList, global.EXCHANGE_BINANCE)
	case 2:
		key = fmt.Sprintf(rediskey.StrategyFutOrderList, global.EXCHANGE_BINANCE)
	default:
		logger.Errorf("id:%d 交易对类型不存在:%d", mainOrder.Id, mainOrder.SymbolType)
		return
	}

	params := FutOrderPlace{
		ApiId:            mainOrder.ApiId,
		Symbol:           mainOrder.Symbol,
		Side:             mainOrder.Site,
		OrderType:        mainOrder.MainOrderType,
		SideType:         mainOrder.MainOrderType,
		Price:            price,
		Quantity:         num,
		NewClientOrderId: mainOrder.OrderSn,
	}

	if err := futApi.OrderPlaceLoop(db, params, 3); err != nil {
		logger.Error("下单失败", mainOrder.Symbol, " err:", err)
		if _, err := helper.DefaultRedis.LRem(key, cacheVal); err != nil {
			logger.Error("删除redis 预下单失败:", err)
		}

		err := db.Model(&models.LinePreOrder{}).Where("id =? and status='0'", mainOrder.Id).Updates(map[string]interface{}{"status": "2", "desc": err.Error()}).Error

		if err != nil {
			logger.Error("更新预下单状态失败")
		}

		return
	} else {
		if _, err := helper.DefaultRedis.LRem(key, cacheVal); err != nil {
			logger.Error("删除redis 预下单失败:", err)
		}
	}

	if err := db.Model(&models.LinePreOrder{}).Where("id =? ", mainOrder.Id).Updates(map[string]interface{}{"trigger_time": time.Now()}).Error; err != nil {
		logger.Error("更新预下单状态失败 ordersn:", mainOrder.OrderSn, " status:1")
	}

	if err := db.Model(&models.LinePreOrder{}).Where("id =? AND status ='0'", mainOrder.Id).Updates(map[string]interface{}{"status": "1"}).Error; err != nil {
		logger.Error("更新预下单状态失败 ordersn:", mainOrder.OrderSn, " status:1")
	}
}

// 重新计算订单单价、数量
// tradeSet 交易对行情
// mainOrder 主订单
// setting 系统设置
func (e *BinanceStrategyOrderService) RecalculateOrder(tradeSet models2.TradeSet, mainOrder *models.LinePreOrder, setting models.LineSystemSetting) error {
	exts := make([]models.LinePreOrderExt, 0)
	if err := e.Orm.Model(models.LinePreOrderExt{}).Where("main_order_id =?", mainOrder.Id).Find(&exts).Error; err != nil {
		return errors.New("获取拓展信息失败")
	}

	var newPrice decimal.Decimal
	lastPrice := utility.StrToDecimal(tradeSet.LastPrice)
	rate := utility.StrToDecimal(mainOrder.Rate)
	newPrice = lastPrice.Mul(decimal.NewFromInt(1).Add(rate.Div(decimal.NewFromInt(100)).Truncate(4))).Truncate(int32(tradeSet.PriceDigit))
	buyPrice := utility.StrToDecimal(mainOrder.BuyPrice)
	totalNum := buyPrice.Div(newPrice).Truncate(int32(tradeSet.AmountDigit))

	mainOrder.SignPrice = lastPrice.String()
	mainOrder.Price = newPrice.String()
	mainOrder.Num = totalNum.String()
	remainQuantity := totalNum
	var totalLossAmount decimal.Decimal
	prePrice := lastPrice

	for index := range mainOrder.Childs {
		var ext models.LinePreOrderExt
		extOrderId := mainOrder.Childs[index].Id
		takeStopArray := []int{1, 2}

		//止盈止损 ext拓展id为 pid
		if utility.ContainsInt(takeStopArray, mainOrder.Childs[index].OrderType) {
			extOrderId = mainOrder.Childs[index].Pid
		}

		for _, v := range exts {
			if v.OrderId == extOrderId {
				ext = v
				break
			}
		}
		if ext.Id <= 0 {
			logger.Errorf("子订单ext不存在 id:%d", mainOrder.Childs[index].Id)
			continue
		}

		//主单止盈、止损
		if mainOrder.Childs[index].Pid == mainOrder.Childs[index].MainId && (mainOrder.Childs[index].OrderType == 1 || mainOrder.Childs[index].OrderType == 2) {
			var percent decimal.Decimal

			switch {
			// 加价
			case mainOrder.Childs[index].OrderType == 1 && mainOrder.Site == "BUY", mainOrder.Childs[index].OrderType == 2 && mainOrder.Site == "SELL":
				percent = decimal.NewFromInt(100).Add(ext.TakeProfitRatio)
				//减价
			case mainOrder.Childs[index].OrderType == 2 && mainOrder.Site == "BUY", mainOrder.Childs[index].OrderType == 1 && mainOrder.Site == "SELL":
				percent = decimal.NewFromInt(100).Sub(ext.StopLossRatio)
			}

			childPrice := lastPrice.Mul(percent.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(int32(tradeSet.PriceDigit))
			mainOrder.Childs[index].Price = childPrice.String()
			mainOrder.Childs[index].Num = totalNum.String()

		} else {
			lastNum := remainQuantity

			//过期时间
			if ext.ExpirateHour <= 0 {
				mainOrder.Childs[index].ExpireTime = time.Now().AddDate(10, 0, 0)
			} else {
				mainOrder.Childs[index].ExpireTime = time.Now().Add(time.Hour * time.Duration(ext.ExpirateHour))
			}

			switch {
			//加仓单
			case mainOrder.Childs[index].OrderType == 1 && mainOrder.Childs[index].OrderCategory == 3:
				var percentage decimal.Decimal

				if mainOrder.Site == "BUY" {
					percentage = decimal.NewFromInt(1).Sub(ext.PriceRatio.Div(decimal.NewFromInt(100)))
				} else {
					percentage = decimal.NewFromInt(1).Add(ext.PriceRatio.Div(decimal.NewFromInt(100)))
				}

				dataPrice := utility.StrToDecimal(mainOrder.Price).Mul(percentage).Truncate(int32(tradeSet.PriceDigit))
				mainOrder.Childs[index].Price = dataPrice.String()

				priceDiff := dataPrice.Sub(prePrice).Abs()
				totalLossAmount = totalLossAmount.Add(lastNum.Mul(priceDiff))

				if ext.AddPositionType == 1 {
					buyPrice := utility.StrToDecimal(mainOrder.BuyPrice).Mul(utility.SafeDiv(ext.AddPositionVal, decimal.NewFromInt(100))).Truncate(2)
					mainOrder.Childs[index].Num = utility.SafeDiv(buyPrice, dataPrice).Truncate(int32(tradeSet.AmountDigit)).String()

				} else {
					mainOrder.Childs[index].Num = utility.SafeDiv(ext.AddPositionVal.Truncate(2), dataPrice).Truncate(int32(tradeSet.AmountDigit)).String()
				}

				//加库存
				lastNum = lastNum.Add(utility.StrToDecimal(mainOrder.Childs[index].Num))
				// 计算子订单
				if len(mainOrder.Childs[index].Childs) > 0 {
					calculateChildOrder(&mainOrder.Childs[index].Childs, &tradeSet, ext, lastNum, dataPrice, totalLossAmount, false)
				}
				//覆盖最近的订单价
				prePrice = dataPrice
				//减仓单
			case mainOrder.Childs[index].OrderType == 4:
				percentage := decimal.NewFromInt(1)

				if mainOrder.Site == "BUY" && ext.PriceRatio.Cmp(decimal.Zero) > 0 {
					percentage = decimal.NewFromInt(1).Sub(ext.PriceRatio.Div(decimal.NewFromInt(100)))
				} else if ext.PriceRatio.Cmp(decimal.Zero) > 0 {
					percentage = decimal.NewFromInt(1).Add(ext.PriceRatio.Div(decimal.NewFromInt(100)))
				}

				dataPrice := utility.StrToDecimal(mainOrder.Price).Mul(percentage).Truncate(int32(tradeSet.PriceDigit))
				mainOrder.Childs[index].Price = dataPrice.String()
				priceDiff := dataPrice.Sub(prePrice).Abs()
				totalLossAmount = totalLossAmount.Add(lastNum.Mul(priceDiff))

				//百分比减仓
				if ext.AddPositionType == 1 {
					mainOrder.Childs[index].Num = lastNum.Mul(ext.AddPositionVal.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit)).String()

				} else {
					logger.Error("减仓不能是固定数值")
				}

				//减库存
				lastNum = lastNum.Sub(utility.StrToDecimal(mainOrder.Childs[index].Num))
				// 计算子订单
				if len(mainOrder.Childs[index].Childs) > 0 {
					calculateChildOrder(&mainOrder.Childs[index].Childs, &tradeSet, ext, lastNum, dataPrice, totalLossAmount, false)
				}
				//覆盖最近的订单价
				prePrice = dataPrice
			}
		}
	}
	return nil
}

// 计算子订单信息
// isTpTp 是否是止盈后止损止盈
// totalLossAmount 累计亏损金额
func calculateChildOrder(orders *[]models.LinePreOrder, tradeSet *models2.TradeSet, ext models.LinePreOrderExt, lastNum decimal.Decimal, price decimal.Decimal, totalLossAmount decimal.Decimal, isTpTp bool) error {
	for index := range *orders {
		orderQuantity := lastNum.Truncate(int32(tradeSet.AmountDigit))
		percentage := decimal.NewFromInt(1)
		var addPercentage decimal.Decimal

		switch {
		//止盈
		case !isTpTp && (*orders)[index].OrderType == 1:
			addPercentage = ext.TakeProfitRatio
			if ext.TakeProfitNumRatio.Cmp(decimal.Zero) > 0 {
				orderQuantity = lastNum.Mul(ext.TakeProfitNumRatio.Div(decimal.NewFromInt(100))).Truncate(int32(tradeSet.AmountDigit))
			}
		//止损
		case !isTpTp && (*orders)[index].OrderType == 2:
			addPercentage = ext.StopLossRatio
		//止盈后止盈
		case isTpTp && (*orders)[index].OrderType == 1:
			addPercentage = ext.TpTpPriceRatio
		//止盈后止损
		case isTpTp && (*orders)[index].OrderType == 2:
			addPercentage = ext.TpSlPriceRatio
		}

		switch {
		//做多止盈、做空止损
		case (*orders)[index].OrderType == 1 && (*orders)[index].Site == "SELL", (*orders)[index].OrderType == 2 && (*orders)[index].Site == "BUY":
			percentage = decimal.NewFromInt(100).Add(addPercentage)

			//做多止损、做空止盈
		case (*orders)[index].OrderType == 2 && (*orders)[index].Site == "SELL", (*orders)[index].OrderType == 1 && (*orders)[index].Site == "BUY":
			percentage = decimal.NewFromInt(100).Sub(addPercentage)
		}

		//止盈亏损回本百分比
		if (*orders)[index].OrderType == 1 && totalLossAmount.Cmp(decimal.Zero) > 0 {
			lossPercent := totalLossAmount.Div(lastNum).Mul(decimal.NewFromInt(100)).Truncate(2)
			percentage = percentage.Add(lossPercent)
		}

		if percentage.Cmp(decimal.Zero) > 0 {
			percentage = percentage.Div(decimal.NewFromInt(100))
		}

		orderPrice := price.Mul(percentage).Truncate(int32(tradeSet.PriceDigit))
		(*orders)[index].Price = orderPrice.String()
		(*orders)[index].Num = orderQuantity.String()
		lastOrderQuantity := lastNum.Sub(orderQuantity).Truncate(int32(tradeSet.AmountDigit))

		//止盈后止盈、止盈后止损
		if len((*orders)[index].Childs) > 0 && lastOrderQuantity.Cmp(decimal.Zero) > 0 {
			calculateChildOrder(&(*orders)[index].Childs, tradeSet, ext, lastOrderQuantity, orderPrice, decimal.Zero, true)
		}
	}

	return nil
}

// 递归订单树
func GetOrderByPid(order *models.LinePreOrder, orders []models.LinePreOrder, pid int) {
	for _, v := range orders {
		if v.Pid == pid {
			GetOrderByPid(&v, orders, v.Id)

			order.Childs = append(order.Childs, v)
		}
	}
}
