package service

import (
	"errors"
	"fmt"
	"strings"

	"github.com/bytedance/sonic"
	"github.com/gin-gonic/gin"
	"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"

	"go-admin/app/admin/models"
	"go-admin/app/admin/service/dto"
	"go-admin/common/actions"
	cDto "go-admin/common/dto"
	"go-admin/common/global"
	"go-admin/common/helper"
	commonModels "go-admin/models"
	models2 "go-admin/models"
	"go-admin/pkg/utility"
	"go-admin/services/binanceservice"
	"go-admin/services/cacheservice"
)

type LineSymbol struct {
	service.Service
}

// GetPage 获取LineSymbol列表
func (e *LineSymbol) GetPage(c *dto.LineSymbolGetPageReq, p *actions.DataPermission, list *[]models.LineSymbol, count *int64) error {
	var err error
	var data models.LineSymbol

	err = e.Orm.Model(&data).
		Scopes(
			cDto.MakeCondition(c.GetNeedSearch()),
			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
			// actions.Permission(data.TableName(), p),
		).
		Find(list).Limit(-1).Offset(-1).
		Count(count).Error
	if err != nil {
		e.Log.Errorf("LineSymbolService GetPage error:%s \r\n", err)
		return err
	}

	var key string

	if c.Type == "1" {
		key = fmt.Sprintf(global.TICKER_SPOT, c.ExchangeType, "*")
	} else {
		key = fmt.Sprintf(global.TICKER_FUTURES, c.ExchangeType, "*")
	}

	tickers, _ := helper.DefaultRedis.GetAllKeysAndValues(key)
	allTicker := make(map[string]commonModels.TradeSet)
	tradeSet := commonModels.TradeSet{}

	for _, v := range tickers {
		sonic.Unmarshal([]byte(v), &tradeSet)

		if tradeSet.Coin != "" && tradeSet.Currency != "" {
			allTicker[tradeSet.Coin+tradeSet.Currency] = tradeSet
		}
	}

	for index := range *list {
		symbol := (*list)[index].Symbol

		if v, ok := allTicker[symbol]; ok {
			(*list)[index].LastPrice = utility.StrToDecimal(v.LastPrice)
		}
	}

	return nil
}

// GetSamePage 获取LineSymbol列表
func (e *LineSymbol) GetSamePage(c *dto.LineSymbolGetPageReq, p *actions.DataPermission, list *[]models.LineSymbol, count *int64) error {
	var err error
	var data models.LineSymbol
	tx := e.Orm.Model(&data).
		Scopes(
			cDto.Paginate(c.GetPageSize(), c.GetPageIndex()),
			actions.Permission(data.TableName(), p),
		)

	scopes := e.Orm.Model(&data).
		Scopes(
			actions.Permission(data.TableName(), p),
		)
	if c.ExchangeType != "" {
		tx.Where("exchange_type = ?", c.ExchangeType)
		scopes.Where("exchange_type = ?", c.ExchangeType)
	}

	if c.Symbol != "" {
		tx.Where("symbol = ?", c.Symbol)
		scopes.Where("symbol = ?", c.Symbol)
	}
	err = scopes.Select("count(id) as number").Group("symbol").Having("number > ?", 1).Count(count).Error
	if err != nil {
		e.Log.Errorf("LineSymbolService GetSamePage count:%s \r\n", err)
		return err
	}
	err = tx.Select("count(id) as number,symbol").Group("symbol").Having("number > ?", 1).
		Find(list).Limit(-1).Offset(-1).
		Error

	if err != nil {
		e.Log.Errorf("LineSymbolService GetSamePage error:%s \r\n", err)
		return err
	}
	return nil
}

