package account

import (
	"encoding/base64"
	"encoding/hex"
	"errors"
	"fmt"
	"mtp20access/client"
	"mtp20access/global"
	accountModel "mtp20access/model/account"
	"mtp20access/model/account/request"
	"mtp20access/packet"
	"mtp20access/rabbitmq"
	"mtp20access/res/pb"
	"sync"

	"mtp20access/utils"
	"strconv"

	commonRequest "mtp20access/model/common/request"

	"github.com/gin-gonic/gin"
	"github.com/gofrs/uuid"
	"github.com/golang/protobuf/proto"
	"go.uber.org/zap"
)

var (
	mtx          sync.RWMutex
	curSessionID int = 90000 // 本服务SessionID从90000开始，以避免与旧登录服务重叠
)

// Login 用户登录
func Login(req request.LoginReq, addr string) (loginaccount *accountModel.Loginaccount, token string, sessionID int, expiresAt int64, err error) {
	// 分别尝试用LoginID、LoginCode和手机号码进行登录
	loginaccount, _, err = getLoginAccount(req.UserName, req.Password)
	if err != nil {
		return
	}

	// 判断用户状态
	if loginaccount.LOGINSTATUS == 2 {
		err = errors.New("账户已冻结")
		return
	}
	if loginaccount.LOGINSTATUS == 3 {
		err = errors.New("账户已注销")
		return
	}

	// 生成Token，并写入Redis
	token, expiresAt, sessionID, err = buildRedisLoginInfo(*loginaccount, addr, req.ClientType)
	if err != nil {
		return
	}

	// 发送登录报文给总线
	loginReq := pb.LoginReq{}
	loginReq.LoginID = utils.SetPointValue(uint64(loginaccount.LOGINID))
	// loginReq.LoginPWD = utils.SetPointValue(strings.ToLower(utils.EncoderSha256(fmt.Sprintf("%v%s", loginReq.LoginID, oriPwd))))
	loginReq.LoginPWD = utils.SetPointValue(loginaccount.PASSWORD)
	loginReq.LoginIp = utils.SetPointValue(addr)
	loginReq.LoginType = utils.SetPointValue[uint32](0)
	loginReq.ClientType = utils.SetPointValue(uint32(req.ClientType))
	uid, _ := uuid.NewV4()
	loginReq.GUID = utils.SetPointValue(uid.String())
	loginReq.Version = utils.SetPointValue("1.0.0")
	uid, _ = uuid.NewV4()
	loginReq.DeviceID = utils.SetPointValue(uid.String())
	loginReq.ClientAppID = utils.SetPointValue("MTP20_GO_ACCESS")
	header := pb.MessageHead{}
	header.FunCode = utils.SetPointValue(uint32(global.LoginReq))
	uid, _ = uuid.NewV4()
	header.UUID = utils.SetPointValue(uid.String())
	loginReq.Header = &header
	if b, e := proto.Marshal(&loginReq); e == nil {
		packet := &client.MQPacket{
			FunCode:   uint32(global.LoginReq),
			SessionId: uint32(sessionID),
			Data:      &b,
		}
		go rabbitmq.Publish(global.TOPIC_REQ_USER, packet)
	}

	return
}

