package svc

import (
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"net/rpc"
	"time"

	"github.com/globalsign/mgo"
	"github.com/globalsign/mgo/bson"
	"mongo.games.com/game/common"
	"mongo.games.com/game/dbproxy/mongo"
	"mongo.games.com/game/model"
	"mongo.games.com/goserver/core/logger"
)

var (
	AccountDBName            = "user"
	AccountCollName          = "user_account"
	AccountDelBackupCollName = "user_account_del_backup"
	ErrAccDBNotOpen          = model.NewDBError(AccountDBName, AccountCollName, model.NOT_OPEN)
)

func AccountCollection(plt string) *mongo.Collection {
	s := mongo.MgoSessionMgrSington.GetPltMgoSession(plt, AccountDBName)
	if s != nil {
		c, first := s.DB().C(AccountCollName)
		if first {
			c.EnsureIndex(mgo.Index{Key: []string{"username", "tagkey"}, Background: true, Sparse: true})
			c.EnsureIndex(mgo.Index{Key: []string{"tel", "tagkey"}, Background: true, Sparse: true})
			c.EnsureIndex(mgo.Index{Key: []string{"snid"}, Background: true, Sparse: true})
			c.EnsureIndex(mgo.Index{Key: []string{"registerts"}, Background: true, Sparse: true})
		}
		return c
	}
	return nil
}

func AccountDelBackupCollection(plt string) *mongo.Collection {
	s := mongo.MgoSessionMgrSington.GetPltMgoSession(plt, AccountDBName)
	if s != nil {
		c, _ := s.DB().C(AccountDelBackupCollName)
		return c
	}
	return nil
}

type AccountSvc struct {
}

