package dao

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/go-redis/redis"

	"tower_api/model"
	"tower_api/pkg/util"
)

// AddRealTimeMsg 把消息发送到实时推送队列
func AddRealTimeMsg(pipeTx redis.Pipeliner, msgJsonStr string) {
	pipeTx.LPush(MsgNotifyListKey, msgJsonStr)
}

// FindNewestServerWithTcp 查找最新的区服及对应的tcp信息
func FindNewestServerWithTcp(grayscale, specialId int) *model.ServerWithTcp {
	var info model.ServerWithTcp
	err := DB().Raw(`
SELECT 
    s.server_id, t.tcp_id, t.port, t.ip, s.cross_id
FROM
    game_server AS s
        INNER JOIN
    game_tcp AS t ON s.tcp_id = t.tcp_id AND t.tcp_state = 1
WHERE
    s.server_state = 1 AND s.server_grayscale = ? AND s.special_id = ? AND s.running_status = 2
ORDER BY s.server_id DESC
LIMIT 1;`, grayscale, specialId).Scan(&info).Error
	util.CheckErr(err)
	return &info
}

// FindNewestServerInCache 从缓存中获取最新的服务器信息
func FindNewestServerInCache(grayscale, specialId int) *model.ServerWithTcp {
	jsonStr, err := Redis().Get(fmt.Sprintf("%s:%d:%d", LoginNewestServer, grayscale, specialId)).Result()
	util.CheckRedisErr(err)
	if len(jsonStr) == 0 {
		return nil
	}
	var res *model.ServerWithTcp
	err = json.Unmarshal([]byte(jsonStr), &res)
	if err != nil {
		return nil
	}
	return res
}

// CacheNewestServer 缓存最新的区服信息
func CacheNewestServer(info *model.ServerWithTcp, expired int, grayscale, specialId int) {
	b, _ := json.Marshal(&info)
	key := fmt.Sprintf("%s:%d:%d", LoginNewestServer, grayscale, specialId)
	_, err := Redis().Set(key, string(b), time.Duration(expired)*time.Second).Result()
	util.CheckRedisErr(err)
}

// FindLastServerWithTcp 根据openid查找玩家最近登录的区服信息
func FindLastServerWithTcp(openid string, grayscale, specialId int) *model.ServerWithTcp {
	var info model.ServerWithTcp
	err := DB().Raw(`
SELECT 
    player.player_id, t.tcp_id, t.ip, t.port, s.server_id, s.cross_id
FROM
    game_channel_user AS u
        INNER JOIN
    game_player AS player ON u.user_id = player.user_id
        AND player.state = 1
        INNER JOIN
    game_server AS s ON player.server_id = s.server_id
        AND s.server_state = 1 AND s.server_grayscale = ? AND s.special_id = ?
        INNER JOIN
    game_tcp AS t ON s.tcp_id = t.tcp_id AND t.tcp_state = 1
WHERE
    u.openid = ? AND u.user_state = 1
ORDER BY player.last_login DESC
LIMIT 1;`, grayscale, specialId, openid).Scan(&info).Error
	util.CheckErr(err)
	return &info
}

func FindFixedServerWithTcp(serverId int, grayscale int) *model.ServerWithTcp {
	var info model.ServerWithTcp
	err := DB().Raw(`
SELECT 
    0 AS player_id, t.tcp_id, t.ip, t.port, s.server_id, s.cross_id
FROM
    game_server AS s
        INNER JOIN
    game_tcp AS t ON s.tcp_id = t.tcp_id AND t.tcp_state = 1
WHERE
    s.server_id = ? AND s.server_state = 1 AND s.server_grayscale = ?
LIMIT 1;`, serverId, grayscale).Scan(&info).Error
	util.CheckErr(err)
	return &info
}

