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"
	"time"

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

type BinanceStrategyOrderService struct {
	service.Service
}

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

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

// 处理订单
func (e *BinanceStrategyOrderService) DoJudge(orderStrs []string, symbolType int, exchangeType string) {
	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.Symbol), 200, 50, 100*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, 1, exchangeType)

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

			if success {
				e.TriggerOrder(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
	startPrice := utility.StrToDecimal(beforePrice)
	lastPrice := utility.StrToDecimal(lastPrices[0].Member.(string))

	//时间差超过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)

	//价格没有变动
	if percentag.Cmp(decimal.Zero) == 0 {
		return result, nil
	}

	//满足条件
	switch order.CompareType {
	case 1:
		result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) > 0
	case 2:
		result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) >= 0
	case 5:
		result = percentag.Mul(decimal.NewFromInt(100)).Cmp(order.Percentag) == 0
	default:
		return result, errors.New("没有对应的类型")
	}

	return result, nil
}

// 触发委托单
func (e *BinanceStrategyOrderService) TriggerOrder(order dto.StrategyOrderRedisList, symbolType int) error {
	orders := make([]models.LinePreOrder, 0)
	if err := e.Orm.Model(&models.LinePreOrder{}).Where("main_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("获取系统设置失败")
	}

	tradeSet, _ := cacheservice.GetTradeSet(global.EXCHANGE_BINANCE, order.Symbol, symbolType)

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

	lastPrice := utility.StrToDecimal(tradeSet.LastPrice)

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

	var mainOrder models.LinePreOrder

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

	GetOrderByPid(&mainOrder, orders, mainOrder.Id)

	return nil
}

// 重新计算订单单价、数量
// 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_id =?", mainOrder.Id).Find(&exts).Error; err != nil {
		return errors.New("获取拓展信息失败")
	}

	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()

	for index := range mainOrder.Childs {
		//主单止盈
		if mainOrder.Child[index].OrderType == 1 {
			var ext models.LinePreOrderExt

			for _, v := range exts {
				if v.OrderId == mainOrder.Child[index].Id {
					ext = v
					break
				}
			}

			if ext.Id > 0 {
				var percent decimal.Decimal
				//多
				if mainOrder.Site == "BUY" {
					percent = decimal.NewFromInt(100).Add(ext.TakeProfitRatio)
				} else {
					percent = decimal.NewFromInt(100).Sub(ext.StopLossRatio)
				}

				childPrice := lastPrice.Mul(percent.Div(decimal.NewFromInt(100).Truncate(4))).Truncate(int32(tradeSet.PriceDigit))
				mainOrder.Child[index].Price = childPrice.String()
			}
		} else {
			//todo 重新计算
		}
	}
	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)
		}
	}
}
