package luckybox_m

import (
	"context"
	"fmt"
	"git.hilo.cn/hilo-common/domain"
	"git.hilo.cn/hilo-common/mylogrus"
	"git.hilo.cn/hilo-common/resource/mysql"
	"git.hilo.cn/hilo-common/resource/redisCli"
	"git.hilo.cn/hilo-common/utils"
	redis2 "github.com/go-redis/redis/v8"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"hilo-group/_const/enum/luckybox_e"
	"hilo-group/_const/redis_key"
	"hilo-group/domain/model"
	"hilo-group/myerr"
	"math/rand"
	"strconv"
	"strings"
	"time"
)

type LuckyboxCycleSum struct {
	mysql.Entity
	*domain.Model `gorm:"-"`
	DiamondNum    mysql.Num
	UserId        mysql.ID
	Cycle         mysql.Str
}

// 幸运盒子
type Luckybox struct {
	mysql.Entity
	*domain.Model `gorm:"-"`
	DiamondNum    mysql.Num
}

// 幸运盒子奖励
type LuckyboxAward struct {
	mysql.Entity
	*domain.Model `gorm:"-"`
	//可能是id,也可能是diamondNum
	AwardN                uint64
	AwardType             luckybox_e.AwardTypeLuckyboxAward
	PicUrl                string
	DayN                  mysql.Num
	DayTotalN             mysql.Num
	DayStr                mysql.Str
	Probability           mysql.Num
	IsUse                 mysql.YesNo
	IsPublicScreen        mysql.YesNo
	IsScrollScreen        mysql.YesNo
	IntervalN             mysql.Num //间隔次数才能再次获取
	ProtectionSecond      mysql.Num // 保护的时长
	ProtectionSum         mysql.Num //保护的金额
	ProtectionProbability mysql.Num //变成的概率
}

func LuckyboxAwardInit(model *domain.Model, awardN mysql.ID, awardType luckybox_e.AwardTypeLuckyboxAward, picUrl string, dayN mysql.Num, dayTotalN mysql.Num, probability mysql.Num, isUse mysql.YesNo, isPublicScreen mysql.YesNo, isScrollScreen mysql.YesNo, intervalN mysql.Num) *LuckyboxAward {
	return &LuckyboxAward{
		Model:          model,
		AwardN:         awardN,
		AwardType:      awardType,
		PicUrl:         picUrl,
		DayN:           dayN,
		DayTotalN:      dayTotalN,
		DayStr:         time.Now().Format(utils.COMPACT_DATE_FORMAT),
		Probability:    probability,
		IsUse:          isUse,
		IsPublicScreen: isPublicScreen,
		IsScrollScreen: isScrollScreen,
		IntervalN:      intervalN,
	}
}

func GetLuckyboxAwardNilOrErr(model *domain.Model, id mysql.ID) (*LuckyboxAward, error) {
	luckyboxAward := LuckyboxAward{}
	if err := model.Db.Model(&LuckyboxAward{}).First(&luckyboxAward, id).Error; err != nil {
		return nil, myerr.WrapErr(err)
	}
	luckyboxAward.Model = model
	return &luckyboxAward, nil
}

func (luckyboxAward *LuckyboxAward) Edit(awardN mysql.ID, awardType luckybox_e.AwardTypeLuckyboxAward, picUrl string, dayN mysql.Num, dayTotalN mysql.Num, probability mysql.Num, isUse mysql.YesNo, isPublicScreen mysql.YesNo, isScrollScreen mysql.YesNo, intervalN mysql.Num) *LuckyboxAward {
	luckyboxAward.AwardN = awardN
	luckyboxAward.AwardType = awardType
	luckyboxAward.PicUrl = picUrl
	luckyboxAward.DayN = dayN
	luckyboxAward.DayTotalN = dayTotalN
	luckyboxAward.Probability = probability
	luckyboxAward.IsUse = isUse
	luckyboxAward.IsPublicScreen = isPublicScreen
	luckyboxAward.IsScrollScreen = isScrollScreen
	luckyboxAward.IntervalN = intervalN
	return luckyboxAward
}

