package antiCheating

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"gadmin/config"
	"gadmin/internal/gorm/model"
	"gadmin/internal/gorm/query"
	"gadmin/package/gmdata"
	"gadmin/utility"
	"github.com/go-redis/redis"
	"github.com/sirupsen/logrus"
	"gorm.io/gen"
	"gorm.io/gorm"
	"io/ioutil"
	"os"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

type Combat struct {
	ctx context.Context
	sync.Mutex
	YearMonth string
}

type CombatOption func(*Combat)

func WithYearMonth(yearMonth string) CombatOption {
	return func(combat *Combat) {
		combat.YearMonth = yearMonth
	}
}

func NewCombat(options ...CombatOption) *Combat {
	combat := &Combat{
		ctx: context.Background(),
	}
	for _, f := range options {
		f(combat)
	}
	return combat
}
func (c *Combat) Run() {
	c.Lock()
	defer c.Unlock()
	logrus.Infof("异常数据筛选...")
	c.getJson()
	c.getCombatData()
}

// 读取角色json
var RoleMap map[int64]map[int]gmdata.Role
var SkillGroupMap map[int64][]gmdata.Exskill

func (c *Combat) getJson() {
	if len(RoleMap) != 0 && len(SkillGroupMap) != 0 {
		return
	}

	confPath := os.Getenv("JSON_PATH") + "/" + os.Getenv("JSON_VERSION")
	err := gmdata.LoadItemData(confPath, "Role.json", &gmdata.Roles)
	if err != nil {
		logrus.Errorf("读取Role.json错误:%v", err)
		return
	}

	RoleMap = make(map[int64]map[int]gmdata.Role)

	for _, item := range gmdata.Roles {
		if RoleMap[item.Type] == nil {
			RoleMap[item.Type] = make(map[int]gmdata.Role)
		}
		RoleMap[item.Type][item.Evolution] = item
	}
	err = gmdata.LoadItemData(confPath, "exskill.json", &gmdata.Exskills)
	if err != nil {
		logrus.Errorf("读取exskill.json错误:%v", err)
		return
	}
	SkillGroupMap = make(map[int64][]gmdata.Exskill)
	for _, item := range gmdata.Exskills {
		SkillGroupMap[int64(item.SkillGroup)] = append(SkillGroupMap[int64(item.SkillGroup)], item)
	}
}

func (c *Combat) coverRoleInfo(id int64) map[string]int {
	roleInfo := make(map[string]int)

	roleInfo["type"] = c.coverIDToSlice(id, 8) % 100
	roleInfo["evolution"] = c.coverIDToSlice(id, 4) % 1000
	roleInfo["level"] = c.coverIDToSlice(id, 1) % 1000
	roleInfo["break"] = int(id % 10)

	return roleInfo
}

func (c *Combat) coverIDToSlice(id int64, length int) int {
	idStr := strconv.FormatInt(id, 10)
	substring := idStr[:len(idStr)-length]
	result, _ := strconv.Atoi(substring)
	return result
}

func (c *Combat) getRoleSkillGroupIds(roleType int, evolution int, level int) []int {
	skillGroupIdArr := make([]int, 0)

	if role, ok := RoleMap[int64(roleType)][evolution]; ok {
		for i, item := range role.SkillUnlockLevel {
			if level >= item {
				skillGroupIdArr = append(skillGroupIdArr, role.Skill[i])
			}
		}
	}

	return skillGroupIdArr
}

func (c *Combat) getRoleSkillToID(skillGroupId, evolution int) (gmdata.Exskill, error) {
	if skillGroup, ok := SkillGroupMap[int64(skillGroupId)]; ok {
		sort.Slice(skillGroup, func(i, j int) bool {
			return skillGroup[i].SkillUnlock < skillGroup[j].SkillUnlock
		})
		exskill := gmdata.Exskill{}
		for _, skill := range skillGroup {
			if evolution >= 0 {
				exskill = skill
			}
		}
		return exskill, nil
	}
	return gmdata.Exskill{}, errors.New("未找到技能信息")

}

func (c *Combat) getMax(x, y float64) float64 {
	if x > y {
		return x
	}
	return y
}