// FindTcpInfoByPlayerId 根据openid查找玩家最近登录的区服信息
func FindTcpInfoByPlayerId(playerId int) *model.ServerWithTcp {
	var info model.ServerWithTcp
	err := DB().Raw(`
SELECT 
    player.player_id, s.server_id, s.tcp_id, s.cross_id
FROM
    game_player AS player
        INNER JOIN
    game_server AS s ON player.server_id = s.server_id
WHERE
    player.player_id = ?
LIMIT 1;`, playerId).Scan(&info).Error
	util.CheckErr(err)
	return &info
}

// AddZSetMsg 定时任务消息添加到有序集合
func AddZSetMsg(tcpId int, content string) error {
	keyZSet := fmt.Sprintf("%s:%d", MsgNotifyListNew, tcpId)
	err := Redis().ZAdd(keyZSet, redis.Z{
		Score:  float64(time.Now().Unix()),
		Member: content,
	}).Err()
	return err
}

// CacheLoginToken 缓存登录的token
func CacheLoginToken(openid string, loginCacheMap map[string]interface{}, expireSeconds int) {
	key := LoginToken + ":" + openid
	pipeTx := Redis().TxPipeline()
	pipeTx.HMSet(key, loginCacheMap)
	pipeTx.Expire(key, time.Second*time.Duration(expireSeconds))
	_, err := pipeTx.Exec()
	util.CheckErr(err)
}

// FindAppSecret 通过appid找出密钥
func FindAppSecret(appid string, state uint8) string {
	var appSecret string
	err := DB().Raw(`
SELECT 
    appsecret
FROM
    game_package
WHERE
    appid = ? AND package_state = ?
LIMIT 1;`, appid, state).Scan(&appSecret).Error
	util.CheckErr(err)
	return appSecret
}

// FindAppSecretWithChannel 找到密钥和渠道名称
func FindAppSecretWithChannel(appid string, state uint8) (appSecret, channelName string, specialId int) {
	info := struct {
		Appsecret   string
		ChannelName string
		SpecialId   int
	}{}
	err := DB().Raw(`
SELECT 
    p.appsecret, c.channel_name, p.special_id
FROM
    game_package AS p
        INNER JOIN
    game_channel AS c ON p.channel_id = c.channel_id
WHERE
    p.appid = ? AND p.package_state = ?
LIMIT 1;`, appid, state).Scan(&info).Error
	util.CheckErr(err)
	return info.Appsecret, info.ChannelName, info.SpecialId
}

// CacheAppSecret 缓存app的密钥
func CacheAppSecret(appid, appSecret string, expired int) {
	pipe := Redis().Pipeline()
	pipe.HSet(MiniGameSecrets, appid, appSecret)
	duration, _ := pipe.TTL(MiniGameSecrets).Result()
	if int64(duration) <= 0 {
		pipe.Expire(MiniGameSecrets, time.Duration(expired)*time.Second)
	}
	_, err := pipe.Exec()
	util.CheckErr(err)
}

// GetAppSecretFromCache 从缓存中读取appSecret
func GetAppSecretFromCache(appid string) string {
	appSecret, err := Redis().HGet(MiniGameSecrets, appid).Result()
	if err != nil && err != redis.Nil {
		panic(err)
	}
	return appSecret
}

// CacheOpenId 缓存校验成功的openid减少再次校验
func CacheOpenId(appId, openid string, isOk bool, expired int) {
	var key string
	if isOk == true {
		key = fmt.Sprintf(OpenIdOk, appId)
	} else {
		key = fmt.Sprintf(OpenIdWrong, appId)
	}
	pipe := Redis().Pipeline()
	pipe.SAdd(key, openid)
	duration, _ := pipe.TTL(key).Result()
	if int64(duration) <= 0 {
		pipe.Expire(key, time.Duration(expired)*time.Second)
	}
	_, err := pipe.Exec()
	util.CheckErr(err)
}

// GetOpenIdInCache 判断当前openid是否已经校验过
func GetOpenIdInCache(appId, openid string, isOk bool) bool {
	var key string
	if isOk == true {
		key = fmt.Sprintf(OpenIdOk, appId)
	} else {
		key = fmt.Sprintf(OpenIdWrong, appId)
	}
	result, err := Redis().SIsMember(key, openid).Result()
	util.CheckErr(err)
	return result
}