func (svc *AccountSvc) AccountIsExist(args *model.AccIsExistArg, ret *model.AccRet) error {
	caccounts := AccountCollection(args.Platform)
	if caccounts == nil {
		ret.Tag = 0
		return ErrAccDBNotOpen
	}

	acc := &model.Account{}

	switch args.LoginType {
	case common.LoginTypeGuest:
		//游客登录
		err := caccounts.Find(bson.M{"username": args.Username, "tagkey": args.TagKey}).One(acc)
		if err != nil {
			if errors.Is(err, mgo.ErrNotFound) {
				ret.Tag = common.LoginNew
				return nil
			}
			return err
		}
		raw := fmt.Sprintf("%v%v%v", acc.PassWord, common.GetAppId(), args.Ts)
		h := md5.New()
		io.WriteString(h, raw)
		pwd := hex.EncodeToString(h.Sum(nil))
		if pwd != args.Password {
			logger.Logger.Warnf("Password is error:%v raw:%v get:%v expect:%v", acc.AccountId, raw, args.Password, pwd)
			ret.Tag = common.LoginPasswordError
			return nil
		}

	case common.LoginTypeAccount:
		//帐号登录
		err := caccounts.Find(bson.M{"username": args.Username, "tagkey": args.TagKey}).One(acc)
		if err != nil {
			logger.Logger.Info("Tel or Password is error:", err)
			ret.Tag = common.LoginPasswordError
			return nil
		}
		rawt := fmt.Sprintf("%v%v%v", acc.TelPassWord, common.GetAppId(), args.Ts)
		ht := md5.New()
		io.WriteString(ht, rawt)
		pwdt := hex.EncodeToString(ht.Sum(nil))
		if pwdt != args.Password {
			logger.Logger.Warnf("Password is error:%v raw:%v get:%v expect:%v", acc.AccountId, rawt, args.Password, pwdt)
			ret.Tag = common.LoginPasswordError
			return nil
		}

	case common.LoginTypeTelCode:
		err := caccounts.Find(bson.M{"tel": args.Tel, "tagkey": args.TagKey}).One(acc)
		if err != nil {
			if errors.Is(err, mgo.ErrNotFound) {
				ret.Tag = common.LoginNew
				return nil
			}
			return err
		}

		if args.CodeValid {
			// 刷新密码和有效期
			raw := fmt.Sprintf("%v%v", bson.NewObjectId().Hex(), common.GetAppId())
			h := md5.New()
			io.WriteString(h, raw)
			pwd := hex.EncodeToString(h.Sum(nil))

			acc.TelPassWord = pwd
			acc.TelPassWordExpire = time.Now().AddDate(0, 0, common.TelLoginValidDays).Unix()

			err = caccounts.UpdateId(acc.AccountId, acc)
			if err != nil {
				return err
			}
		} else {
			// 验证密码和有效期
			rawt := fmt.Sprintf("%v%v%v", acc.TelPassWord, common.GetAppId(), args.Ts)
			ht := md5.New()
			io.WriteString(ht, rawt)
			pwdt := hex.EncodeToString(ht.Sum(nil))
			if pwdt != args.Password {
				logger.Logger.Warnf("Password is error:%v raw:%v get:%v expect:%v", acc.AccountId, rawt, args.Password, pwdt)
				ret.Tag = common.LoginPasswordError
				return nil
			}
			// 判断是否过期
			if time.Now().Unix()-acc.TelPassWordExpire > 0 { // 过期
				logger.Logger.Tracef("TelPassWordExpire AccountId:%v", acc.AccountId)
				ret.Tag = common.LoginTelCodeExpire
				return nil
			}
		}

	case common.LoginTypeTelegram:
		// telegram
		err := caccounts.Find(bson.M{"username": args.Username, "tagkey": args.TagKey}).One(acc)
		if err != nil {
			if errors.Is(err, mgo.ErrNotFound) {
				ret.Tag = common.LoginNew
				return nil
			}
			return err
		}
		if args.VerifyToken {
			if acc.PassWord != args.Password {
				logger.Logger.Warnf("Password is error:%v get:%v ", acc.AccountId, args.Password)
				ret.Tag = common.LoginPasswordError
				return nil
			}
		}

	case common.LoginTypeGoogle:
		err := caccounts.Find(bson.M{"username": args.Username, "tagkey": args.TagKey}).One(acc)
		if err != nil {
			if errors.Is(err, mgo.ErrNotFound) {
				ret.Tag = common.LoginNew
				return nil
			}
			return err
		}
		// 验证

	case common.RegisterTypeTel:
		//帐号注册,验证手机号
		err := caccounts.Find(bson.M{"tel": args.Tel, "tagkey": args.TagKey}).One(acc)
		if err != nil {

		} else {
			//手机号已经注册
			ret.Tag = common.LoginTelExist
			return nil
		}

		//帐号注册,验证游客帐号
		err = caccounts.Find(bson.M{"username": args.Username, "tagkey": args.TagKey}).One(acc)
		if err != nil {
			//设备号没有注册
			//logger.Logger.Info("Username is not Exist:", err)
			ret.Tag = common.RegisterNotExist
			return nil
		} else {
			//设备号已经注册
			//logger.Logger.Info("Username is Exist:", err)
			ret.Tag = common.RegisterExist
			return nil
		}

	case common.RegisterTypeName:
		//账号注册 不需要验证手机号
		//帐号注册,验证游客帐号
		err := caccounts.Find(bson.M{"username": args.Username, "tagkey": args.TagKey}).One(acc)
		if err != nil {
			//设备号没有注册
			//logger.Logger.Info("Username is not Exist:", err)
			ret.Tag = common.RegisterNotExist
			return nil
		} else {
			//设备号已经注册
			//logger.Logger.Info("Username is Exist:", err)
			ret.Tag = common.RegisterExist
			return nil
		}

	default:
		ret.Tag = common.LoginTypeNoExist
		return nil
	}

	// freeze
	if acc.State > time.Now().Unix() {
		ret.Tag = common.LoginFreeze
		ret.Acc = acc
		return nil
	}
	// ok
	ret.Tag = common.LoginOk
	ret.Acc = acc
	return nil
}

func (svc *AccountSvc) AccountTelIsRegiste(args *model.AccIsExistArg, exist *bool) error {
	caccounts := AccountCollection(args.Platform)
	if caccounts != nil {
		err := caccounts.Find(bson.M{"tel": args.Tel, "tagkey": args.TagKey}).One(nil)
		if err == nil {
			*exist = true
		}
	}

	*exist = false
	return nil
}

func (svc *AccountSvc) InsertAccount(args *model.Account, ret *model.AccRet) error {
	caccounts := AccountCollection(args.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}

	id, err := GetOnePlayerIdFromBucket()
	if err == nil {
		args.SnId = id
	}

	err = caccounts.Insert(args)
	if err != nil {
		logger.Logger.Info("InsertAccount error:", err)
		return nil
	}

	ret.Acc = args
	ret.Tag = common.InsertAccountOk
	return nil
}

func (svc *AccountSvc) InsertTelAccount(args *model.Account, ret *model.AccRet) error {
	caccounts := AccountCollection(args.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}

	id, err := GetOnePlayerIdFromBucket()
	if err == nil {
		args.SnId = id
	}

	err = caccounts.Insert(args)
	if err != nil {
		logger.Logger.Info("InsertAccount error:", err)
		return nil
	}

	ret.Acc = args
	ret.Tag = common.InsertAccountOk
	return nil
}