// 导出excel
func (e *LineSymbol) ExportExcel(c *gin.Context, p *actions.DataPermission, req *dto.LineSymbolGetPageReq) error {
	list := make([]models.LineSymbol, 0)
	datas := make([]dto.LineSymbolExportResp, 0)
	var data models.LineSymbol
	var fileName string

	err := e.Orm.Model(&data).
		Scopes(
			cDto.MakeCondition(req.GetNeedSearch()),
			// actions.Permission(data.TableName(), p),
		).
		Find(&list).Error

	if err != nil {
		return err
	}

	exchanges := []string{}
	exchangeFutMap := make(map[string]models2.TradeSet)
	exchangeSpotMap := make(map[string]models2.TradeSet)

	for _, v := range list {
		if !utility.ContainsStr(exchanges, v.ExchangeType) {
			key := fmt.Sprintf(global.TICKER_FUTURES, v.ExchangeType, "*")
			key2 := fmt.Sprintf(global.TICKER_SPOT, v.ExchangeType, "*")
			futs, _ := helper.DefaultRedis.GetAllKeysAndValues(key)
			spots, _ := helper.DefaultRedis.GetAllKeysAndValues(key2)
			trade := models2.TradeSet{}

			for _, v2 := range futs {
				if v2 != "" {
					sonic.Unmarshal([]byte(v2), &trade)

					if trade.LastPrice != "" {
						exchangeFutMap[v.ExchangeType+"_"+trade.Coin+trade.Currency] = trade
					}
				}
			}

			for _, v2 := range spots {
				if v2 != "" {
					sonic.Unmarshal([]byte(v2), &trade)

					if trade.LastPrice != "" {
						exchangeSpotMap[v.ExchangeType+"_"+trade.Coin+trade.Currency] = trade
					}
				}
			}

			exchanges = append(exchanges, v.ExchangeType)
		}
	}

	for _, v := range list {
		item := dto.LineSymbolExportResp{
			ExchangeType: v.ExchangeType,
			Symbol:       v.Symbol,
			Coin:         v.BaseAsset,
			Currency:     v.QuoteAsset,
		}

		if v.Type == "1" {
			item.SymbolType = "现货"
			if v, ok := exchangeSpotMap[v.ExchangeType+"_"+v.Symbol]; ok {
				if v.LastPrice != "" {
					item.LastPrice = v.LastPrice
				}
			}
		} else {
			item.SymbolType = "合约"
			if v, ok := exchangeFutMap[v.ExchangeType+"_"+v.Symbol]; ok {
				if v.LastPrice != "" {
					item.LastPrice = v.LastPrice
				}
			}
		}

		datas = append(datas, item)
	}

	if len(datas) == 0 {
		return errors.New("无数据")
	}

	if req.Type == "1" {
		fileName = "现货交易对"
	} else {
		fileName = "合约交易对"
	}

	err = helper.ExportExcel(c, fileName, datas, []string{})

	return err
}

// 获取所有交易对
func (e *LineSymbol) GetAll(req *dto.LineSymbolGetListReq) ([]string, error) {
	result := make([]string, 0)

	if err := e.Orm.Model(&models.LineSymbol{}).Where("type = ? AND exchange_type =?", req.Type, req.ExchangeType).Pluck("symbol", &result).Error; err != nil {
		return nil, err
	}

	return result, nil
}

// Get 获取LineSymbol对象
func (e *LineSymbol) Get(d *dto.LineSymbolGetReq, p *actions.DataPermission, model *models.LineSymbol) error {
	var data models.LineSymbol

	err := e.Orm.Model(&data).
		Scopes(
			actions.Permission(data.TableName(), p),
		).
		First(model, d.GetId()).Error
	if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
		err = errors.New("查看对象不存在或无权查看")
		e.Log.Errorf("Service GetLineSymbol error:%s \r\n", err)
		return err
	}
	if err != nil {
		e.Log.Errorf("db error:%s", err)
		return err
	}
	return nil
}

// Insert 创建LineSymbol对象
func (e *LineSymbol) Insert(c *dto.LineSymbolInsertReq) error {
	var err error
	var data models.LineSymbol
	c.Generate(&data)
	err = e.Orm.Create(&data).Error
	if err != nil {
		e.Log.Errorf("LineSymbolService Insert error:%s \r\n", err)
		return err
	}
	return nil
}

// Update 修改LineSymbol对象
func (e *LineSymbol) Update(c *dto.LineSymbolUpdateReq, p *actions.DataPermission) error {
	var err error
	var data = models.LineSymbol{}
	e.Orm.Scopes(
		actions.Permission(data.TableName(), p),
	).First(&data, c.GetId())
	c.Generate(&data)

	db := e.Orm.Omit("api_id").Save(&data)
	if err = db.Error; err != nil {
		e.Log.Errorf("LineSymbolService Save error:%s \r\n", err)
		return err
	}
	if db.RowsAffected == 0 {
		return errors.New("无权更新该数据")
	}
	return nil
}