// IpInBlackList 校验ip是否在黑名单中
func IpInBlackList(ip string) bool {
	result, err := Redis().SIsMember(LoginBlacklist, ip).Result()
	util.CheckRedisErr(err)
	return result
}

// AddIpBlackList 将ip添加到黑名单中
func AddIpBlackList(ip string) {
	_, err := Redis().SAdd(LoginBlacklist, ip).Result()
	util.CheckRedisErr(err)
}

// GetVisitTimesOneMinute 获取一分钟内的访问次数
func GetVisitTimesOneMinute(ip string, expired int) int {
	key := fmt.Sprintf(LoginRate, ip)
	times, err := Redis().Incr(key).Result()
	util.CheckRedisErr(err)
	duration, _ := Redis().TTL(key).Result()
	if int64(duration) <= 0 {
		Redis().Expire(key, time.Duration(expired)*time.Second)
	}
	return int(times)
}

// GetLoginServerId 获取登录指定的区服信息
func GetLoginServerId(openid string) int {
	key := fmt.Sprintf(LoginServer, openid)
	serverIdStr, err := Redis().Get(key).Result()
	util.CheckRedisErr(err)
	serverId, _ := strconv.Atoi(serverIdStr)
	return serverId
}

// SetLoginServerId 设置选择指定的区服
func SetLoginServerId(openid string, serverId int) {
	key := fmt.Sprintf(LoginServer, openid)
	err := Redis().Set(key, serverId, time.Duration(12*3600)*time.Second).Err()
	util.CheckRedisErr(err)
}

// DelLoginServerId 删除指定的区服
func DelLoginServerId(openid string) {
	key := fmt.Sprintf(LoginServer, openid)
	Redis().Del(key)
}

// GetPlayerIdByOpenIdServerId 通过openid和区服id来找玩家角色ID
func GetPlayerIdByOpenIdServerId(openid string, serverId int) int {
	var playerId int
	err := DB().Raw(`
SELECT 
    player.player_id
FROM
    game_channel_user AS u
        INNER JOIN
    game_player AS player ON u.user_id = player.user_id
        AND (player.server_id = ?
        AND player.state = 1)
WHERE
    u.openid = ?
LIMIT 1;`, serverId, openid).Scan(&playerId).Error
	util.CheckErr(err)
	return playerId
}

// FindChannelUser 获取渠道用户信息
func FindChannelUser(openid string, column []string) *model.ChannelUser {
	columnStr := strings.Join(column, ",")
	var cUser *model.ChannelUser
	err := DB().Select(columnStr).
		Where("openid = ?", openid).
		Limit(1).
		Find(&cUser).Error
	util.CheckErr(err)
	return cUser
}

// FindVersion 根据版本名称来获取版本信息
func FindVersion(version string, column []string) model.Version {
	columnStr := strings.Join(column, ",")
	var versionInfo model.Version
	err := DB().Select(columnStr).
		Where("version_name = ?", version).
		Where("version_state = 1").
		Limit(1).
		Find(&versionInfo).Error
	util.CheckErr(err)
	return versionInfo
}

// FindServer 根据区服ID来获取服务信息
func FindServer(serverId int, column []string) *model.Server {
	columnStr := strings.Join(column, ",")
	serverInfo := new(model.Server)
	err := DB().Select(columnStr).
		Where("server_id = ?", serverId).
		Limit(1).
		Find(&serverInfo).Error
	util.CheckErr(err)
	return serverInfo
}

func FindPlayerLastServer(openid string, grayscale, specialId int) *model.Server {
	var info model.Server
	err := DB().Raw(`
SELECT 
    s.server_id, s.server_name, s.naming, s.running_status, s.open_at
FROM
    game_channel_user AS u
        INNER JOIN
    game_player AS player ON u.user_id = player.user_id
        AND player.state = 1
        INNER JOIN
    game_server AS s ON player.server_id = s.server_id
        AND s.server_state = 1 AND s.server_grayscale = ? AND s.special_id = ?
WHERE
    u.openid = ? AND u.user_state = 1
ORDER BY player.last_login DESC
LIMIT 1;`, grayscale, specialId, openid).Scan(&info).Error
	util.CheckErr(err)
	return &info
}

