package service

import (
	"database/sql"
	"encoding/json"
	"errors"
	"fengkuangGameServerJob/dao"
	"fengkuangGameServerJob/model"
	"fengkuangGameServerJob/pkg/errcode"
	"fengkuangGameServerJob/pkg/sdk"
	"fengkuangGameServerJob/pkg/setting"
	"fengkuangGameServerJob/pkg/util"
	"fmt"
	"github.com/gin-gonic/gin"
	"math"
	"strconv"
	"strings"
	"time"
)

var (
	sySdk          = sdk.NewSy()
	ServerListSize = 20
	RecommendGroup = 10000
	MyRoleGroup    = 20000
	EmptyName      = "武神"
)

// PlayerService 玩家+区服相关的服务层
type PlayerService struct {
}

func NewPlayerService() *PlayerService {
	return &PlayerService{}
}

// GetAppointedServer 获取玩家是否有选服
func (p *PlayerService) GetAppointedServer(openid string) int {
	return dao.GetLoginServerId(openid)
}

// SetAppointedServer 设置玩家选服信息
func (p *PlayerService) SetAppointedServer(openid string, serverId int) {
	dao.SetLoginServerId(openid, serverId)
}

// DelAppointedServer 删除玩家的选服
func (p *PlayerService) DelAppointedServer(openid string) {
	dao.DelLoginServerId(openid)
}

// GetTcpWithPlayerId 获取玩家登录的tcp服务器+玩家ID(未注册=0)信息
func (p *PlayerService) GetTcpWithPlayerId(openid string, serverId int, grayscale int) *model.ServerWithTcp {
	//1 有指定进哪个逻辑区服
	if serverId > 0 {
		return p.GetFixedServer(openid, serverId, grayscale)
	}
	//2 选择玩家最近登录的服
	tcpInfo := p.GetLastServer(openid, grayscale)
	if tcpInfo.TcpId == 0 {
		//若新玩家则选择最新的服
		tcpInfo = p.GetNewestServer(grayscale)
	}
	return tcpInfo
}

// GetNewestServer 获取最新的服务器连接信息
func (p *PlayerService) GetNewestServer(grayscale int) *model.ServerWithTcp {
	res := dao.FindNewestServerInCache(grayscale)
	if res == nil {
		res = dao.FindNewestServerWithTcp(grayscale)
		if res.TcpId > 0 {
			dao.CacheNewestServer(res, 60, grayscale)
		}
	}
	return res
}

// GetLastServer 根据openid来获取玩家最近服务器连接信息
func (p *PlayerService) GetLastServer(openid string, grayscale int) *model.ServerWithTcp {
	return dao.FindLastServerWithTcp(openid, grayscale)
}

// GetFixedServer 根据openid来获取指定的服务器连接信息
func (p *PlayerService) GetFixedServer(openid string, serverId int, grayscale int) *model.ServerWithTcp {
	//1 先获取区服的基础信息
	info := dao.FindFixedServerWithTcp(serverId, grayscale)
	if info.TcpId == 0 {
		//找不到信息提前终止
		return info
	}
	//2 获取玩家的playerId
	playerId := dao.GetPlayerIdByOpenIdServerId(openid, serverId)
	info.PlayerId = playerId
	return info
}

// GenToken 生成token
func (p *PlayerService) GenToken() string {
	randStr := fmt.Sprintf("%d", util.Uint64Random(0, 0))
	return util.EncodeMD5(randStr)
}

// CacheLoginToken 保存登录的token
func (p *PlayerService) CacheLoginToken(info *model.ServerWithTcp, openid string) string {
	token := p.GenToken()
	cacheMap := gin.H{
		"player_id": info.PlayerId,
		"token":     token,
		"tcp_id":    info.TcpId,
		"server_id": info.ServerId,
	}
	dao.CacheLoginToken(openid, cacheMap, 12*3600)
	return token
}

// GetAppSecret 获取appSecret
func (p *PlayerService) GetAppSecret(appid string) string {
	//1 看redis缓存中有没有secret
	appSecret := dao.GetAppSecretFromCache(appid)
	if len(appSecret) > 0 {
		return appSecret
	}
	appSecret = dao.FindAppSecret(appid, 1)
	if appSecret == "" {
		return ""
	}
	dao.CacheAppSecret(appid, appSecret, 3600)
	return appSecret
}