// Remove 删除LineSymbol
func (e *LineSymbol) Remove(d *dto.LineSymbolDeleteReq, p *actions.DataPermission) error {
	var data models.LineSymbol

	db := e.Orm.Model(&data).
		Scopes(
			actions.Permission(data.TableName(), p),
		).Delete(&data, d.GetId())
	if err := db.Error; err != nil {
		e.Log.Errorf("Service RemoveLineSymbol error:%s \r\n", err)
		return err
	}
	if db.RowsAffected == 0 {
		return errors.New("无权删除该数据")
	}
	return nil
}

func (e *LineSymbol) GetSymbol(req *dto.LineSymbolGetListReq) (data []models.LineSymbol, err error) {
	var (
		spotSymbol, futSymbol []models.LineSymbol
	)
	err = e.Orm.Model(&models.LineSymbol{}).Where("type = '1' AND switch = 1 AND exchange_type =?", req.ExchangeType).Find(&spotSymbol).Error
	if err != nil {
		e.Log.Errorf("Service GetSpotSymbol error:%s \r\n", err)
		return data, err
	}
	err = e.Orm.Model(&models.LineSymbol{}).Where("type = '2' AND switch = 1 AND exchange_type =?", req.ExchangeType).Find(&futSymbol).Error
	if err != nil {
		e.Log.Errorf("Service GetFutSymbol error:%s \r\n", err)
		return data, err
	}
	//取两个的交集
	symbols := intersection(spotSymbol, futSymbol)
	return symbols, nil
}

// 泛型交集函数
func intersection[T comparable](slice1, slice2 []T) []T {
	result := []T{}
	seen := make(map[T]bool)

	for _, v := range slice1 {
		seen[v] = true
	}

	for _, v := range slice2 {
		if seen[v] {
			result = append(result, v)
			delete(seen, v) // 防止重复添加
		}
	}

	return result
}

// 重设现货交易对
func (e *LineSymbol) ResetSpotSymbol() error {
	tradeSets, deleteSymbols, err := binanceservice.GetSpotSymbols()

	if err != nil {
		logger.Error("获取币安现货交易对失败")
		return err
	}
	insertSymbols := make([]models.LineSymbol, 0)
	sysConfig := SysConfig{Service: service.Service{Orm: e.Orm}}

	var req = new(dto.SysConfigByKeyReq)
	var resp = new(dto.GetSysConfigByKEYForServiceResp)
	req.ConfigKey = "quote_volume_24hr"
	sysConfig.GetWithKey(req, resp)
	symbolBlackMap := getSymbolBlackMap(e, "1")
	type Ticker struct {
		Symbol string `json:"symbol"`
		Price  string `json:"price"`
	}
	// tickerSymbol := helper.DefaultRedis.Get(rediskey.SpotSymbolTicker).Val()
	// tickerSymbolMaps := make([]Ticker, 0)
	// sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps)
	tickerSymbolMaps, _ := cacheservice.GetExchangeTradeSets(global.EXCHANGE_BINANCE, 1)
	oldMapSymbols, err := getMapSymbol(e, "1")
	if err != nil {
		return err
	}

	//判断是否在黑名单、是否需要修改
	for symbol, tradeSet := range tradeSets {

		key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BINANCE, symbol)

		//判断是否在黑名单里面
		if _, ok := symbolBlackMap[symbol]; ok {
			helper.DefaultRedis.DeleteString(key)
			deleteSymbols = append(deleteSymbols, symbol)
			continue
		}

		val := helper.DefaultRedis.Get(key).Val()
		var spotTicker24h commonModels.TradeSet
		sonic.Unmarshal([]byte(val), &spotTicker24h)
		//成交量
		if spotTicker24h.Currency == "USDT" {
			if utility.StringToFloat64(spotTicker24h.QuoteVolume) < utility.StringToFloat64(resp.ConfigValue) {
				helper.DefaultRedis.DeleteString(key)
				deleteSymbols = append(deleteSymbols, symbol)
				continue
			}
		} else {

			var tickerPrice decimal.Decimal
			for _, symbolMap := range tickerSymbolMaps {
				if symbolMap.Symbol == strings.ToUpper(spotTicker24h.Currency+"USDT") {
					tickerPrice, _ = decimal.NewFromString(symbolMap.LastPrice)
				}
			}
			if tickerPrice.GreaterThan(decimal.Zero) {
				mul := decimal.NewFromFloat(utility.StringToFloat64(spotTicker24h.QuoteVolume)).Mul(tickerPrice)
				if mul.LessThan(decimal.NewFromFloat(utility.StringToFloat64(resp.ConfigValue))) {
					helper.DefaultRedis.DeleteString(key)
					deleteSymbols = append(deleteSymbols, symbol)
					continue
				}
			}
		}
		lineSymbol, _ := oldMapSymbols[symbol]

		if lineSymbol.Id <= 0 {
			lineSymbol.ExchangeType = global.EXCHANGE_BINANCE
			lineSymbol.Symbol = symbol
			lineSymbol.BaseAsset = tradeSet.Coin
			lineSymbol.QuoteAsset = tradeSet.Currency
			lineSymbol.Switch = "1"
			lineSymbol.Type = "1"
			if lineSymbol.Symbol == "" {
				continue
			}
			insertSymbols = append(insertSymbols, lineSymbol)
		}
	}

	//判断已经移除的交易对
	for _, v := range oldMapSymbols {
		if _, ok := tradeSets[v.Symbol]; !ok && !utility.ContainsStr(deleteSymbols, v.Symbol) {
			deleteSymbols = append(deleteSymbols, v.Symbol)
		}
	}

	groups, err := getSymbolGroups(e, "1")
	if err != nil {
		return err
	}

	if len(deleteSymbols) > 0 {
		for _, symbol := range deleteSymbols {
			for _, group := range groups {
				if group.Id > 0 && strings.Contains(group.Symbol, symbol) {
					split := strings.Split(group.Symbol, ",")
					value := utility.RemoveByValue(split, symbol)
					join := strings.Join(value, ",")
					e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", group.Id).Update("symbol", join)
				}
			}
		}

		batchDeleteBySymbols(deleteSymbols, "1", e)
	}

	if len(insertSymbols) > 0 {
		err := e.Orm.Model(&models.LineSymbol{}).Omit("api_id").Create(&insertSymbols).Error
		if err != nil {
			return err
		}
	}

	return nil
}

