package main

import (
	"../gameapi"
	"../log"
	"../mod"
	"../nnet"
	"../nt"
	pb "../protos"
	"../rpc"
	"../sms"
	"../utils"
	"errors"
	"github.com/go-redis/redis"
	"github.com/golang/protobuf/proto"
	"go.uber.org/zap"
	"strconv"
	"strings"
	"time"
)

var (
	ErrCheckUserRedisFailed = errors.New("check user in redis is failed")

	TokenTimouter *mod.TokenExpires
)

const (
	GAMEHISTORY = 7 //客户端导航栏界面 近期游戏
)

func do_error(ses nnet.ISession, req *pb.Request) {

}

func do_default(ses nnet.ISession, req *pb.Request) {
	def := &pb.S_Default{
		Ec: int32(pb.ErrCode_CMD_UNSUPPORT),
	}

	SendToClient(ses, req.Guid, def)
}

func do_C_Heartbeat(ses nnet.ISession, req *pb.Request) {
	hb := &pb.S_Heartbeat{
		Ts: time.Now().Unix(),
	}
	SendToClient(ses, req.Guid, hb)
}

func do_C_VerifiCode(ses nnet.ISession, req *pb.Request) {
	p := &pb.C_VerifiCode{}
	r := &pb.S_VerifiCode{}

	bind_mode := true
	err := proto.Unmarshal(req.Req, p)
	if nil != err {
		r.Ec = int32(pb.ErrCode_BAD_REQUEST)
		SendToClient(ses, req.Guid, r)
		return
	}
	if len(p.Phone) <= 0 {
		phone, err := mod.GetUserAttr(req.Guid, "phone")
		if nil != err {
			log.Error("do_C_VerifiCode", zap.Error(err), zap.Uint64("guid", req.Guid))
			r.Ec = int32(pb.ErrCode_FAILED)
			SendToClient(ses, req.Guid, r)
			return
		}
		p.Phone = phone
		bind_mode = false
	}
	// 手机号合法性检查
	if !utils.IsPhoneSimple(p.Phone) {
		log.Error("do_C_VerifiCode bad format", zap.String("phone", p.Phone), zap.Uint64("guid", req.Guid))
		r.Ec = int32(pb.ErrCode_BAD_FORMAT)
		SendToClient(ses, req.Guid, r)
		return
	}
	if bind_mode {
		_, err := mod.GetGuidByPhone(p.Phone)
		if err == nil { // found guid
			log.Warn("do_C_VerifiCode occupied", zap.String("phone", p.Phone), zap.Uint64("guid", req.Guid))
			r.Ec = int32(pb.ErrCode_OCCUPIED)
			SendToClient(ses, req.Guid, r)
			return
		}
	}
	// 手机号验证码发送频率控制
	_, err = mod.GetVerifiCode(req.Guid, p.Phone)
	if err == nil {
		log.Error("do_C_VerifiCode frequency", zap.Uint64("guid", req.Guid))
		r.Ec = int32(pb.ErrCode_DUP_OPER)
		SendToClient(ses, req.Guid, r)
		return
	}

	// 发送验证码
	code := utils.GenCaptcha(4)
	err = sms.SendSmsTo(p.Phone, code)
	if nil != err {
		log.Error("do_C_VerifiCode", zap.Error(err), zap.Uint64("guid", req.Guid))
		r.Ec = int32(pb.ErrCode_TIMEOUT)
		SendToClient(ses, req.Guid, r)
		return
	}
	// cache verification code
	err = mod.CacheVerifiCode(req.Guid, p.Phone, code)
	if nil != err {
		log.Error("do_C_VerifiCode", zap.Error(err), zap.Uint64("guid", req.Guid))
		r.Ec = int32(pb.ErrCode_UNKNOWN)
		SendToClient(ses, req.Guid, r)
		return
	}

	SendToClient(ses, req.Guid, r)
}