// getLoginAccount 分别尝试用LoginID、LoginCode和手机号码进行登录
func getLoginAccount(userName string, password string) (loginaccount *accountModel.Loginaccount, oriPwd string, err error) {
	// 密码解密（5.0报文解密）
	d, err := base64.StdEncoding.DecodeString(password)
	if err != nil {
		return
	}
	d1 := d[4 : len(d)-8] // 解密时要去头尾
	p, err := packet.Decrypt(d1, packet.AESKey, true)
	if err != nil {
		return
	}
	oriPwd = string(p)

	// 通过LoginID查询
	if loginID, _ := strconv.Atoi(userName); loginID != 0 {
		loginaccount = &accountModel.Loginaccount{
			LOGINID:  int64(loginID),
			PASSWORD: utils.EncoderSha256(fmt.Sprintf("%s%s", userName, oriPwd)), // 构建数据库存储的密码
		}
		if has, _ := global.M2A_DB.Get(loginaccount); has {
			return
		}
	}

	// 通过LoginCode查询
	loginaccount = &accountModel.Loginaccount{
		LOGINCODE: userName,
	}
	if has, _ := global.M2A_DB.Get(loginaccount); has {
		// 构建数据库存储的密码
		if loginaccount.PASSWORD == utils.EncoderSha256(fmt.Sprintf("%d%s", loginaccount.LOGINID, oriPwd)) {
			return
		}
	}

	// 通过手机号码查询，需要AES加密
	key, _ := hex.DecodeString(utils.AESSecretKey)
	if mobileEncrypted, _ := utils.AESEncrypt([]byte(userName), key); mobileEncrypted != nil {
		// 从三方表获取LoginID
		userauthinfo := &accountModel.Userauthinfo{
			AUTHID:   hex.EncodeToString(mobileEncrypted),
			AUTHTYPE: 3,
		}
		if has, _ := global.M2A_DB.Get(userauthinfo); has {
			loginaccount = &accountModel.Loginaccount{
				LOGINID:  userauthinfo.LOGINID,
				PASSWORD: utils.EncoderSha256(fmt.Sprintf("%v%s", userauthinfo.LOGINID, oriPwd)), // 构建数据库存储的密码
			}
			if has, _ := global.M2A_DB.Get(loginaccount); has {
				return
			}
		}

		// loginaccount = &accountModel.Loginaccount{
		// 	MOBILE: string(mobileEncrypted),
		// }
		// if has, _ := global.M2A_DB.Get(loginaccount); has {
		// 	// 构建数据库存储的密码
		// 	if loginaccount.PASSWORD == utils.EncoderSha256(fmt.Sprintf("%d%s", loginaccount.LOGINID, pwd)) {
		// 		return
		// 	}
		// }
	}

	err = errors.New("错误的用户名或密码")
	return
}

// newSessionID 获取
func newSessionID() int {
	mtx.RLock()
	defer mtx.RUnlock()
	curSessionID += 1

	return curSessionID
}

// buildRedisLoginInfo 生成Token，并写入Redis
func buildRedisLoginInfo(loginaccount accountModel.Loginaccount, addr string, group int) (token string, expiresAt int64, sessionID int, err error) {
	// 生成SessionID
	sessionID = newSessionID()

	// 生成本服务Token
	j := &utils.JWT{SigningKey: []byte(global.M2A_CONFIG.JWT.SigningKey)} // 唯一签名
	claims := j.CreateClaims(commonRequest.BaseClaims{
		LoginID:   int(loginaccount.LOGINID),
		Group:     group,
		SessionID: sessionID,
		UserID:    int(loginaccount.USERID),
	})
	token, err = j.CreateToken(claims)
	if err != nil {
		global.M2A_LOG.Error("生成本服token失败", zap.Error(err))
		return
	}
	expiresAt = claims.RegisteredClaims.ExpiresAt.Unix()
	loginLogin := client.LoginRedis{
		LoginID:   strconv.Itoa(int(loginaccount.LOGINID)),
		UserID:    strconv.Itoa(int(loginaccount.USERID)),
		SessionID: strconv.Itoa(sessionID),
		Token:     token,
		Group:     strconv.Itoa(group),
		Addr:      addr,
	}
	loginMap, err := loginLogin.ToMap()
	// loginMap := map[string]interface{}{
	// 	"LoginID":   strconv.Itoa(int(loginaccount.LOGINID)),
	// 	"UserID":    strconv.Itoa(int(loginaccount.USERID)),
	// 	"SessionID": strconv.Itoa(sessionID),
	// 	"Token":     token,
	// 	"Group":     strconv.Itoa(group),
	// 	"Addr":      addr,
	// }
	if err != nil {
		global.M2A_LOG.Error("生成登录信息MAP失败", zap.Error(err))
		return
	}
	if err = j.SetRedisLogin(int(loginaccount.LOGINID), group, loginMap); err != nil {
		global.M2A_LOG.Error("Token写入Redis失败", zap.Error(err))
		return
	}
	// 生成旧登录服务Token
	// if err = j.SetOriRedisToken(int(loginaccount.LOGINID), group); err != nil {
	// 	// FIXME: 这里有类事务的回滚问题
	// 	global.M2A_LOG.Error("生成旧登录服务Token失败", zap.Error(err))
	// 	return
	// }

	// 记录用户信息
	mtx.Lock()
	defer mtx.Unlock()
	if client.Clients == nil {
		client.Clients = make(map[int]*client.Client, 0)
	}
	// 这里应该按loginid和group把之前的client删除掉
	// delete(client.Clients, s)
	targetKeys := make([]int, 0)
	for key, item := range client.Clients {
		if item.LoginID == loginLogin.LoginID && item.Group == loginLogin.Group {
			targetKeys = append(targetKeys, key)
		}
	}
	for _, k := range targetKeys {
		delete(client.Clients, k)
	}
	client.Clients[claims.SessionID] = &client.Client{LoginRedis: loginLogin}

	return
}

