package service

import (
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"

	"tower_api/dao"
	"tower_api/model"
	"tower_api/pkg/errcode"
	"tower_api/pkg/sdk"
	"tower_api/pkg/setting"
	"tower_api/pkg/util"
)

var (
	sySdk                 = sdk.NewSy()
	eightUSdk             = sdk.New8U()
	quickSdk              = sdk.NewQuick()
	ServerListSize        = 20
	RecommendGroup        = 10000
	MyRoleGroup           = 20000
	EmptyName             = ""
	FullServerMaintenance = -1 //全服维护标识
)

// 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, specialId int) *model.ServerWithTcp {
	//1 有指定进哪个逻辑区服
	if serverId > 0 {
		return p.GetFixedServer(openid, serverId, grayscale)
	}
	//2 选择玩家最近登录的服
	tcpInfo := p.GetLastServer(openid, grayscale, specialId)
	if tcpInfo.TcpId == 0 {
		//若新玩家则选择最新的服
		tcpInfo = p.GetNewestServer(grayscale, specialId)
	}
	return tcpInfo
}

// GetNewestServer 获取最新的服务器连接信息
func (p *PlayerService) GetNewestServer(grayscale, specialId int) *model.ServerWithTcp {
	res := dao.FindNewestServerInCache(grayscale, specialId)
	if res == nil {
		res = dao.FindNewestServerWithTcp(grayscale, specialId)
		if res.TcpId > 0 {
			dao.CacheNewestServer(res, 60, grayscale, specialId)
		}
	}
	return res
}

// GetLastServer 根据openid来获取玩家最近服务器连接信息
func (p *PlayerService) GetLastServer(openid string, grayscale, specialId int) *model.ServerWithTcp {
	return dao.FindLastServerWithTcp(openid, grayscale, specialId)
}

// 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, platId int16) string {
	token := p.GenToken()
	cacheMap := gin.H{
		"player_id": info.PlayerId,
		"token":     token,
		"tcp_id":    info.TcpId,
		"server_id": info.ServerId,
		"plat_id":   platId,
	}
	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
}

// GetAppSecretWithChannel 根据appid获取密钥和渠道信息和专服类型
func (p *PlayerService) GetAppSecretWithChannel(appid string) (appSecret, channelName string, specialId int) {
	appSecret, channelName, specialId = dao.FindAppSecretWithChannel(appid, 1)
	return
}

func (p *PlayerService) CheckOpenid(appid, openid, secret, channelName 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合法性
	var (
		isCorrectOpenId bool
		err             error
	)
	channelName = strings.ToUpper(channelName)
	switch channelName {
	case "QUICK":
		isCorrectOpenId, err = quickSdk.CheckOpenId(appid, openid, secret)
	case "8U":
		isCorrectOpenId, err = eightUSdk.CheckOpenId(appid, openid, secret)
	default:
		//默认走盛也的
		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) 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", "special_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, token string) 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, channelName, specialId := p.GetAppSecretWithChannel(appId)
	ret.SpecialId = specialId
	if secret == "" {
		ret.Code = errcode.InvalidAppid
		return ret
	}
	if channelName == "" {
		ret.Code = errcode.InvalidChannel
		return ret
	}
	//3 openid是否真实有效校验(测试环境下不进行校验)
	if setting.Settings.Server.RunMode != "debug" {
		//3.1 校验openid是否正确
		if strings.ToUpper(channelName) == "QUICK" {
			secret = strings.TrimSpace(token)
		}
		isOpenId, err := p.CheckOpenid(appId, openId, secret, channelName)
		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 || serverInfo.SpecialId != specialId {
			ret.Code = errcode.InvalidServerId
			return ret
		}
	}
	//6 校验成功返回
	return ret
}

// GetPlayerOwnershipServer 获取玩家最新所属的区服信息
// specialId 专服类型
func (p *PlayerService) GetPlayerOwnershipServer(openid string, grayscale, specialId int) *model.Server {
	//1 选择玩家最近登录的服
	serverInfo := dao.FindPlayerLastServer(openid, grayscale, specialId)
	if serverInfo.ServerId == 0 {
		//2若新玩家则选择最新的服(优先从缓存里面读取)-这里也要区分专服类型
		key := fmt.Sprintf("%s:%d:%d", dao.HomePageNewestServer, grayscale, specialId)
		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, specialId)
			infoByte, _ := json.Marshal(&serverInfo)
			dao.CacheCommonSet(key, string(infoByte), 2*60)
		} else {
			serverInfo = cache
		}
	}
	return serverInfo
}

// GetServerGroupNum 获取当前区服有多少分组
// specialId 专服类型
func (p *PlayerService) GetServerGroupNum(grayscale, specialId int) int {
	key := fmt.Sprintf("%s:%d:%d", dao.HomePageGroupNum, grayscale, specialId)
	numStr := dao.CacheCommonGetStr(key)
	num, _ := strconv.Atoi(numStr)
	if num > 0 {
		return num
	}
	size := float64(ServerListSize)
	total := dao.GetServerTotal(grayscale, specialId)
	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)
}