func getSymbolBlackMap(e *LineSymbol, symbolType string) map[string]models.LineSymbolBlack {
	symbolBlack := make([]models.LineSymbolBlack, 0)
	symbolBlackMap := make(map[string]models.LineSymbolBlack)

	e.Orm.Model(&models.LineSymbolBlack{}).Where("type = ?", symbolType).Find(&symbolBlack)

	for _, v := range symbolBlack {
		symbolBlackMap[v.Symbol] = v
	}
	return symbolBlackMap
}

// 批量删除 根据交易对名
// symbolType 1-现货 2-合约
func batchDeleteBySymbols(deleteSymbols []string, symbolType string, e *LineSymbol) {
	deleteSymbolArray := utility.SplitSlice(deleteSymbols, 100)

	for _, item := range deleteSymbolArray {
		e.Orm.Model(&models.LineSymbol{}).Where("type = ? AND symbol IN ? ", symbolType, item).Unscoped().Delete(&models.LineSymbol{})
	}
}

// 获取交易对分组
// symbolType 1-现货 2-合约
func getSymbolGroups(e *LineSymbol, symbolType string) ([]models.LineSymbolGroup, error) {
	groups := make([]models.LineSymbolGroup, 0)

	if err := e.Orm.Model(&models.LineSymbolGroup{}).Where("type = ? ", symbolType).Find(&groups).Error; err != nil {
		return nil, err
	}
	return groups, nil
}

// 获取所有交易对
// symbolType 1-现货 2-合约
func getMapSymbol(e *LineSymbol, symbolType string) (map[string]models.LineSymbol, error) {
	oldSymbols := make([]models.LineSymbol, 0)
	oldMapSymbols := make(map[string]models.LineSymbol)

	if err := e.Orm.Model(&models.LineSymbol{}).Where(" type = ?", symbolType).Find(&oldSymbols).Error; err != nil {
		return nil, errors.New("获取现有交易对失败")
	}

	for _, item := range oldSymbols {
		oldMapSymbols[item.Symbol] = item
	}
	return oldMapSymbols, nil
}