// RestoreLoginWithToken 通过Token检验恢复登录状态失败
func RestoreLoginWithToken(loginID int, group int, token string) (err error) {
	// 先判断Clients中是否有对应的对像
	if client.Clients != nil {
		targetKeys := make([]int, 0)
		for key, item := range client.Clients {
			if item.LoginID == strconv.Itoa(loginID) && item.Group == strconv.Itoa(group) {
				targetKeys = append(targetKeys, key)
			}
		}
		if len(targetKeys) > 0 {
			return
		}
	}

	// 没有的话重新创建一个
	// 从Redis获取登录信息
	j := utils.NewJWT()
	loginMap, err := j.GetRedisLogin(loginID, group)
	if err != nil {
		global.M2A_LOG.Error("Token检验恢复登录状态失败", zap.Error(err))
		return
	}
	loginId := loginMap["loginId"]
	userId := loginMap["userId"]
	sessionId := loginMap["sessionId"]
	addr := loginMap["addr"]
	loginLogin := client.LoginRedis{
		LoginID:   loginId,
		UserID:    userId,
		SessionID: sessionId,
		Token:     token,
		Group:     strconv.Itoa(group),
		Addr:      addr,
	}

	// 记录用户信息
	mtx.Lock()
	defer mtx.Unlock()
	if client.Clients == nil {
		client.Clients = make(map[int]*client.Client, 0)
	}
	s, err := strconv.Atoi(sessionId)
	if err != nil {
		global.M2A_LOG.Error("Token检验恢复登录状态失败", zap.Error(err))
		return
	}
	client.Clients[s] = &client.Client{LoginRedis: loginLogin}

	return
}

// GetClientsByAccountID 通过资金账户获取所有的
func GetClientsByAccountID(accountID uint64) (clients []*client.Client, err error) {
	clients = make([]*client.Client, 0)

	loginIds := make([]string, 0)
	sql := fmt.Sprintf(`
	SELECT 
		to_char(t.loginid) 
	FROM loginaccount t 
	INNER JOIN taaccount a ON a.userid = t.userid
	WHERE a.accountid = %v
	`, accountID)
	if err = global.M2A_DB.SQL(sql).Find(&loginIds); err != nil {
		global.M2A_LOG.Info("获取LoginID失败", zap.Error(err))
		return
	} else {
		global.M2A_LOG.Info("获取LoginID", zap.Any("loginIds", loginIds))
	}

	// var mtx sync.RWMutex
	// mtx.Lock()
	// defer mtx.Unlock()
	if len(loginIds) > 0 && len(client.Clients) > 0 {
		for _, item := range loginIds {
			for _, client := range client.Clients {
				// c := client.Clients[i]
				if client.LoginID == item {
					clients = append(clients, client)
				}
			}
		}
	}

	return
}

func Logout(c *gin.Context) (err error) {
	// 获取请求账号信息
	s, exists := c.Get("claims")
	if !exists {
		err = errors.New("获取请求账号信息异常")
		global.M2A_LOG.Error(err.Error(), zap.Error(err))
		return
	}
	claims := s.(*commonRequest.CustomClaims)

	// 获取登录账户信息
	t, exists := client.Clients[claims.SessionID]
	if !exists {
		err = errors.New("获取登录账户信息异常")
		global.M2A_LOG.Error(err.Error(), zap.Any("SessionID", claims.SessionID), zap.Error(err))
		return
	}

	// 发送登出报文给总线
	logoutReq := pb.LogoutReq{}
	logoutReq.LoginID = utils.SetPointValue(uint64(claims.LoginID))
	logoutReq.Token = utils.SetPointValue(t.OldToken)
	logoutReq.LoginIp = utils.SetPointValue(c.ClientIP())
	port, _ := strconv.Atoi(c.Request.URL.Port())
	logoutReq.LoginPort = utils.SetPointValue(uint32(port))
	header := pb.MessageHead{}
	header.FunCode = utils.SetPointValue(uint32(global.LogoutReq))
	userID, _ := strconv.Atoi(t.UserID)
	header.UserID = utils.SetPointValue(uint32(userID))
	uid, _ := uuid.NewV4()
	header.UUID = utils.SetPointValue(uid.String())
	logoutReq.Header = &header
	if b, e := proto.Marshal(&logoutReq); e == nil {
		packet := &client.MQPacket{
			FunCode:   uint32(global.LogoutReq),
			SessionId: uint32(claims.SessionID),
			Data:      &b,
		}
		go rabbitmq.Publish(global.TOPIC_REQ_USER, packet)
	}

	return
}