func (p *PlayerService) CheckOpenid(appid, openid, secret string) (bool, error) {
	//1 判断是否在校验成功的集合中
	isOk := dao.GetOpenIdInCache(appid, openid, true)
	if isOk == true {
		return true, nil
	}
	//2 判断是否在校验失败的集合中
	isWrong := dao.GetOpenIdInCache(appid, openid, false)
	if isWrong == true {
		return false, errors.New("illegality openid")
	}

	//4 调用sdk校验openid合法性
	isCorrectOpenId, err := sySdk.CheckOpenId(openid, secret)
	if err != nil {
		return false, err
	}
	//5 处理校验结果
	expiredSeconds := 7 * 24 * 3600 //保存n天,减少重复校验
	if isCorrectOpenId == false {
		//5.1 不是正确的openid
		dao.CacheOpenId(appid, openid, false, expiredSeconds)
		return false, errors.New("illegality openid")
	}
	//5.2 是正确的openid
	dao.CacheOpenId(appid, openid, true, expiredSeconds)
	return true, nil
}

// InBlackList 判断ip是否在黑名单中
func (p *PlayerService) InBlackList(ip string) bool {
	return dao.IpInBlackList(ip)
}

// CanVisit 当前ip是否可以访问
func (p *PlayerService) CanVisit(ip string, maxTimes int) bool {
	times := dao.GetVisitTimesOneMinute(ip, 60)
	if times > maxTimes {
		//dao.AddIpBlackList(ip)
		return false
	}
	return true
}

func (p *PlayerService) AddResource(serverId, resourceId, resourceAmount int) {
	beginId := 0
	size := 200
	for {
		//1. 分页获取
		players := dao.GetPlayerList(beginId, size, []string{"player_id", "server_id"})
		for _, player := range players {
			beginId = player.PlayerId
			if player.ServerId != serverId {
				continue
			}
			//2. 增加资源
			affectRow := dao.IncrResource(player.PlayerId, resourceId, resourceAmount)
			if affectRow == 0 {
				//dao.AddResource(player.PlayerId, resourceId, resourceAmount)
			}
		}
		if len(players) < size {
			break
		}
	}
}

func (p *PlayerService) GetChannelUser(openid string) *model.ChannelUser {
	user := dao.FindChannelUser(openid, []string{"user_id", "user_state"})
	return user
}

// GetVersionInfo 获取版本信息
func (p *PlayerService) GetVersionInfo(version string) model.Version {
	return dao.FindVersion(version, []string{"version_id", "version_grayscale", "version_state", "version_audit"})
}

// ValidateServer 校验serverId是否正确
func (p *PlayerService) ValidateServer(serverId int) (*model.Server, error) {
	serverInfo := dao.FindServer(serverId, []string{"server_id", "server_state", "server_grayscale", "tcp_id"})
	if serverInfo.ServerId == 0 || serverInfo.ServerState != 1 {
		return serverInfo, errors.New("区服ID不正确")
	}
	return serverInfo, nil
}

// ValidateOpenIdAndServerId 公共校验数据
// code 错误码
// isGray 是否是灰度服 1是 0否
func (p *PlayerService) ValidateOpenIdAndServerId(appId, openId, version string, serverId int) WechatUserChecked {
	//1 参数基础格式校验
	ret := WechatUserChecked{Code: errcode.SUCCESS}
	if appId == "" || util.IsAppId(appId) == false {
		ret.Code = errcode.InvalidAppid
		return ret
	}
	if openId == "" || util.IsOpenId(openId) == false {
		ret.Code = errcode.InvalidOpenid
		return ret
	}
	if version == "" {
		ret.Code = errcode.InvalidVersionName
		return ret
	}
	//2 校验appid
	secret := p.GetAppSecret(appId)
	if secret == "" {
		ret.Code = errcode.InvalidAppid
		return ret
	}
	//3 openid是否真实有效校验(测试环境下不进行校验)
	if setting.Settings.Server.RunMode != "debug" {
		//3.1 校验openid是否正确
		isOpenId, err := p.CheckOpenid(appId, openId, secret)
		if err != nil || isOpenId == false {
			ret.Code = errcode.InvalidOpenid
			return ret
		}
		//3.2 校验版本信息是否正确,并判断是否是灰度服
		versionInfo := p.GetVersionInfo(version)
		if versionInfo.VersionId == 0 {
			ret.Code = errcode.InvalidVersionName
			return ret
		}
		ret.Grayscale = versionInfo.VersionGrayscale
	}
	//4 查看当前渠道用户的状态是否被禁用
	user := p.GetChannelUser(openId)
	if user.UserId > 0 && user.UserState != 1 {
		ret.Code = errcode.LoginForbidden
		return ret
	}
	ret.UserId = user.UserId
	//5 校验serverId
	if serverId > 0 {
		serverInfo, err := p.ValidateServer(serverId)
		//校验服务信息+是否与版本是否是灰度服一致
		if err != nil || int(serverInfo.ServerGrayscale) != ret.Grayscale {
			ret.Code = errcode.InvalidServerId
			return ret
		}
	}
	//6 校验成功返回
	return ret
}