func do_C_BindPhone(ses nnet.ISession, req *pb.Request) {
	p := &pb.C_BindPhone{}
	r := &pb.S_BindPhone{}

	err := proto.Unmarshal(req.Req, p)
	if nil != err {
		r.Ec = int32(pb.ErrCode_BAD_REQUEST)
		SendToClient(ses, req.Guid, r)
		return
	}
	//  是否已绑定
	_, err = mod.GetGuidByPhone(p.Phone)
	if err == nil {
		log.Warn("do_C_VerifiCode occupied",zap.String("phone", p.Phone), zap.Uint64("guid", req.Guid))
		r.Ec = int32(pb.ErrCode_OCCUPIED)
		SendToClient(ses, req.Guid, r)
		return
	}
	// 密码长度
	if len(p.Password) < 6 {
		r.Ec = int32(pb.ErrCode_LENGTH)
		SendToClient(ses, req.Guid, r)
		return
	}
	// 验证
	code, err := mod.GetVerifiCode(req.Guid, p.Phone)
	if code != p.Code {
		r.Ec = int32(pb.ErrCode_TIMEOUT)
		SendToClient(ses, req.Guid, r)
		return
	}

	// 更新密码，手机
	err = mod.UpdateUserinfoToRedis(req.Guid, map[string]interface{}{
		"account":  p.Phone,
		"phone":    p.Phone,
		"password": p.Password,
	})
	if err != nil {
		r.Ec = int32(pb.ErrCode_UNKNOWN)
		SendToClient(ses, req.Guid, r)
		return
	}
	// 记录映射
	mod.SetupPhoneGuid(p.Phone, req.Guid)
	// 删除验证码
	mod.ClearVerifiCode(req.Guid, p.Phone)

	err = LoginPostBindPhone(req.Guid, ses, p)
	if nil != err {
		log.Error("LoginPostBindPhone", zap.Uint64("guid", req.Guid),
			zap.Error(err), zap.String("phone", p.Phone))
		r.Ec = int32(pb.ErrCode_FAILED)
		SendToClient(ses, req.Guid, r)
		return
	}

	//r.Phone = p.Phone
	//r.Password = p.Password
	//SendToClient(ses, req.Guid, r)
	//
	//// 事件
	//Ev.OnUserBindPhone(req.Guid, p)
}

func do_C_Login(ses nnet.ISession, req *pb.Request) {
	var login = &pb.C_Login{}
	s_login := &pb.S_Login{}

	err := proto.Unmarshal(req.Req, login)
	if nil != err {
		log.Warn("do_login", zap.Error(err))
		s_login.Ec = int32(pb.ErrCode_BAD_REQUEST)
		OnLoginAuthFinished(req.Guid, ses, s_login)
		return
	}

	log.Debug("do_login", zap.String("token", login.Token))
	// 服务器下线不接受新的连接
	if Online == 0 {
		s_login.Ec = int32(pb.ErrCode_MAINTAINING)
		OnLoginAuthFinished(req.Guid, ses, s_login)
		return
	}
	// 被踢后一段时间内禁止登录
	if defKickoffPolicy.UserKickoffBlocked(req.Guid) {
		s_login.Ec = int32(pb.ErrCode_FORBIDDEN)
		OnLoginAuthFinished(req.Guid, ses, s_login)
		return
	}
	user_registered := false
	user, err := mod.GetUserinfoFromRedis(req.Guid)
	if err == nil && len(user.Account) > 0 {
		if user.Status != 0 {
			s_login.Ec = int32(pb.ErrCode_FORBIDDEN)
			OnLoginAuthFinished(req.Guid, ses, s_login)
			return
		}
		user_registered = true
	}
	err = LoginPostAuth(req.Guid, login.Token, !user_registered, ses)
	if nil != err {
		s_login.Ec = int32(pb.ErrCode_FAILED)
		OnLoginAuthFinished(req.Guid, ses, s_login)
	}
	return
}