// FindNewestServer 查找最新的区服
func FindNewestServer(grayscale, specialId int) *model.Server {
	var info model.Server
	err := DB().Raw(`
SELECT 
    s.server_id, s.server_name, s.naming, s.running_status, s.open_at
FROM
    game_server AS s
WHERE
    s.server_state = 1 AND s.server_grayscale = ? AND s.special_id = ? AND s.running_status = 2
ORDER BY s.server_id DESC
LIMIT 1;`, grayscale, specialId).Scan(&info).Error
	util.CheckErr(err)
	return &info
}

// GetServerTotal 获取正常运行中的区服数量
func GetServerTotal(grayscale, specialId int) int {
	var total int
	err := DB().Raw(`
SELECT 
	COUNT(server_id) AS total
FROM
	game_server
WHERE
	server_state = 1 AND
	server_grayscale = ? AND special_id = ?;`, grayscale, specialId).Scan(&total).Error
	util.CheckErr(err)
	return total
}

// GetRecommendServer 获取推荐的服务器
func GetRecommendServer(userId, grayscale, specialId int) *model.PlayerWithServer {
	var info model.PlayerWithServer
	err := DB().Raw(`
SELECT 
	s.server_id,
	s.server_name,
	s.naming,
	s.open_at,
	s.running_status,
	IFNULL(p.nick_name, '') AS nick_name,
	IFNULL(p.player_level, 0) AS player_level,
	IFNULL(p.player_id, 0) AS player_id
FROM
	game_server AS s
		LEFT JOIN
	game_player AS p ON s.server_id = p.server_id
		AND p.user_id = ?
WHERE
	s.server_state = 1 AND s.running_status = 2 AND s.server_grayscale = ? AND s.special_id = ?
ORDER BY s.server_id DESC
LIMIT 1;`, userId, grayscale, specialId).Scan(&info).Error
	util.CheckErr(err)
	return &info
}

// GetMyRoleServer 获取自己所属的区服列表
// grayscale 灰度服 1是灰度 0不是
func GetMyRoleServer(userId, grayscale, specialId int) []*model.PlayerWithServer {
	lists := make([]*model.PlayerWithServer, 0)
	err := DB().Raw(`
SELECT 
	s.server_id,
	s.server_name,
	s.naming,
	s.open_at,
	s.running_status,
	IFNULL(p.nick_name, '') AS nick_name,
	IFNULL(p.player_level, 0) AS player_level,
	IFNULL(p.player_id, 0) AS player_id
FROM
	game_player AS p
		INNER JOIN
	game_server AS s ON p.server_id = s.server_id AND s.server_grayscale = ? AND s.server_state = 1 AND s.special_id = ?
WHERE
	p.user_id = ?
ORDER BY p.player_level DESC
LIMIT 100;`, grayscale, specialId, userId).Scan(&lists).Error
	util.CheckErr(err)
	return lists
}

// GetPagingServer 分页获取区服信息
// userId 玩家渠道ID
// serverGrayscale 灰度服 1是 0不是
// specialId 专服类型
// begin 分页开始(page - 1) * size
// size 每页显示条数
func GetPagingServer(userId, grayscale, specialId, begin, size int) []*model.PlayerWithServer {
	lists := make([]*model.PlayerWithServer, 0)
	err := DB().Raw(`
SELECT 
	s.server_id,
	s.server_name,
	s.naming,
	s.open_at,
	s.running_status,
	IFNULL(p.nick_name, '') AS nick_name,
	IFNULL(p.player_level, 0) AS player_level,
	IFNULL(p.player_id, 0) AS player_id
FROM
	game_server AS s
		LEFT JOIN
	game_player AS p ON s.server_id = p.server_id
		AND p.user_id = ?
WHERE
	s.server_state = 1 AND s.server_grayscale = ? AND s.special_id = ?
ORDER BY s.server_id ASC
LIMIT ? , ?;`, userId, grayscale, specialId, begin, size).Scan(&lists).Error
	util.CheckErr(err)
	return lists
}