func (svc *AccountSvc) UpgradeAccount(args *model.Account, ret *model.AccRet) error {
	caccounts := AccountCollection(args.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}

	acc := &model.Account{}
	err := caccounts.Find(bson.M{"tel": args.Tel, "tagkey": args.TagKey}).One(acc)
	if err == nil && acc.AccountId != args.AccountId {
		return fmt.Errorf("tel used account %v", acc.AccountId.Hex())
	}
	if acc.Tel != "" {
		return errors.New("Tel is Bind")
	}
	err = caccounts.Update(bson.M{"_id": args.AccountId}, bson.D{{"$set", bson.D{{"tel", args.Tel}, {"telpassword", args.PassWord}}}})
	if err != nil {
		logger.Logger.Info("UpgradeAccount error ", err)
	}

	return err
}

func (svc *AccountSvc) BindTelAccount(args *model.BindTelAccountArgs, ret *model.BindTelAccountRet) error {
	caccounts := AccountCollection(args.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}

	ret.Code = 1

	acc := &model.Account{}
	err := caccounts.Find(bson.M{"tel": args.Tel, "tagkey": args.TagKey}).One(acc)
	if err == nil {
		return fmt.Errorf("tel used account %v", acc.AccountId.Hex())
	}
	if !errors.Is(err, mgo.ErrNotFound) {
		return err
	}
	if acc.Tel != "" {
		return errors.New("Tel is Bind")
	}
	err = caccounts.Update(bson.M{"_id": bson.ObjectIdHex(args.AccId)}, bson.D{{"$set", bson.D{{"tel", args.Tel}}}})
	if err != nil {
		logger.Logger.Info("BindTelAccount error ", err)
		return err
	}

	ret.Acc = acc
	ret.Code = 0

	return err
}

func (svc *AccountSvc) LogoutAccount(acc *model.Account, ret *model.AccRet) error {
	caccounts := AccountCollection(acc.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}
	return caccounts.Update(bson.M{"_id": acc.AccountId}, bson.D{{"$set", bson.D{{"logintimes", acc.LoginTimes}, {"lastlogouttime", acc.LastLogoutTime}}}})
}

func (svc *AccountSvc) FreezeAccount(args *model.AccFreezeArg, ret *model.AccRet) error {
	caccounts := AccountCollection(args.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}
	endTime := time.Now().Unix() + int64(time.Minute.Seconds())*int64(args.FreezeTime)
	return caccounts.Update(bson.M{"_id": bson.ObjectIdHex(args.AccId)}, bson.D{{"$set", bson.D{{"state", endTime}}}})
}

func (svc *AccountSvc) UnfreezeAccount(args *model.AccFreezeArg, ret *model.AccRet) error {
	caccounts := AccountCollection(args.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}
	return caccounts.Update(bson.M{"_id": bson.ObjectIdHex(args.AccId)}, bson.D{{"$set", bson.D{{"state", 0}}}})
}

func (svc *AccountSvc) getAccount(plt, accId string) (*model.Account, error) {
	caccounts := AccountCollection(plt)
	if caccounts == nil {
		return nil, ErrAccDBNotOpen
	}

	var acc model.Account
	err := caccounts.Find(bson.M{"_id": bson.ObjectIdHex(accId)}).One(&acc)
	if err != nil {
		return nil, err
	}

	return &acc, err
}

func (svc *AccountSvc) getAccountBySnId(plt string, snid int32) (*model.Account, error) {
	caccounts := AccountCollection(plt)
	if caccounts == nil {
		return nil, ErrAccDBNotOpen
	}

	var acc model.Account
	err := caccounts.Find(bson.M{"snid": snid}).One(&acc)
	if err != nil {
		return nil, err
	}

	return &acc, err
}

func (svc *AccountSvc) GetAccountBySnId(args *model.UserSnidArg, ret *model.AccRet) error {
	var err error
	ret.Acc, err = svc.getAccountBySnId(args.Platform, args.Snid)
	return err
}

func (svc *AccountSvc) getAccountByUserName(plt, username string) (*model.Account, error) {
	caccounts := AccountCollection(plt)
	if caccounts == nil {
		return nil, ErrAccDBNotOpen
	}

	var acc model.Account
	err := caccounts.Find(bson.M{"username": username}).One(&acc)
	if err != nil {
		return nil, err
	}

	return &acc, err
}
func (svc *AccountSvc) GetAccountByUserName(args *model.UserNameArg, ret *model.AccRet) error {
	var err error
	ret.Acc, err = svc.getAccountByUserName(args.Platform, args.UserName)
	return err
}