// 幸运盒子用户抽奖记录
type LuckyboxUser struct {
	mysql.Entity
	*domain.Model `gorm:"-"`
	UserId        mysql.ID
	// 0 代表没有中奖
	LuckyboxAwardId mysql.ID
	//0 代表 没有中奖
	AwardN uint64
	//0 代表 没有中奖
	AwardType luckybox_e.AwardTypeLuckyboxAward
	//购买幸运小盒子所需的钻石
	BuyDiamond mysql.Num
	//奖励价值
	AwardDiamond mysql.Num
}

// 获取最大的分数
func GetMaxLuckyboxDiamond(model *domain.Model, userId mysql.ID) (mysql.Num, error) {
	luckyboxUser := LuckyboxUser{}
	if err := model.Db.Model(&LuckyboxUser{}).Where(&LuckyboxUser{
		UserId: userId,
	}).Order("award_diamond desc").First(&luckyboxUser).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return 0, nil
		} else {
			return 0, myerr.WrapErr(err)
		}
	}
	return luckyboxUser.AwardDiamond, nil
}

func GetSumLuckyboxDiamond(model *domain.Model, userId mysql.ID) (uint64, error) {
	type summary struct {
		Sum uint64
	}
	result := summary{}
	err := model.Db.Model(&LuckyboxUser{}).
		Select("SUM(award_diamond) AS sum").
		Where(&LuckyboxUser{
			UserId: userId,
		}).
		First(&result).Error
	if err != nil {
		return 0, err
	}
	return result.Sum, err
}

func GetSumLuckyboxDiamondV2(_model *domain.Model, userId mysql.ID) (uint64, error) {
	var luckyboxTotalUser LuckyboxTotalUser
	if err := _model.Db.Model(LuckyboxTotalUser{}).Where("user_id = ?", userId).
		First(&luckyboxTotalUser).Error; err != nil {
		if err != gorm.ErrRecordNotFound {
			return 0, err
		} else {
			// gorm.RecordNotFound
			luckyboxTotalUser.UserId = userId
		}
	}
	if luckyboxTotalUser.Sync == mysql.YES {
		return uint64(luckyboxTotalUser.AwardDiamond), nil
	}
	// sync before
	var awardDiamond, awardN, buyDiamond uint64
	type summary struct {
		AwardN       uint64
		BuyDiamond   uint64
		AwardDiamond uint64
	}
	result := summary{}
	err := _model.Db.Model(&LuckyboxUser{}).
		Select("SUM(award_diamond) AS award_diamond,SUM(award_n) as award_n,SUM(buy_diamond) as buy_diamond").
		Where(&LuckyboxUser{
			UserId: userId,
		}).
		First(&result).Error
	if err != nil {
		return 0, err
	}
	awardDiamond += result.AwardDiamond
	awardN += result.AwardN
	buyDiamond += result.BuyDiamond
	// 备份表
	for i := 3; i <= 7; i++ {
		table := fmt.Sprintf("luckybox_user_20220%d", i)
		result := summary{}
		err := _model.Db.Table(table).
			Select("SUM(award_diamond) AS award_diamond,SUM(award_n) as award_n,SUM(buy_diamond) as buy_diamond").
			Where(&LuckyboxUser{
				UserId: userId,
			}).
			First(&result).Error
		if err != nil {
			return 0, err
		}
		awardDiamond += result.AwardDiamond
		awardN += result.AwardN
		buyDiamond += result.BuyDiamond
	}
	// 回写总表
	luckyboxTotalUser.Sync = mysql.YES
	luckyboxTotalUser.BuyDiamond = mysql.Num(buyDiamond)
	luckyboxTotalUser.AwardDiamond = mysql.Num(awardDiamond)
	luckyboxTotalUser.AwardN = awardN
	if err := model.Persistent(mysql.Db, &luckyboxTotalUser); err != nil {
		mylogrus.MyLog.Errorf("luckyboxTotalUser fail:%v-%v", luckyboxTotalUser, err)
	}
	return awardDiamond, err
}

func GetLuckyboxNilOrErr(model *domain.Model) (*Luckybox, error) {
	luckybox := Luckybox{}
	if err := model.Db.Model(&Luckybox{}).First(&luckybox).Error; err != nil {
		return nil, myerr.WrapErr(err)
	}
	luckybox.Model = model
	return &luckybox, nil
}

