package auth2

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"regexp"
	"strings"
	"time"

	"git.rosy.net.cn/baseapi/utils"
	"git.rosy.net.cn/jx-callback/business/model"
	"git.rosy.net.cn/jx-callback/business/model/dao"
	"git.rosy.net.cn/jx-callback/globals/api"
	"github.com/dchest/captcha"
)

const (
	UserIDNone   = ""
	UserIDID     = "userid"
	UserIDID2    = "userid2"
	UserIDMobile = "mobile"
	UserIDEmail  = "email"
)

const (
	TokenHeader    = "TOKEN"
	TokenVer       = "V2"
	TokenTypeSep   = "."
	TokenUserEmpty = "NULL"
)

const (
	AuthTypeNone        = ""
	AuthTypePassword    = "localpass"
	AuthTypeEmail       = "email"
	AuthTypeMobile      = "mobile"
	AuthTypeWXApp       = "weixinapp"   //微信小程序(商家版)
	AuthTypeWXAppCaishi = "weixinappcs" //微信小程序(用户)
	AuthTypeWXMini      = "weixinmini"  //微信小程序
)

const (
	DefTokenDuration = 7 * 24 * 2 * time.Hour // 正式TOKEN，7天有效期
	TmpTokenDuration = 30 * time.Minute       // 临时TOKEN，30分钟有效期
	MinCaptchaLen    = 4
	MaxCaptchaWidth  = 400
	MaxCaptchaHeight = 400

	InternalAuthSecret = "a36ca416-c85e-4dcf-aff9-590be3d2f8a2"
)

type IUser interface {
	GetID() string  // 这个ID是不可变的，系统内部使用的唯一标识
	GetID2() string // 这个是可改的，唯一的，用户设置的用户名
	GetMobile() string
	GetEmail() string
	GetName() string
	GetAvatar() string
}

const (
	UpdateUserTypeAdd    = 1
	UpdateUserTypeDelete = 2
	UpdateUserTypeUpdate = 3
	WeCartAppID          = "wx4b5930c13f8b1170"
)

type IUserProvider interface {
	GetUser(authID, authIDType string) (user IUser)
	UpdateUserMobile(userID string, mobile string) (err error)
	UpdateUserEmail(userID string, email string) (err error)
	UpdateUserType(userID string, userTypeMask int8, updateType int) (err error)
	UpdateLastLogin(userID string, lastLoginType, fromIP string) (err error)
	// CreateUser(userID2, mobile, email, name string) (user IUser, err error)
}

type CaptchaInfo struct {
	ID        string `json:"id"`
	ImgBase64 string `json:"imgBase64"`
}

type IAuther interface {
	SendVerifyCode(authID string) (verifyCode string, err error)
	// 负责验证secret，并找到相应的用户返回（password,email,mobile类型的不负责用户查找）如果找不到用户UserID为空
	VerifySecret(authID, authSecret string) (authBindEx *AuthBindEx, err error)
	AddAuthBind(authBindEx *AuthBindEx, userName string) (err error)
	UnbindAuth(userID, authType, authTypeID, userName string) (err error)
	Logout(authInfo *AuthInfo) (err error)
	GetUserType() (userType int8)
}

var (
	authers       map[string]IAuther
	userProvider  IUserProvider
	TestMobileMap = map[string]int{
		"91112345678": 1,
	}
	TestCaptchaMap = map[string]string{
		"hGU7pB": "dd1AvY",
	}
	authTypeGuesserMap = map[string]*regexp.Regexp{
		AuthTypeEmail:  regexp.MustCompile(`^[A-Za-z0-9_\-\.]+\@[A-Za-z0-9_\-]+(\.[A-Za-z]+){1,5}$`),
		AuthTypeMobile: regexp.MustCompile(`^1[3456789]\d{9}$`),
	}

	// 永久全局的TOKEN，内部使用
	fixedTokenMap = map[string]*AuthInfo{
		"85e60d6a-f1f8-4837-9c7d-d7072b0ba1c6": &AuthInfo{
			UserBasic: UserBasic{
				UserID2: "shifengfix",
				Mobile:  "18048531223",
				Name:    "石峰固定",
			},
			AuthBindInfo: &AuthBindEx{},
			LoginTime:    utils.DefaultTimeValue,
			ExpiresAt:    0,
		},
	}
)