// 用户验证结束处理逻辑
// 1 session 管理，(包含踢掉重复的 session)
// 3 发送登录事件
// 4 POST 游戏列表
// 5 POST 公告，邮件等信息
func OnLoginAuthFinished(guid uint64, ses nnet.ISession, info *pb.S_Login) {
	info.Guid = guid
	// update client and server version
	info.VerMainServer = VERSION
	verClient, ok := nt.DynConf.Kv["client_version"]
	if ok {
		info.VerClient = utils.If_to_string(verClient, "")
	}
	//
	SendToClient(ses, guid, info)

	// 响应
	ServerStatus.TmpSessions.Remove(guid)
	ServerStatus.NewUserFlag.Remove(guid)
	LoginStatus.VerifyTimeout.Remove(guid)

	if info.Ec == 0 {
		// 查找是否有重复ID登录
		s, err := Server.PeekSession(guid)
		if err == nil {
			log.Debug("closed session:", zap.Uint64("sesId", s.Id()))
			// DO NOT remove line below.
			s.SetId(nnet.RUBBISH_SESSION_ID)
			//踢掉
			KillUserSession(guid, s, pb.ReasonDisconn_DUP)
			// 算踢掉
			//defKickoffPolicy.OnUserKickedoff(guid)

			time.Sleep(100)
		} else {
			// kickoff from other instances
			pair, err := rpc.GetUserAgentRpcUrl(guid)
			if err == nil && pair != rpc.GetThisRpcUrl() {
				args := rpc.RpcArgs{
					Cmd: "kickoff",
					Params: utils.H{
						"guids":  []uint64{guid},
						"reason": pb.ReasonDisconn_DUP,
					},
				}
				res := rpc.RpcResult{}
				err := rpc.RpcRequest(pair, &args, &res)
				if nil != err {
					log.Error("RpcRequest", zap.Error(err), zap.Uint64("guid", guid))
				}
				time.Sleep(100)
				log.Debug("kickoff from other server", zap.Uint64("guid", guid), zap.String("addr", pair))
			}
		}

		// 注册 session
		ses.UpdateId(guid)

		// 暂时只记录成功消息
		Ev.OnUserLogin(guid, info, ses)
	} else { // 登录失败
		KillUserSession(guid, ses, pb.ReasonDisconn_ERR_AUTH)
	}
}

func do_C_ChildrenItemsOf(ses nnet.ISession, req *pb.Request) {
	// 解包
	idpkg := &pb.C_ChildrenItemsOf{}

	res := &pb.S_ChildrenItemsOf{ParentId: idpkg.ParentId}

	err := proto.Unmarshal(req.Req, idpkg)
	if nil != err {
		log.Warn("req", zap.Uint64("guid", req.Guid), zap.Error(err))
		SendToClient(ses, req.Guid, res)
		return
	}
	if idpkg.ParentId < 0 {
		log.Warn("req", zap.Uint64("guid", req.Guid), zap.Int64("parentId", idpkg.ParentId), zap.Error(err))
		SendToClient(ses, req.Guid, res)
		return
	}

	res.ParentId = idpkg.ParentId
	if res.ParentId == GAMEHISTORY {

		guid := strconv.FormatUint(ses.Id(),10)
		gh, err := mod.GetGameHistory(guid)
		if err != nil && err != redis.Nil {
			log.Debug("RecoderUserGameHistory err", zap.String("guid",guid))
			return
		}
		gh_arr := strings.Split(gh,",")
		var order int32 = 0
		for i:= len(gh_arr)-1; i >= 0; i--{
			gameId, err := strconv.ParseInt(gh_arr[i],10,64)
			if err != nil{
				log.Debug("do_C_ChildrenItemsOf ParseInt err",zap.Error(err),zap.Any("gh_arr", gh_arr))
			}
			obj,ok := gameapi.FindGameConfBygameId(gameId)
			if ok {
				obj.Order = order
				order += 1
				res.List = append(res.List, &obj)
			}
		}

	}else{
		res.List = nt.DynConf.GetChidlrenItems(idpkg.ParentId)
	}

	SendToClient(ses, req.Guid, res)
}

func do_C_EnterGame(ses nnet.ISession, req *pb.Request) {
	var enter = &pb.C_EnterGame{}
	err := proto.Unmarshal(req.Req, enter)
	if nil != err {
		log.Debug("do_C_EnterGame", zap.Error(err))
		return
	}
	res := &pb.S_EnterGame{}
	res.GameId = enter.GameId

	Ev.OnUserPreEnterGame(req.Guid, enter.GameId, ses)

	if gameapi.Get_withdraw_task(strconv.FormatUint(ses.Id(),10)){ //检查该用户 是否还有未结算的牌局
		res.Ec = gameapi.XF_UNSETTLEMENT
		res.GameId = 0
	}else{

		gameapi.Dispatcher_EnterGame(ses.Id(), enter.GameId, enter.GameParam, res)
	}

	SendToClient(ses, ses.Id(), res)

	if res.Ec == int32(pb.ErrCode_OK) {
		Ev.OnUserEnterGame(req.Guid, enter.GameId, ses)
	}
}