func GetLucyboxDiamond(model *domain.Model) (uint32, error) {
	if luckybox, err := GetLuckyboxNilOrErr(model); err != nil {
		return 0, err
	} else {
		return luckybox.DiamondNum, nil
	}
}

// 幸运盒子用户抽奖汇总(周)
type LuckyboxWeekUser struct {
	Date         string
	UserId       mysql.ID
	BuyDiamond   mysql.Num //购买幸运小盒子所需的钻石
	AwardDiamond mysql.Num //奖励价值
	AwardN       uint64
}

func (lwu *LuckyboxWeekUser) AddDiamond(db *gorm.DB) error {
	return db.Clauses(clause.OnConflict{
		DoUpdates: clause.Assignments(map[string]interface{}{
			"buy_diamond":   gorm.Expr("buy_diamond + ?", lwu.BuyDiamond),
			"award_diamond": gorm.Expr("award_diamond + ?", lwu.AwardDiamond),
			"award_n":       gorm.Expr("award_n + ?", lwu.AwardN),
		}),
	}).Create(lwu).Error
}

// 乐透抽奖
func (luckybox *Luckybox) Lottery(userId mysql.ID) (*LuckyboxUser, string, mysql.YesNo, error) {
	luckyboxAwards, err := luckybox.getAllValidAward()
	if err != nil {
		return nil, "", mysql.NO, err
	}
	//打乱
	rand.Shuffle(len(luckyboxAwards), func(i, j int) {
		t := luckyboxAwards[i]
		luckyboxAwards[i] = luckyboxAwards[j]
		luckyboxAwards[j] = t
	})

	var winLuckyboxAward *LuckyboxAward = nil
	var picUrl string = ""
	var isPublicScreen mysql.YesNo = mysql.NO
	//循环判断
	for i, r := range luckyboxAwards {
		random := luckybox.getRandomN()
		luckybox.Model.Log.Infof("luckybox lottery r.Probability :%v, random :%v, flag:%v userId:%v", r.Probability, random, r.Probability > random, userId)
		//间隔了多少, 产品认为只要是大奖,都间隔,同页面显示,数据库存储不太一样
		var intervalSum int64 = -1

		//产品的逻辑:无论抽多少次,都不能连续抽中大奖。
		//产品条件: 不能连续抽中大奖 && 满足概率膨胀 && 每日放出的次数
		//实现: 满足概率膨胀 && 不能连续抽中大奖 &&  每日放出的次数

		//当用户在最近48小时内:
		//下注金额-获奖金额≥100,000
		//则用户进入抽奖保护期,满足这一条件的中奖概率调整为
		//100,000大奖的中奖概率调整为100
		if r.ProtectionProbability > 0 {
			/*			s := luckySum{}
						if err := luckybox.Db.Model(&LuckyboxUser{}).Select("SUM(Buy_Diamond - Award_Diamond) AS money").Where(&LuckyboxUser{
							UserId: userId,
						}).Where("created_time > ?", time.Now().Add(-time.Second * time.Duration(r.ProtectionSecond))).First(&s).Error; err != nil {
							return nil, "", mysql.NO, myerr.WrapErr(err)
						}*/

			//只是移除一周,因为不知道,不同的奖励周期不一样
			redisCli.GetRedis().ZRemRangeByScore(context.Background(), redis_key.GetLuckyboxBuyAward(userId), "0", strconv.FormatInt(time.Now().AddDate(0, 0, -7).Unix(), 10))
			//

			var sumMoney int64 = 0
			if zList, err := redisCli.GetRedis().ZRevRangeByScore(context.Background(), redis_key.GetLuckyboxBuyAward(userId), &redis2.ZRangeBy{
				Min: strconv.FormatInt(time.Now().Add(-time.Second*time.Duration(r.ProtectionSecond)).Unix(), 10),
				Max: "+inf",
			}).Result(); err != nil {
				return nil, "", mysql.NO, myerr.WrapErr(err)
			} else {
				for i, _ := range zList {
					zs := strings.Split(zList[i], "_")
					if len(zs) > 1 {
						if money, err := strconv.ParseInt(zs[1], 10, 64); err != nil {
							return nil, "", mysql.NO, myerr.WrapErr(err)
						} else {
							sumMoney = sumMoney + money
						}
					}
				}
			}
			luckybox.Model.Log.Infof("luckybox lottery sumMoney:%v > ProtectionSum:%v, userId:%v", sumMoney, r.ProtectionSum, userId)

			if sumMoney >= int64(r.ProtectionSum) {
				r.Probability = r.ProtectionProbability
				luckybox.Model.Log.Infof("luckybox lottery sumMoney:%v > ProtectionSum:%v, userId:%v r.Probability:%v", sumMoney, r.ProtectionSum, userId, r.Probability)
			}
		}

		if r.Probability > random {
			//是否符合间隔多少次
			if r.IntervalN > 0 {
				//
				if intervalSum == -1 {
					//
					luckyboxUserMax := LuckyboxUser{}
					if err := luckybox.Db.Model(&LuckyboxUser{}).Where(&LuckyboxUser{
						UserId: userId,
					}).Where("luckybox_award_id in (select a.id from luckybox_award a where a.is_use = ? and a.interval_n > 0)", mysql.YES).Order("id desc").First(&luckyboxUserMax).Error; err != nil {
						/*	if err == gorm.ErrRecordNotFound {
								intervalSum = 0
							} else {
								return nil, "", mysql.NO, myerr.WrapErr(err)
							}*/
						if err != gorm.ErrRecordNotFound {
							return nil, "", mysql.NO, myerr.WrapErr(err)
						}
					}
					/*					else {
										//判断数量
										var c int64
										if err := luckybox.Db.Model(&LuckyboxUser{}).Where(&LuckyboxUser{
											UserId: userId,
										}).Where("id > ?", luckyboxUserMax.ID).Count(&c).Error; err != nil {
											return nil, "", mysql.NO, myerr.WrapErr(err)
										} else {
											intervalSum = c
										}
									}*/

					//luckyboxUserMax 可能为0
					//判断数量
					var c int64
					if err := luckybox.Db.Model(&LuckyboxUser{}).Where(&LuckyboxUser{
						UserId: userId,
					}).Where("id > ?", luckyboxUserMax.ID).Count(&c).Error; err != nil {
						return nil, "", mysql.NO, myerr.WrapErr(err)
					} else {
						intervalSum = c
					}
				}
				if int64(r.IntervalN) > intervalSum {
					luckybox.Model.Log.Infof("luckybox lottery IntervalN:%v, intervalSum:%v userId:%v", r.IntervalN, intervalSum, userId)
					continue
				}
			}
			dayStr := time.Now().Format(utils.COMPACT_DATE_FORMAT)
			//预先判断,拦截了部分。查询的时候,已经过滤了,
			//if luckyboxAwards[i].DayStr == dayStr && luckyboxAwards[i].DayN >= luckyboxAwards[i].DayTotalN {
			//	continue
			//}
			//
			txWinLuckyboxAward := luckybox.Db.Model(&luckyboxAwards[i])
			if err := txWinLuckyboxAward.Where("is_use = ?", mysql.YES).Where("day_total_n > 0").Where("day_n < ? or day_str <> ? ", luckyboxAwards[i].DayTotalN, dayStr).Updates(map[string]interface{}{"day_n": gorm.Expr("case when day_str = ? then day_n + 1 else 1 end", dayStr), "day_str": gorm.Expr(dayStr)}).Error; err != nil {
				//可能形成死锁,单做没抽中
				luckybox.Log.Errorf("txWinLuckyboxAward.Where sqlErr:%+v", myerr.WrapErr(err))
				continue
				//return nil, "", mysql.NO, myerr.WrapErr(err)
			}
			if txWinLuckyboxAward.RowsAffected == 0 {
				print(0)
				//没有更新任何数据,则不算中奖
			} else {
				//已更新了数据,中奖,跳出
				winLuckyboxAward = &luckyboxAwards[i]
				winLuckyboxAward.Model = luckybox.Model
				picUrl = luckyboxAwards[i].PicUrl
				isPublicScreen = luckyboxAwards[i].IsPublicScreen

				//
				luckybox.Model.Log.Infof("luckybox lottery win Probability:%v, random:%v, userId:%v", r.Probability, random, userId)
				//
				break
			}
			//严格控制数量,不允许并发超过日临界值值
			/*			if dayStr == luckyboxAwards[i].DayStr {
							//
							txWinLuckyboxAward := luckybox.Db.Model(&luckyboxAwards[i])
							if err := txWinLuckyboxAward.Where("day_n < ?", luckyboxAwards[i].DayTotalN).UpdateColumn("day_n", gorm.Expr("day_n + 1")).Error; err != nil {
								return nil, "", mysql.NO, myerr.WrapErr(err)
							}
							if txWinLuckyboxAward.RowsAffected == 0 {
								//没有更新任何数据,则不算中奖
							} else {
								//已更新了数据,中奖,跳出
								winLuckyboxAward = &luckyboxAwards[i]
								winLuckyboxAward.Model = luckybox.Model
								picUrl = luckyboxAwards[i].PicUrl
								isPublicScreen = luckyboxAwards[i].IsPublicScreen
								break
							}
							//winLuckyboxAward.DayN = winLuckyboxAward.DayN + 1
						} else {
							txWinLuckyboxAward := luckybox.Db.Model(&luckyboxAwards[i])
							//这里存在并发的问题, 方案1:加上where day_str = dayStr 解决并发,问题在于,对计算概率产生了影响, 方案2:容错并发,day_n=1冲刷了数据。低概率,可接受,产品都接受改数据。
							//情况1:先更新 day_n + 1,再更新day_n=1 没有问题。 情况2:先更新day_n=1,再更新day_n+1 没有问题,情况3:先更新day_n=1,再更新day_n=1 fixme:有问题, 解决方案:set case when
							if err := txWinLuckyboxAward.Where("day_n < ?", luckyboxAwards[i].DayTotalN).UpdateColumn("day_n", gorm.Expr("1")).UpdateColumn("day_str", gorm.Expr(dayStr)).Error; err != nil {
								return nil, "", mysql.NO, myerr.WrapErr(err)
							}
							if txWinLuckyboxAward.RowsAffected == 0 {
								//没有更新任何数据,则不算中奖
							} else {
								//已更新了数据,中奖,跳出
								winLuckyboxAward = &luckyboxAwards[i]
								winLuckyboxAward.Model = luckybox.Model
								picUrl = luckyboxAwards[i].PicUrl
								isPublicScreen = luckyboxAwards[i].IsPublicScreen
								break
							}
						}*/
		}
	}
	luckyboxUser := LuckyboxUser{
		Model:           luckybox.Model,
		UserId:          userId,
		LuckyboxAwardId: 0,
		AwardN:          0,
		AwardType:       0,
		BuyDiamond:      luckybox.DiamondNum,
		AwardDiamond:    0,
	}
	//判断是否中奖
	if winLuckyboxAward != nil {
		if winLuckyboxAward.AwardType == luckybox_e.Diamond {
			luckyboxUser.LuckyboxAwardId = winLuckyboxAward.ID
			luckyboxUser.AwardType = winLuckyboxAward.AwardType
			luckyboxUser.AwardN = winLuckyboxAward.AwardN
			luckyboxUser.AwardDiamond = mysql.Num(winLuckyboxAward.AwardN)
		} else {
			luckybox.Log.Errorf("Lottery AwardType:%v err", winLuckyboxAward.AwardType)
		}
	}
	return &luckyboxUser, picUrl, isPublicScreen, nil
}

// 获取全部的奖励规则
func (luckybox *Luckybox) getAllValidAward() ([]LuckyboxAward, error) {
	dateStr := time.Now().Format(utils.COMPACT_DATE_FORMAT)
	luckyboxAwards := []LuckyboxAward{}
	if err := luckybox.Db.Model(&LuckyboxAward{}).Where(&LuckyboxAward{
		IsUse: mysql.YES,
	}).Where("(day_total_n > day_n and day_str = ?) or (day_str <> ?)", dateStr, dateStr).Find(&luckyboxAwards).Error; err != nil {
		return nil, myerr.WrapErr(err)
	}
	return luckyboxAwards, nil
}

// 获取随机数
func (luckybox *Luckybox) getRandomN() uint32 {
	return uint32(rand.Intn(100000))
}

// 幸运盒子用户抽奖汇总(总)
type LuckyboxTotalUser struct {
	mysql.Entity
	UserId       mysql.ID
	BuyDiamond   mysql.Num //购买幸运小盒子所需的钻石
	AwardDiamond mysql.Num //奖励价值
	AwardN       uint64
	Sync         mysql.YesNo
}