// 获取当月战斗数据的player_id集合
func (c *Combat) getCombatData() {
	table := config.DB.Scopes(model.ChapterLogsUserDetailTableSetDate(c.YearMonth))
	chapterModel := query.Use(table).ChapterLogsUserDetail.Table("")
	chapterQuery := chapterModel.WithContext(c.ctx)
	alarmModel := query.Use(config.DB).GameAlarmCombatLog
	alarmQuery := alarmModel.WithContext(c.ctx)
	//取lastId 先从redis，再从mysql
	lastIdMap, err := config.LogRedis.HGetAll("alarm:cheating:id").Result()
	if err != nil && !errors.Is(err, redis.Nil) {
		logrus.Errorf("redis获取作弊同步数据ID失败:%v", err)
		return
	}
	lastId, _ := strconv.ParseInt(lastIdMap["id"], 10, 64)
	yearMonth := lastIdMap["yearMonth"]
	if yearMonth != c.YearMonth {
		lastId = 0
	}
	if lastId == 0 {
		alarmInfo, err := alarmQuery.
			Where(alarmModel.YearMonth.Eq(c.YearMonth)).
			Order(alarmModel.OriginTime.Desc(), alarmModel.OriginID.Desc()).First()
		if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
			lastId = 0
		} else {
			lastId = alarmInfo.OriginID
		}
	}

	chapterList := make([]*model.ChapterLogsUserDetail, 0)
	chapterQuery = chapterQuery.WithContext(c.ctx).
		Select(chapterModel.ID, chapterModel.UserID, chapterModel.ServerID, chapterModel.Extra, chapterModel.EventAtNs)
	if lastId != 0 {
		chapterQuery = chapterQuery.Where(chapterModel.ID.Gt(lastId))
	}
	err = chapterQuery.FindInBatches(&chapterList, 1000, func(tx gen.Dao, batch int) error {
		// 批量从cos获取extra数据
		extraPaths := make([]string, 0)
		for _, item := range chapterList {
			if !strings.Contains(item.Extra, "{") {
				extraPaths = append(extraPaths, item.Extra)
			}
		}
		extraPathMap := &sync.Map{}
		var wg sync.WaitGroup
		smallPaths := utility.SplitStringsBySize(extraPaths, 100)
		for _, paths := range smallPaths {
			wg.Add(1)
			var wg1 sync.WaitGroup
			for _, path := range paths {
				wg1.Add(1)
				go func(path string) {
					defer wg1.Done()
					if response, err := config.GetCOSClient().Object.Get(context.Background(), path, nil); err != nil {
						logrus.Errorf("从cos中获取extra发生错误: %v", err)
						return
					} else {
						extraByt, err := ioutil.ReadAll(response.Body)
						response.Body.Close()
						if err != nil {
							logrus.Errorf("读取cos数据出错: %v", err)
							return
						} else {
							extraPathMap.Store(path, string(extraByt))
						}
					}
				}(path)
			}
			wg1.Wait()
			wg.Done()
		}
		wg.Wait()

		//遍历
		for _, item := range chapterList {
			var extra Extra
			extraStr := ""
			if !strings.Contains(item.Extra, "{") {
				if val, ok := extraPathMap.Load(item.Extra); ok {
					extraStr = val.(string)
				}
			} else {
				extraStr = item.Extra
			}

			err := json.Unmarshal([]byte(extraStr), &extra)
			if err != nil {
				logrus.Errorf("战斗数据解析失败: %v", err)
				continue
			}
			if len(extra.Harm) == 0 {
				continue
			}
			var tempHarm float64
			var data *model.GameAlarmCombatLog
			for _, harm := range extra.Harm {
				if harm[0] != 1 {
					continue
				}
				roleAttr := extra.Attr[harm[2]]
				roleInfo := c.coverRoleInfo(roleAttr.RoleId)
				if roleInfo["type"] != 1 {
					continue
				}
				skillGroupIds := c.getRoleSkillGroupIds(roleInfo["type"], roleInfo["evolution"], roleInfo["level"])
				if len(skillGroupIds) == 0 {
					continue
				}
				for _, skillGroupId := range skillGroupIds {
					skillInfo, err := c.getRoleSkillToID(skillGroupId, roleInfo["evolution"])
					if err != nil {
						return err
					}
					if skillInfo.SkillType == 1 {
						/* 倍率 */
						var rate = skillInfo.SkillValue[2]

						/** 伤害估值 */
						dd := float64(roleAttr.Attr.InitDps) * rate * 1.2
						// 如果实际伤害大于估值 说明异常
						if float64(harm[1]) > dd {
							//取最大值
							if tempHarm < float64(harm[1]) {
								tempHarm = float64(harm[1])
								ratio := fmt.Sprintf("%.2f", tempHarm/dd)
								ratioFloat, err := strconv.ParseFloat(ratio, 10)
								if err != nil {
									return err
								}
								roleInfoJson, err := json.Marshal(roleInfo)
								if err != nil {
									return err
								}
								data = &model.GameAlarmCombatLog{
									OriginID:   item.ID,
									OriginTime: strconv.FormatInt(item.EventAtNs, 10),
									YearMonth:  c.YearMonth,
									PlayerID:   item.UserID,
									ServerID:   item.ServerID,
									Dps:        float64(roleAttr.Attr.InitDps), //秒伤
									Rate:       rate,                           //技能倍率
									Valuation:  dd,                             //估值伤害
									Damage:     tempHarm,                       //实际伤害
									Ratio:      ratioFloat,                     //实际伤害/估值伤害
									RoleInfo:   string(roleInfoJson),
									AlarmData:  item.Extra,
									CreatedAt:  time.Now(),
								}
							}
						}
					}
				}
			}
			//循环harm完成
			if data != nil {
				err = alarmQuery.Create(data)
				if err != nil {
					logrus.Errorf("异常数据插入失败: %v", err)
					return err
				}
			}
		}
		//保存lastId至redis
		id := chapterList[len(chapterList)-1].ID
		err := config.LogRedis.HMSet("alarm:cheating:id", map[string]interface{}{"id": id, "yearMonth": c.YearMonth}).Err()
		if err != nil {
			logrus.Errorf("redis保存作弊同步数据ID失败")
			return err
		}
		time.Sleep(time.Millisecond * 100)
		return nil
	})
	if err != nil {
		logrus.Errorf("检测失败：%v", err)
		return
	}
}

type Extra struct {
	Attr []ExtraAttr `json:"attr"`
	Hp   [][]int     `json:"hp"`
	Harm [][]int64   `json:"harm"`
}

type ExtraAttr struct {
	RoleId int64         `json:"roleId"`
	State  int           `json:"state"`
	Attr   ExtraAttrInfo `json:"attr"`
}

type ExtraAttrInfo struct {
	RoleId     int     `json:"roleId"`
	InitAtk    int64   `json:"initAtk"`
	AtkUp      float64 `json:"atkUp"`
	InitEleAtk int64   `json:"initEleAtk"`
	EleAtkUp   float64 `json:"eleAtkUp"`
	BossUP     float64 `json:"BossUP"`
	MonUp      float64 `json:"MonUP"`
	InitDps    int64   `json:"initDps"`
}