var (
	ErrInternalErrror         = errors.New("内部错误")
	ErrUserAlreadyExist       = errors.New("用户已经存在")
	ErrUserMobileAlreadyExist = errors.New("用户手机已经存在")
	ErrUserID2AlreadyExist    = errors.New("用户标识已经存在")
	ErrUserNotExist           = errors.New("用户不存在")
	ErrUserAuthTypeNotExist   = errors.New("用户登录类型不存在")
	ErrIllegalAuthType        = errors.New("非法的登录类型")
	ErrAuthTypeAlreadyExist   = errors.New("要登录类型已经存在")
	ErrCaptchaIsNotOk         = errors.New("图形校验码不正确")
	ErrNeedV2Token            = errors.New("需要V2版的TOKEN")
	ErrInvalidParams          = errors.New("参数不合法")
)

func init() {
	authers = make(map[string]IAuther)

	InitFixedToken()
}

func InitFixedToken() {
	for k, v := range fixedTokenMap {
		v.UserID = k
		v.Token = k
		v.TokenType = TokenTypeNormal
	}
}

func Init(userProvider2 IUserProvider) {
	userProvider = userProvider2
}

func RegisterAuther(authType string, handler IAuther) {
	authers[authType] = handler
}

func getFixedTokenName(token string) *AuthInfo {
	return fixedTokenMap[token]
}

func createAuthInfo(user IUser, authBindInfo *AuthBindEx) (authInfo *AuthInfo) {
	token, tokenType := createToken(user, authBindInfo)
	//user为空   tokenType=2
	expireDuration := DefTokenDuration
	authInfo = &AuthInfo{
		AuthBindInfo: authBindInfo,
		LoginTime:    time.Now(),
		ExpiresAt:    time.Now().Add(DefTokenDuration).Unix(),
		Token:        token,
		TokenType:    tokenType,
	}
	if user != nil {
		authInfo.UpdateByIUser(user)
	} else {
		expireDuration = TmpTokenDuration
		authInfo.ExpiresAt = time.Now().Add(expireDuration).Unix()
	}
	SetUserInfo(token, authInfo, expireDuration)
	return authInfo
}

func CreateCaptcha(width, height, captchaLen int) (captchaInfo *CaptchaInfo, err error) {
	if captchaLen < MinCaptchaLen {
		captchaLen = MinCaptchaLen
	}
	if width > MaxCaptchaWidth {
		width = MaxCaptchaWidth
	}
	if height > MaxCaptchaHeight {
		height = MaxCaptchaHeight
	}
	captchaInfo = &CaptchaInfo{
		ID: captcha.NewLen(captchaLen),
	}
	bufWriter := bytes.NewBuffer(nil)
	captcha.WriteImage(bufWriter, captchaInfo.ID, width, height)
	captchaInfo.ImgBase64 = "data:image/png;base64," + base64.StdEncoding.EncodeToString(bufWriter.Bytes())
	return captchaInfo, err
}

func SendVerifyCode(authToken, captchaID, captchaValue, authID string) (verfifyCode string, authInfo *AuthInfo, err error) {
	if authToken != "" {
		authInfo, err = GetTokenInfo(authToken)
	} else if captchaID != "" && captchaValue != "" {
		if !(TestCaptchaMap[captchaID] == captchaValue || captcha.VerifyString(captchaID, captchaValue)) {
			err = ErrCaptchaIsNotOk
		}
	} else {
		err = errors.New("发送验证必须要有认证或CAPTCHA信息")
	}
	if err == nil {
		authType := GuessAuthTypeFromAuthID(authID)
		if handler := authers[authType]; handler == nil {
			err = ErrIllegalAuthType
		} else {
			//if user, _ := dao.GetUserByID(dao.GetDB(), "mobile", authID); user != nil {
			//	return "", authInfo, fmt.Errorf("该用户已存在，请勿重复注册！")
			//}
			verfifyCode, err = handler.SendVerifyCode(authID)
		}
	}
	return verfifyCode, authInfo, err
}