// GetPackageByChannelKey 根据channelKey来获取包信息
func GetPackageByChannelKey(channelKey string) *model.Package {
	var p model.Package
	err := DB().Where("channel_key = ?", channelKey).Limit(1).Find(&p).Error
	util.CheckErr(err)
	return &p
}

// GetTcpByCrossId 根据跨服ID来获取Tcp信息
func GetTcpByCrossId(crossId int) *model.Tcp {
	tcp := new(model.Tcp)
	err := DB().Raw(`
SELECT 
    IFNULL(t.tcp_id, 0) AS tcp_id, IFNULL(t.ip, '') AS ip
FROM
    game_server_cross AS c
        LEFT JOIN
    game_tcp AS t ON c.cross_tcp_id = t.tcp_id
WHERE
    c.cross_id = ?
LIMIT 1;`, crossId).Scan(&tcp).Error
	util.CheckErr(err)
	return tcp
}

// GetPackageByAppId 根据appid来获取包信息
func GetPackageByAppId(appId string) *model.Package {
	var p model.Package
	err := DB().Where("appid = ?", appId).Limit(1).Find(&p).Error
	util.CheckErr(err)
	return &p
}

func FindUserIdByOpenId(openid string) int {
	var userId int
	err := DB().Raw(`
SELECT 
    user_id
FROM
    game_channel_user
WHERE
    openid = ?
LIMIT 1;`, openid).Scan(&userId).Error
	util.CheckErr(err)
	return userId
}

// 获取专服维护时间
func GetMaintenanceUnix(specialIds []int) map[int]int64 {
	specialIdsCnt := len(specialIds)
	fields := make([]string, 0, specialIdsCnt)
	for _, val := range specialIds {
		fields = append(fields, fmt.Sprintf("%d", val))
	}
	//获取专服的维护时间戳
	result, err := Redis().HMGet(Maintenance, fields...).Result()
	util.CheckRedisErr(err)
	unixMap := make(map[int]int64)
	for i := 0; i < specialIdsCnt; i++ {
		tmpUnixStr, _ := result[i].(string)
		tmpUnix, _ := strconv.Atoi(tmpUnixStr)
		unixMap[specialIds[i]] = int64(tmpUnix)
	}
	return unixMap
}

// 根据openid检查否在白名单中
func GetInWhiteList(openid string) bool {
	result, err := Redis().SIsMember(WhiteList, openid).Result()
	util.CheckRedisErr(err)
	return result
}

// 获取用户在某个区服下角色信息
func GetOneInServer(openid string, serverId int) *model.Player {
	playerInfo := new(model.Player)
	err := DB().Raw(`
SELECT 
    IFNULL(p.nick_name, '') AS nick_name,
    IFNULL(p.player_level, 0) AS player_level,
    IFNULL(p.player_id, 0) AS player_id
FROM
    game_channel_user AS u
        LEFT JOIN
    game_player AS p ON u.user_id = p.user_id
        AND p.server_id = ?
WHERE
    u.openid = ?
LIMIT 1;`, serverId, openid).Scan(&playerInfo).Error
	util.CheckErr(err)
	return playerInfo
}

// 获取用户在区服下的角色id
func GetOnInServerCache(openid string, serverId int) int {
	cacheKey := IdsInServer + ":" + openid
	idStr, err := Redis().HGet(cacheKey, fmt.Sprintf("%d", serverId)).Result()
	util.CheckRedisErr(err)
	id, _ := strconv.Atoi(idStr)
	return id
}

// 缓存用户在区服下的角色id
func SetOnInServerCache(openid string, serverId, playerId int) {
	cacheKey := IdsInServer + ":" + openid
	err := Redis().HSet(cacheKey, fmt.Sprintf("%d", serverId), playerId).Err()
	util.CheckRedisErr(err)
	Redis().Expire(cacheKey, time.Second*3600*24)
}