// GetPlayerOwnershipServer 获取玩家最新所属的区服信息
func (p *PlayerService) GetPlayerOwnershipServer(openid string, grayscale int) *model.Server {
	//1 选择玩家最近登录的服
	serverInfo := dao.FindPlayerLastServer(openid, grayscale)
	if serverInfo.ServerId == 0 {
		//2若新玩家则选择最新的服(优先从缓存里面读取)
		key := fmt.Sprintf("%s:%d", dao.HomePageNewestServer, grayscale)
		jsonStr := dao.CacheCommonGetStr(key)
		var (
			err   error
			cache = new(model.Server)
		)
		if jsonStr != "" {
			err = util.ParseJsonStr(jsonStr, &cache)
		}
		if err != nil || cache == nil || cache.ServerId == 0 {
			serverInfo = dao.FindNewestServer(grayscale)
			infoByte, _ := json.Marshal(&serverInfo)
			dao.CacheCommonSet(key, string(infoByte), 2*60)
		} else {
			serverInfo = cache
		}
	}
	return serverInfo
}

// GetServerGroupNum 获取当前区服有多少分组
func (p *PlayerService) GetServerGroupNum(grayscale int) int {
	key := fmt.Sprintf("%s:%d", dao.HomePageGroupNum, grayscale)
	numStr := dao.CacheCommonGetStr(key)
	num, _ := strconv.Atoi(numStr)
	if num > 0 {
		return num
	}
	size := float64(ServerListSize)
	total := dao.GetServerTotal(grayscale)
	group := math.Ceil(float64(total) / size)
	num = util.F2I(group)
	dao.CacheCommonSet(key, num, 60*30)
	return num
}

// ServerIsNew 判断当前服的时间是否是新服
// t从数据库查询出来的时间
func (p *PlayerService) ServerIsNew(t sql.NullTime) int8 {
	if t.Valid == false {
		return 1
	}
	days := util.Days(t.Time.Unix(), time.Now().Unix())
	if days <= 8 {
		return 1
	}
	return 0
}

// HandlerBlackList1 禁言(只针对某个角色)
func (p *PlayerService) HandlerBlackList1(param BlackListParam, serverGrayscale uint8) {
	var (
		hashField    = fmt.Sprintf("%d", param.RoleId) //对应角色id
		blackListKey = dao.BlackList1
		err          error
	)
	if serverGrayscale > 0 {
		blackListKey = dao.GrayPrefix + blackListKey
	}
	if param.Status == 0 {
		// 若是解封
		err = dao.Redis().HDel(blackListKey, hashField).Err()
	} else {
		// 若是封禁,解析封禁时长
		var expireUnix int64 = -1 //永久封禁
		if param.Day > 0 || param.Hour > 0 {
			addSeconds := param.Day*86400 + param.Hour*3600
			expireUnix = time.Now().Unix() + int64(addSeconds)
		}
		err = dao.Redis().HSet(blackListKey, hashField, expireUnix).Err()
	}
	util.CheckRedisErr(err)
}

// GenTokenSign 生成封禁定时任务的token
func (p *PlayerService) GenTokenSign(param BlackListParam) string {
	var (
		tokenSign string
		field     string
	)

	if param.Type == 1 {
		field = param.Openid //对应openid
	} else {
		field = fmt.Sprintf("%d", param.RoleId) //对应角色id
	}
	if param.Status == 0 {
		// 解封
		tokenSign = fmt.Sprintf("%s_kickOut_%d", field, 1)
	} else {
		// 封禁
		tokenSign = fmt.Sprintf("%s_kickOut_%d", field, 0)
	}
	return tokenSign
}

// CommonMsgNotify 公共写入redis的定时任务
func (p *PlayerService) CommonMsgNotify(playerId, tcpId int, tokenSign, event, body string) error {
	//组成数据
	contentMap := map[string]interface{}{
		"player_id": playerId,
		"event":     event,
		"body":      body,
	}
	cByte, _ := json.Marshal(&contentMap)
	finalContentMap := map[string]interface{}{
		tokenSign: string(cByte),
	}
	// 写入redis
	pipeTx := dao.Redis().TxPipeline()
	//增加任务的过期时间
	dao.AddZSetMsg(pipeTx, tcpId, tokenSign)
	//任务标识与任务内容写入hash
	dao.AddHashMsg(pipeTx, tcpId, finalContentMap)
	_, err := pipeTx.Exec()
	return err
}