// 账号密码时：authIDType可能是：UserIDID,UserIDID2,UserIDMobile,UserIDEmail，authSecret是密码的sha1
// mobile或email登录时，authIDType必须相应为UserIDMobile,UserIDEmail
// 其它登录时authIDType无用
// 邮箱时（如果允许）：authIDType是：UserIDEmail，authSecret是验证码的sha1
// 手机时（如果允许）：authIDType是：UserIDMobile，authSecret是验证码的sha1
// 公众号登录：authIDTypeD是UserIDEmpty，authSecret是code（这个函数是被微信的回调调用，不是直接被客户端调用）
// 微信登录：authIDType是UserIDEmpty，authSecret是code（这个函数是被微信的回调调用，不是直接被客户端调用）
// 小程序登录：authIDType是UserIDEmpty，authSecret是jsCode
func LoginInternal(ctx *Context, authType, authID, authIDType, authSecret string) (authInfo *AuthInfo, err error) {
	authType = strings.ToLower(authType)
	authIDType = strings.ToLower(authIDType)
	if handler := authers[authType]; handler != nil {
		var authBindEx *AuthBindEx
		var user IUser
		realAuthID := authID
		if authType == AuthTypePassword {
			if authID == "" {
				return nil, ErrInvalidParams
			}
			if user = userProvider.GetUser(authID, authIDType); user == nil {
				return nil, ErrUserNotExist
			}
			realAuthID = user.GetID()
		}
		authBindEx, err = handler.VerifySecret(realAuthID, authSecret)
		if err == nil {
			if authBindEx == nil { // mobile, email会返回nil（表示不会新建AuthBind实体）
				user = userProvider.GetUser(authID, authIDType)
				authBindEx = &AuthBindEx{
					AuthBind: model.AuthBind{
						Type:   authType,
						AuthID: authID,
						Status: model.AuthBindStatusNormal,
					},
				}
			} else {
				// 返回authBind中UserID为空表示只是认证，但本地没有记录，这种情况会返回临时TOKEN
				if authBindEx.UserHint != nil && authBindEx.UserID == "" {
					// 利用得到受信任的信息查找用户
					if authBindEx.UserHint.Mobile != "" {
						user = userProvider.GetUser(authBindEx.UserHint.Mobile, UserIDMobile)
					}
					if user == nil && authBindEx.UserHint.Email != "" {
						user = userProvider.GetUser(authBindEx.UserHint.Email, UserIDEmail)
					}
					if user != nil {
						authBindEx.UserID = user.GetID()
					}
					if user2 := userProvider.GetUser(authBindEx.UserHint.Email, UserIDMobile); user2 != nil {
						user = user2
					}
				} else if authBindEx.UserID != "" {
					user = userProvider.GetUser(authBindEx.UserID, UserIDID)
				}
			}
			authInfo = createAuthInfo(user, authBindEx)
			if user != nil && user.GetID() != "" {
				userProvider.UpdateLastLogin(user.GetID(), authType, ctx.GetRealRemoteIP())
			}
			//如果是小程序
			if authType == AuthTypeWXMini { // || authType == AuthTypeWXApp  此处AuthTypeWXApp没用上
				appID := strings.Split(authSecret, ",")[0]
				// 菜市商家								// 									//								// 京西商城
				if appID == model.JXC4BusinessAppId || appID == "wx2d6949f724b2541d" || appID == "wx18111a41fd17f24f" || appID == model.JXC4AppId { //菜市或者果园
					if authInfo.AuthBindInfo.UserID != "" {
						binds, err := dao.GetUserBindAuthInfo(dao.GetDB(), authInfo.AuthBindInfo.UserID, 0, nil, "", "", []string{"wx2bb99eb5d2c9b82c", "wx4b5930c13f8b1170"})
						if err != nil {
							return authInfo, err
						}
						if len(binds) == 0 {
							authInfo.IsExistOpenID = false
						} else {
							authInfo.IsExistOpenID = true
						}
						if WeCartAppID == appID {
							authInfo.AppID = WeCartAppID
						}
					}
				}
			}
			//微信APP端登录
			if authType == AuthTypeWXApp || authType == AuthTypeWXAppCaishi {
				appID := strings.Split(authSecret, ",")[0]
				if appID == model.JXC4ClientAppID && authInfo.AuthBindInfo.UserID != "" {
					binds, err := dao.GetUserBindAuthInfo(dao.GetDB(), authInfo.AuthBindInfo.UserID, 0, nil, "", "", []string{model.JXC4ClientAppID})
					if err != nil {
						return authInfo, err
					}
					if len(binds) == 0 {
						authInfo.IsExistOpenID = false
					} else {
						authInfo.IsExistOpenID = true
					}
					authInfo.AppID = model.JXC4ClientAppID
				}
			}

		}
	} else {
		err = ErrIllegalAuthType
	}
	return authInfo, err
}