// CommonMsgNotify 公共写入redis的定时任务
func (p *PlayerService) CommonMsgNotify(playerId, tcpId int, event, body string) error {
	//组成数据
	content := TimerData{
		PlayerId: playerId,
		Event:    event,
		Body:     body,
	}
	cByte, _ := json.Marshal(&content)
	//增加任务的过期时间
	return dao.AddZSetMsg(tcpId, string(cByte))
}

// 根据渠道key来获取包信息
func (p *PlayerService) CheckPkgByChannelKey(channelKey string) (*model.Package, error) {
	info := dao.GetPackageByChannelKey(channelKey)
	if info.PackageId == 0 || info.Appsecret == "" {
		return nil, errors.New("未找到对应channel的包信息")
	}
	return info, nil
}

func (p *PlayerService) CheckPkgByAppId(appId string) (*model.Package, error) {
	info := dao.GetPackageByAppId(appId)
	if info.PackageId == 0 || info.Appsecret == "" || info.ChannelKey == "" {
		return nil, errors.New("未找到对应channel的包信息")
	}
	return info, nil
}

// CheckPlatForm 校验平台id是否正确
func (p *PlayerService) CheckPlatForm(platformId int16) (bool, error) {
	return true, nil
}

// GetTcpByCrossId 根据跨服信息来获取tcp信息
func (p *PlayerService) GetTcpByCrossId(crossId int) *model.Tcp {
	return dao.GetTcpByCrossId(crossId)
}

// OpenIdIsNew 检查openid是否是全新的
func (p *PlayerService) OpenIdIsNew(openid string) bool {
	userId := dao.FindUserIdByOpenId(openid)
	if userId > 0 {
		return false
	} else {
		return true
	}
}

// 根据appid获取对应包的专服ID
func (p *PlayerService) GetSpecialIdByAppid(appid string) (specialId int, eCode int) {
	//基础格式校验
	if !util.IsAppId(appid) {
		return 0, errcode.InvalidAppid
	}
	//先去缓存中查找对应的专服ID
	//specialIdStr := dao.GetSpecialId(appid)
	//if specialIdStr != "" {
	//	specialId, _ = strconv.Atoi(specialIdStr)
	//	return specialId, errcode.SUCCESS
	//}
	//缓存中没有则从数据库中获取对应appid的专服id
	packageInfo := dao.GetPackageByAppId(appid)
	if packageInfo.PackageId == 0 {
		return 0, errcode.InvalidAppid
	}
	//dao.CacheSpecialId(appid, specialId)
	return packageInfo.SpecialId, errcode.SUCCESS
}

// 登录增加是否维护中校验，若在维护中则不可以登录
func (p *PlayerService) CanLoginMaintenanceCheck(specialId int, openid string) bool {
	//1 判断当前是否在维护中,若不在维护中则可以登录
	nowUnix := time.Now().Unix()
	isMaintenance := p.IsUnderMaintenance(specialId, nowUnix)
	if !isMaintenance {
		return true
	}
	//2 维护中判断是否在白名单中,若在白名单中则可以登录
	return p.IsInWhiteList(openid)
}

// 是否在白名单中
// openid 用户标识
func (p *PlayerService) IsInWhiteList(openid string) bool {
	return dao.GetInWhiteList(openid)
}

// 获取当前专服是否在维护中
// specialId 专服id
// nowUnix 传入的时间戳
func (p *PlayerService) IsUnderMaintenance(specialId int, nowUnix int64) bool {
	//1 获取全服及当前专服的维护状态
	specialIds := []int{FullServerMaintenance, specialId}
	maintenanceUnix := dao.GetMaintenanceUnix(specialIds)
	//2 优先校验全服是否在维护
	fullServerUnix, _ := maintenanceUnix[FullServerMaintenance]
	if (fullServerUnix > 0) && (nowUnix >= fullServerUnix) {
		return true
	}
	//3 当前专服是否在维护
	specialUnix, _ := maintenanceUnix[specialId]
	if (specialUnix > 0) && (nowUnix >= specialUnix) {
		return true
	}
	return false
}

// 根据用户标识获取在某个服下的数据
func (p *PlayerService) PlayerIdInServer(openid string, serverId int) int {
	//获取缓存
	playerId := dao.GetOnInServerCache(openid, serverId)
	if playerId > 0 {
		return playerId
	}
	//缓存没有则获取db
	playerInfo := dao.GetOneInServer(openid, serverId)
	if playerInfo.PlayerId > 0 {
		playerId = playerInfo.PlayerId
		dao.SetOnInServerCache(openid, serverId, playerId)
	}
	return playerId
}

// 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"`       //是否是新服
	Naming      string `json:"naming"`      //区服冠名信息
	PlayerId    int    `json:"playerId"`    //对应区服的角色id
}

// WechatUserChecked 校验微信用户信息
type WechatUserChecked struct {
	Grayscale int
	UserId    int
	Code      int
	SpecialId 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"`
}