func do_C_LeaveGame(ses nnet.ISession, req *pb.Request) {

	var leave = &pb.C_LeaveGame{}
	err := proto.Unmarshal(req.Req, leave)
	if nil != err {
		log.Debug("do_C_EnterGame", zap.Error(err))
		return
	}

	res 	:= &pb.S_LeaveGame{}
	balance := &pb.S_Ntf_UserBalance{}

	//!!!!!!先断开连接 再下分

	makerId  := gameapi.GetChannelByGameId(leave.GameId) //游戏厂商ID
	guid_str := strconv.FormatUint(ses.Id(), 10)
	gameId   := leave.GameId
	switch makerId {

		case gameapi.VG:

			res.Ec = gameapi.SUCCESS
			SendToClient(ses, ses.Id(), res) //通知客户端断开游戏连接
			gameapi.UpdateUserStateOut("VG", ses.Id(), gameId) //已经断线 清理玩家断线重连记录
			gameapi.CleanReconnectInfo("VG", ses.Id())		   //修改玩家位置 从游戏退出到大厅
			gameapi.Add_withdraw_delay(ses.Id(), gameId,
				guid_str, 5, 30, gameapi.VG_TASK_TAG)
			return

		case gameapi.CQ9_BY:
			res.Ec = gameapi.SUCCESS
			SendToClient(ses, ses.Id(), res)
			gameapi.UpdateUserStateOut("CQ9_BY", ses.Id(), gameId)
			gameapi.CleanReconnectInfo("CQ9_BY", ses.Id())
			gameapi.Add_withdraw_delay(ses.Id(), gameId,
				guid_str, 5, 30, gameapi.CQ9_TASK_TAG)
			return

		case gameapi.DBDZ_QP:
			res.Ec = gameapi.SUCCESS
			SendToClient(ses, ses.Id(), res)
			gameapi.UpdateUserStateOut("DBDZ_QP", ses.Id(), gameId)
			gameapi.CleanReconnectInfo("DBDZ_QP", ses.Id())
			gameapi.Add_withdraw_delay(ses.Id(), gameId,
				guid_str, 8, 50, gameapi.DBDZ_TASK_TAG)
			return
	}

	//!!!!!!先下分 再断开连接
	gameapi.Dispatcher_LeaveGame(ses.Id(), leave.GameId, leave.GameParam, balance, res)
	SendToClient(ses, ses.Id(), res)
	SendToClient(ses, ses.Id(), balance)

	if res.Ec == int32(pb.ErrCode_OK) {
		Ev.OnUserLeaveGame(req.Guid, leave.GameId, ses)
	}
}

// 玩家登录后推送首页游戏
// 2019-09-13: 热点游戏
func PostUser1stPageGames(ses nnet.ISession) {
	entries := nt.DynConf.GetChidlrenItems(int64(pb.GameNavbar_HOT_GAMES))

	SendToClient(ses, ses.Id(), &pb.S_HotGames{Hots: entries})
}

// 玩家登录后推送公告
func PostUserAnnouncement(ses nnet.ISession) {

}

// 登录后推送邮件
func PostUserMail(ses nnet.ISession) {

}

// 实时推送跑马灯
func PostUserMarquee(ses nnet.ISession) {

}

func PostUserUnreadCS(ses nnet.ISession, guid uint64) {
	// check
	num, err := mod.GetUnreadCS(guid)
	if err != nil {
		log.Error("PostUserUnreadCS", zap.Uint64("guid", guid), zap.Error(err))
		return
	}
	if num <= 0 {
		log.Debug("PostUserUnreadCS", zap.Uint64("guid", guid), zap.Int32("num", num))
		return
	}
	r := &pb.S_Ntf_Unread{
		CustomerService:      num,
	}
	SendToClient(ses, guid, r)
}

// 实时推送客户端地址配置
func PostClientAddrsConfig(ses nnet.ISession, guid uint64) {
	//mod.SaveClientAddrConfig()
	log.Debug("send S_Ntf_Config", zap.Uint64("guid", guid))
	SendToClient(ses, guid, nt.DynConf.ClientAddrsConf)
}

func mod_init() {
	mod.LoadScripts()
}