func Login(ctx *Context, authType, authID, authIDType, authSecret string) (authInfo *AuthInfo, err error) {
	if authSecret == InternalAuthSecret {
		authSecret = ""
	}
	return LoginInternal(ctx, authType, authID, authIDType, authSecret)
}

// TikTokDecryptInfo 抖音用户信息解密
type TikTokDecryptInfo struct {
	CountryCode     string `json:"countryCode"`
	PhoneNumber     string `json:"phoneNumber"`
	PurePhoneNumber string `json:"purePhoneNumber"`
	Watermark       *struct {
		AppID     string `json:"appid"`
		Timestamp int64  `json:"timestamp"`
	} `json:"watermark"`
}

// KuaiShouInfo 抖音用户信息解密
type KuaiShouInfo struct {
	CountryCode string `json:"countryCode"`
	PhoneNumber string `json:"phoneNumber"`
}

func DecryptUserMsg(sessionKey, iv, msg, loginType string) (string, error) {
	decodeMsg, err := base64.StdEncoding.DecodeString(msg)
	if err != nil {
		return "", err
	}
	decodeIv, err := base64.StdEncoding.DecodeString(iv)
	if err != nil {
		return "", err
	}
	decodeSessionKey, err := base64.StdEncoding.DecodeString(sessionKey)
	if err != nil {
		return "", err
	}

	userInfo, err := utils.AESCBC16Decrypt(decodeSessionKey, decodeIv, decodeMsg)
	if err != nil {
		return "", err
	}

	switch loginType {
	case "tiktokmini":
		result := &TikTokDecryptInfo{}
		if err := json.Unmarshal(userInfo, result); err != nil {
			return "", err
		}
		return result.PhoneNumber, nil
	case "kuaishoumini":
		result := &KuaiShouInfo{}
		if err := json.Unmarshal(userInfo, result); err != nil {
			return "", err
		}
		return result.PhoneNumber, nil
	}
	return "", ErrIllegalAuthType
}

// 通过临时TOKEN绑定新创建的用户
func BindUser(inauthInfo *AuthInfo, user IUser) (outauthInfo *AuthInfo, err error) {
	if err = AddAuthBind(user, inauthInfo); err == nil {
		inauthInfo.AuthBindInfo.UserID = user.GetID()
		outauthInfo = createAuthInfo(user, inauthInfo.AuthBindInfo)
	}
	return outauthInfo, err
}

// 添加新绑定
func AddAuthBind(user IUser, newAuthInfo *AuthInfo) (err error) {
	if user == nil || newAuthInfo == nil {
		return ErrInternalErrror
	}
	if !newAuthInfo.IsUserEmpty() {
		return ErrAuthTypeAlreadyExist
	}
	RemoveUserInfo(newAuthInfo.Token)
	if newAuthInfo.AuthBindInfo.Type == AuthTypeMobile {
		err = userProvider.UpdateUserMobile(user.GetID(), newAuthInfo.AuthBindInfo.AuthID)
	} else if newAuthInfo.AuthBindInfo.Type == AuthTypeEmail {
		err = userProvider.UpdateUserEmail(user.GetID(), newAuthInfo.AuthBindInfo.AuthID)
	} else {
		if handler := authers[newAuthInfo.AuthBindInfo.Type]; handler != nil {
			newAuthInfo.AuthBindInfo.UserID = user.GetID()
			handler.UnbindAuth(user.GetID(), newAuthInfo.GetAuthType(), newAuthInfo.GetAuthTypeID(), user.GetName())
			err = handler.AddAuthBind(newAuthInfo.AuthBindInfo, user.GetName())
		} else {
			err = ErrIllegalAuthType
		}
	}
	return err
}

func AddAuthBindWithMobile(authInfo *AuthInfo, mobile string) (err error) {
	if handler := authers[authInfo.AuthBindInfo.Type]; handler != nil {
		user, err := dao.GetUserByID(dao.GetDB(), "mobile", mobile)
		if err != nil {
			return errors.New("此接口有执行顺序，请先调用注册登录接口：【第一步：Login，第二步：TiktokDecrypt】")
		}
		authInfo.AuthBindInfo.UserID = user.UserID
		//handler.UnbindAuth(user.GetID(), newAuthInfo.GetAuthType(), newAuthInfo.GetAuthTypeID(), user.GetName())
		err = handler.AddAuthBind(authInfo.AuthBindInfo, user.GetName())
	} else {
		err = ErrIllegalAuthType
	}
	return err
}