// 重设合约交易对
func (e *LineSymbol) ResetFuturesSymbol() error {
	tradeSets := make(map[string]commonModels.TradeSet, 0)

	if err := binanceservice.GetAndReloadSymbols(&tradeSets); err != nil {
		logger.Error("定时同步合约交易对失败", err)
	}
	//获取交易对24小时行情
	deleteSymbols, err := binanceservice.InitSymbolsTicker24h(&tradeSets)
	if err != nil {
		return err
	}

	sysConfig := SysConfig{Service: service.Service{Orm: e.Orm}}
	var req = new(dto.SysConfigByKeyReq)
	var resp = new(dto.GetSysConfigByKEYForServiceResp)
	req.ConfigKey = "quote_volume_24hr"
	sysConfig.GetWithKey(req, resp)
	symbols := make([]models.LineSymbol, 0)
	symbolBlackMap := getSymbolBlackMap(e, "2")
	type Ticker struct {
		Symbol string `json:"symbol"`
		Price  string `json:"price"`
	}
	// tickerSymbol := helper.DefaultRedis.Get(rediskey.FutSymbolTicker).Val()
	// tickerSymbolMaps := make([]Ticker, 0)
	// sonic.Unmarshal([]byte(tickerSymbol), &tickerSymbolMaps)
	tickerSymbolMaps, _ := cacheservice.GetExchangeTradeSets(global.EXCHANGE_BINANCE, 2)
	oldMapSymbols, err := getMapSymbol(e, "2")
	if err != nil {
		return err
	}

	//判断是否在黑名单、是否需要新增
	for symbol, tradeSet := range tradeSets {
		key := fmt.Sprintf(global.TICKER_FUTURES, global.EXCHANGE_BINANCE, symbol)

		//判断是否在黑名单里面
		if _, ok := symbolBlackMap[symbol]; ok {
			helper.DefaultRedis.DeleteString(key)
			deleteSymbols = append(deleteSymbols, symbol)
			continue
		}

		val := helper.DefaultRedis.Get(key).Val()
		var spotTicker24h commonModels.TradeSet
		sonic.Unmarshal([]byte(val), &spotTicker24h)
		//成交量
		if spotTicker24h.Currency == "USDT" {
			if utility.StringToFloat64(spotTicker24h.QuoteVolume) < utility.StringToFloat64(resp.ConfigValue) {
				helper.DefaultRedis.DeleteString(key)
				deleteSymbols = append(deleteSymbols, symbol)
				continue
			}
		} else {
			var tickerPrice decimal.Decimal
			for _, symbolMap := range tickerSymbolMaps {
				if symbolMap.Symbol == strings.ToUpper(spotTicker24h.Currency+"USDT") {
					tickerPrice, _ = decimal.NewFromString(symbolMap.LastPrice)
				}
			}
			if tickerPrice.GreaterThan(decimal.Zero) {
				mul := decimal.NewFromFloat(utility.StringToFloat64(spotTicker24h.QuoteVolume)).Mul(tickerPrice)
				if mul.LessThan(decimal.NewFromFloat(utility.StringToFloat64(resp.ConfigValue))) {
					helper.DefaultRedis.DeleteString(key)
					deleteSymbols = append(deleteSymbols, symbol)
					continue
				}
			}
		}
		lineSymbol, _ := oldMapSymbols[symbol]

		if lineSymbol.Id <= 0 {
			lineSymbol.ExchangeType = global.EXCHANGE_BINANCE
			lineSymbol.Symbol = symbol
			lineSymbol.BaseAsset = tradeSet.Coin
			lineSymbol.QuoteAsset = tradeSet.Currency
			lineSymbol.Switch = "1"
			lineSymbol.Type = "2"
			if lineSymbol.Symbol == "" {
				continue
			}
			symbols = append(symbols, lineSymbol)
		}
	}

	//交易所已经没有的交易对直接去除
	for _, v := range oldMapSymbols {
		if _, ok := tradeSets[v.Symbol]; !ok && !utility.ContainsStr(deleteSymbols, v.Symbol) {
			deleteSymbols = append(deleteSymbols, v.Symbol)
		}
	}

	groups, err := getSymbolGroups(e, "2")
	if err != nil {
		return err
	}

	if len(deleteSymbols) > 0 {
		for _, symbol := range deleteSymbols {
			//如果在交易对组里面
			for _, group := range groups {
				if group.Id > 0 {
					split := strings.Split(group.Symbol, ",")
					value := utility.RemoveByValue(split, symbol)
					join := strings.Join(value, ",")
					e.Orm.Model(&models.LineSymbolGroup{}).Where("id = ?", group.Id).Update("symbol", join)
				}
			}
		}

		batchDeleteBySymbols(deleteSymbols, "2", e)
	}

	if len(symbols) > 0 {
		err = e.Orm.Model(&models.LineSymbol{}).Omit("api_id").Create(&symbols).Error
		if err != nil {
			return err
		}
	}

	return nil
}