func (svc *AccountSvc) GetAccount(args *model.AccIdArg, ret *model.AccRet) error {
	var err error
	ret.Acc, err = svc.getAccount(args.Platform, args.AccId)
	return err
}

// 删除账号
func (svc *AccountSvc) RemoveAccount(args *model.AccIdArg, ret *model.AccRet) error {
	caccounts := AccountCollection(args.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}

	return caccounts.RemoveId(bson.ObjectIdHex(args.AccId))
}

// pwd暂时用明文.需要使用MD5加密。参照task_login.go
func (svc *AccountSvc) EditAccountPwd(acc *model.Account, ret *model.AccRet) error {
	caccounts := AccountCollection(acc.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}
	hashsum := model.GenAccountPwd(acc.PassWord)
	return caccounts.Update(bson.M{"_id": acc.AccountId}, bson.D{{"$set", bson.D{{"backpassword", acc.TelPassWord}, {"telpassword", hashsum}}}})
}

func (svc *AccountSvc) ResetBackAccountPwd(args *model.AccIdArg, ret *model.AccRet) error {
	caccounts := AccountCollection(args.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}

	var acc model.Account
	err := caccounts.Find(bson.M{"_id": bson.ObjectIdHex(args.AccId)}).One(&acc)
	if err != nil {
		return err
	}
	if len(acc.BackPassWord) > 0 {
		return caccounts.Update(bson.M{"_id": acc.AccountId}, bson.D{{"$set", bson.D{{"telpassword", acc.BackPassWord}}}})
	}
	return nil
}

/*
 * 修改帐号密码
 */
func (svc *AccountSvc) UpdatePlayerPassword(acc *model.Account, ret *model.AccRet) error {
	caccounts := AccountCollection(acc.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}

	return caccounts.Update(bson.M{"_id": acc.AccountId, "telpassword": acc.BackPassWord}, bson.D{{"$set", bson.D{{"telpassword", acc.PassWord}}}})
}

/*
 * 修改Token帐号密码
 */
func (svc *AccountSvc) UpdatePlayerTokenPassword(acc *model.Account, ret *model.AccRet) error {
	caccounts := AccountCollection(acc.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}

	return caccounts.Update(bson.M{"_id": acc.AccountId}, bson.D{{"$set", bson.D{{"password", acc.PassWord}}}})
}

/*
 * 找回密码
 */
func (svc *AccountSvc) GetBackPlayerPassword(acc *model.Account, ret *model.AccRet) error {
	caccounts := AccountCollection(acc.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}

	return caccounts.Update(bson.M{"tel": acc.Tel, "tagkey": acc.TagKey}, bson.D{{"$set", bson.D{{"telpassword", acc.PassWord}}}})
}
func (svc *AccountSvc) UpdateAccountPlatformInfo(acc *model.Account, ret *model.AccRet) error {
	caccounts := AccountCollection(acc.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}

	return caccounts.Update(bson.M{"_id": acc.AccountId},
		bson.D{{"$set", bson.M{"platform": acc.Platform,
			"channel": acc.Channel, "promoter": acc.Promoter, "inviterid": acc.InviterId, "packegetag": acc.PackegeTag, "promotertree": acc.PromoterTree}}})
}

func (svc *AccountSvc) GetRobotAccounts(limit int, accs *[]model.Account) error {
	caccounts := AccountCollection(mongo.G_P)
	if caccounts == nil {
		return nil
	}
	*accs = make([]model.Account, 0, limit)
	err := caccounts.Find(bson.M{"platform": common.Platform_Rob}).Limit(limit).All(&accs)
	if err != nil {
		logger.Logger.Info("GetAllRobotAccounts  error:", err)
		return nil
	}
	return nil
}

func (svc *AccountSvc) SaveToDelBackupAccount(acc *model.Account, ret *model.AccRet) error {
	cDelBackup := AccountDelBackupCollection(acc.Platform)
	if cDelBackup == nil {
		return ErrAccDBNotOpen
	}

	_, err := cDelBackup.Upsert(bson.M{"_id": acc.AccountId}, acc)
	if err != nil {
		logger.Logger.Info("InsertDelBackupAccount error:", err)
	}

	return err
}

func (svc *AccountSvc) UpdateAccount(acc *model.Account, ret *model.AccRet) error {
	caccounts := AccountCollection(acc.Platform)
	if caccounts == nil {
		return ErrAccDBNotOpen
	}

	err := caccounts.UpdateId(acc.AccountId, acc)
	if err != nil {
		logger.Logger.Info("UpdateAccount error:", err)
	}

	return err
}

var _AccountSvc = &AccountSvc{}

func init() {
	rpc.Register(_AccountSvc)
}