func UnbindAuth(userID, authType, authTypeID, userName string) (err error) {
	if handler := authers[authType]; handler != nil {
		err = handler.UnbindAuth(userID, authType, authTypeID, userName)
	} else {
		err = ErrIllegalAuthType
	}
	return err
}

func Logout(authInfo *AuthInfo) (err error) {
	if handler := authers[authInfo.AuthBindInfo.Type]; handler != nil {
		err = handler.Logout(authInfo)
	}
	RemoveUserInfo(authInfo.Token)
	return err
}

// token缓存相关
/////////////

func RemoveUserInfo(token string) {
	api.Cacher.Del(token)
}

func GetTokenInfo(token string) (authInfo *AuthInfo, err error) {
	if authInfo = getFixedTokenName(token); authInfo != nil {
		return authInfo, nil
	}
	if err = api.Cacher.GetAs(token, &authInfo); err == nil {
		return authInfo, nil
	}
	return nil, model.ErrTokenIsInvalid
}

func SetUserInfo(token string, authInfo *AuthInfo, duration time.Duration) {
	api.Cacher.Set(token, authInfo, DefTokenDuration)
	api.Cacher.Set(authInfo.Mobile, token, DefTokenDuration)
}

func ClearUserToken(userID string) {
	if keys, err := api.Cacher.Keys(strings.Join([]string{
		TokenHeader,
		TokenVer,
		userID,
		"*",
	}, TokenTypeSep)); err == nil {
		for _, key := range keys {
			api.Cacher.Del(key)
		}
	}
}

func createToken(user IUser, authBindInfo *AuthBindEx) (token string, tokenType int) {
	userID := TokenUserEmpty
	userName := authBindInfo.AuthID
	tokenType = TokenTypeOnlyAuth
	if user != nil {
		userID = user.GetID()
		userName = "[" + user.GetID2() + "]"
		tokenType = TokenTypeNormal
	}
	return strings.Join([]string{
		TokenHeader,
		TokenVer,
		userID,
		time.Now().Format("20060102-150405"),
		authBindInfo.Type,
		utils.GetUUID(),
		userName,
	}, TokenTypeSep), tokenType
}

func GetTokenType(token string) (tokenType int) {
	if getFixedTokenName(token) != nil {
		return TokenTypeNormal
	}
	tokenType = TokenTypeNone
	if token != "" {
		tokenPartList := strings.Split(token, TokenTypeSep)
		if (len(tokenPartList) == 1) || (len(tokenPartList) >= 6 && tokenPartList[2] != TokenUserEmpty) {
			tokenType = TokenTypeNormal
		} else {
			tokenType = TokenTypeOnlyAuth
		}
	}
	return tokenType
}

func IsV2Token(token string) bool {
	if getFixedTokenName(token) != nil {
		return true
	}
	tokenPartList := strings.Split(token, TokenTypeSep)
	return len(tokenPartList) > 1 && tokenPartList[1] == TokenVer
}

func GuessAuthTypeFromAuthID(authID string) (authType string) {
	if TestMobileMap[authID] == 1 {
		return AuthTypeMobile
	}
	for k, v := range authTypeGuesserMap {
		if v.FindStringSubmatch(authID) != nil {
			return k
		}
	}
	return AuthTypeNone
}

func DisableUser(userID, operatorUserName string) (err error) {
	if _, err = dao.UpdateEntityLogically(dao.GetDB(), &model.AuthBind{}, map[string]interface{}{
		"Status": model.AuthBindStatusDisabled,
	}, operatorUserName, map[string]interface{}{
		"UserID": userID,
	}); err == nil {
		ClearUserToken(userID)
	}
	return err
}

func GetUserBindAuthInfo(userID string) (authList []*model.AuthBind, err error) {
	return dao.GetUserBindAuthInfo(dao.GetDB(), userID, model.AuthBindTypeAuth, nil, "", "", nil)
}

func DeletedTokenInfoWithoutParam(authInfo *AuthInfo) (err error) {
	userID := authInfo.UserID
	tokens, err := api.Cacher.Keys("TOKEN.V2." + userID + "*")
	for _, v := range tokens {
		if authInfo.Token != v {
			RemoveUserInfo(v)
		}
	}
	return err
}