func (p *PlayerService) IsNewSeparate() time.Time {
	// 先获取有没有配置默认分割时间
	defaultDateStr := "2024-02-08 00:00:00"
	_date, _ := time.ParseInLocation("2006-01-02 15:04:05", defaultDateStr, time.Local)
	return _date
}

// IsNewOpenid 判断openid是否是新玩家
// platformType 放买量类型 在新用户的时候传 tx或者oe,其他情况就是undefined
func (p *PlayerService) IsNewOpenid(openid, appid, platformType string) int8 {
	platformType = strings.ToUpper(platformType)
	switch platformType {
	case "TX", "OE":
		//缓存中判断是否是新用户
		newExist := dao.GetUserNewOrOldFromCache(appid, openid, true)
		if newExist == true {
			return 1
		}
		//缓存中判断是否是老用户(主要为了减少查询)
		oldExist := dao.GetUserNewOrOldFromCache(appid, openid, false)
		if oldExist == true {
			return 0
		}
		//db中查询用户信息
		playerInfo := dao.GetUserLastLogin(openid)
		var isNewUser int8 = 0 //老用户
		if playerInfo.PlayerId == 0 {
			isNewUser = 1
			//记录到新用户的列表里面
			dao.SetIsNewUserInCache(appid, openid)
		} else {
			//记录到老用户列表里面
			dao.SetIsOldUserInCache(appid, openid, 86400)
		}
		return isNewUser
	default:
		//platformType=undefined或其他情况
		//缓存中判断是否是新用户，若有则是新用户，不然全是老用户
		newExist := dao.GetUserNewOrOldFromCache(appid, openid, true)
		if newExist == true {
			return 1
		}
		return 0
	}
}

// GetTcpByCrossId 根据跨服信息来获取tcp信息
func (p *PlayerService) GetTcpByCrossId(crossId int) *model.Tcp {
	return dao.GetTcpByCrossId(crossId)
}

// ServerHomePageItem 首页区服的结构体
type ServerHomePageItem struct {
	ServerID    int    `json:"serverID"`    //区服ID
	RoleLevel   int    `json:"roleLevel"`   //区服下角色等级 0标识没有角色
	ServerName  string `json:"serverName"`  //区服名称
	RoleName    string `json:"roleName"`    //玩家名
	ServerState uint8  `json:"serverState"` //区服状态 1爆满 2畅通 3维护
	IsNew       int8   `json:"isNew"`       //是否是新服
}

// WechatUserChecked 校验微信用户信息
type WechatUserChecked struct {
	Grayscale int
	UserId    int
	Code      int
}

// BlackListParam 封禁平台请求的参数
type BlackListParam struct {
	Channel  string `form:"channel" json:"channel"`
	Openid   string `form:"openid" json:"openid"`
	Type     int8   `form:"type" json:"type"`
	ServerId int    `form:"server_id" json:"server_id"`
	RoleId   int    `form:"role_id" json:"role_id"`
	Reason   string `form:"reason" json:"reason"`
	Status   int8   `form:"status" json:"status"`
	Hour     int    `form:"hour" json:"hour"`
	Day      int    `form:"day" json:"day"`
	Sign     string `form:"sign" json:"sign"`
}

// Validate 校验参数
// flag 1禁言 其他封禁
func (b *BlackListParam) Validate(flag int) error {
	//基础校验
	b.Channel = strings.TrimSpace(b.Channel)
	b.Openid = strings.TrimSpace(b.Openid)
	b.Sign = strings.TrimSpace(b.Sign)
	if b.Channel == "" || b.Openid == "" || b.Sign == "" || b.RoleId == 0 {
		return errors.New("参数错误")
	}
	//获取对应的app信息
	info := dao.GetPackageByChannelKey(b.Channel)
	if info.PackageId == 0 || info.Appsecret == "" {
		return errors.New("未找到对应channel的包信息")
	}
	secret := info.Appsecret
	//校验sign
	data := make(map[string]interface{})
	data["channel"] = b.Channel
	data["openid"] = b.Openid
	if flag == 1 {
		//禁言校验
		data["server_id"] = b.ServerId
		data["role_id"] = b.RoleId
	} else {
		//封禁校验
		data["type"] = b.Type
	}
	if util.GenSignMD5(data, secret) != b.Sign {
		return errors.New("签名错误")
	}
	return nil
}

// BlackListBody 黑名单定时任务的body内容
type BlackListBody struct {
	PlayerId int    `json:"playerId"`
	OpenId   string `json:"openid"`
	Type     int8   `json:"type"`
	Hour     int    `json:"hour"`
	Day      int    `json:"day"`
	Recovery int8   `json:"recovery"`
}
