package controller

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/golang-jwt/jwt"
	"github.com/mojocn/base64Captcha"
	"github.com/satori/go.uuid"
	"iot_manager_service/app/middleware"
	"iot_manager_service/app/system/dao"
	"iot_manager_service/app/system/service"
	"iot_manager_service/util/cache"
	"iot_manager_service/util/common"
	"strconv"
	"time"

	"iot_manager_service/app/system/model"
	"iot_manager_service/config"
	"net/http"
	"strings"
)

var Auth = new(auth)

type auth struct{}

var driver = &base64Captcha.DriverString{
	Height:          48,
	Width:           130,
	NoiseCount:      100,
	ShowLineOptions: 2,
	Length:          5,
	BgColor:         nil,
}

func (c *auth) Token(ctx *gin.Context) {
	tenantId := ctx.Query("tenantId")
	userName := ctx.Query("username")
	password := ctx.Query("password")
	grantType := ctx.Query("grant_type")
	refreshToken := ctx.Query("refresh_token")

	// 校验连续登录失败次数
	checkLock()

	tId, _ := strconv.Atoi(tenantId)

	userType := ctx.GetHeader(model.UserTypeHeaderKey)
	token := model.Token{
		TenantId:     tId,
		UserName:     userName,
		Password:     password,
		GrantType:    grantType,
		RefreshToken: refreshToken,
		UserType:     userType,
	}
	var userInfo *model.UserInfo
	var err *common.Errors
	if grantType == "captcha" {
		userInfo, err = captchaGrant(token, ctx)
		if err != nil {
			ctx.JSON(http.StatusOK, err)
			return
		}
	} else if grantType == "refresh_token" {
		userInfo, err = refreshGrant(token)
		if err != nil {
			ctx.JSON(http.StatusOK, err)
			return
		}
	}

	if userInfo == nil || userInfo.User == nil {
		ctx.JSON(http.StatusOK, common.NormalResponse(http.StatusBadRequest, model.UserNotFound, nil))
		return
	}
	if len(userInfo.Roles) == 0 {
		ctx.JSON(http.StatusOK, common.NormalResponse(http.StatusBadRequest, model.UserHasNoRole, nil))
		return
	}

	// access token过期时间2小时
	random := common.RandomString(8)
	jwtToken, e := middleware.GetAccessToken(userInfo.ID, userInfo.RoleId, userInfo.TenantId, userInfo.Account, random)
	if e != nil {
		ctx.JSON(http.StatusOK, common.NormalResponse(http.StatusBadRequest, e.Error(), nil))
		return
	}

	// redis记录缓存2小时
	cache.Redis.Set(getAccessTokenKey(userInfo.TenantId, userInfo.ID, random), jwtToken, 2*time.Hour)
	ctx.JSON(http.StatusOK, model.RspToken{
		TenantId:     userInfo.TenantId,
		UserId:       userInfo.ID,
		RoleId:       userInfo.RoleId,
		OauthId:      userInfo.OauthId,
		Account:      userInfo.Account,
		UserName:     userInfo.Name,
		NickName:     userInfo.RealName,
		RoleName:     userInfo.Roles[0],
		Avatar:       userInfo.Avatar,
		AccessToken:  jwtToken,
		RefreshToken: getRefreshToken(*userInfo),
		TokenType:    model.BEARER,
		ExpiresIn:    7200,
		License:      "",
	})
}

func (c *auth) Logout(ctx *gin.Context) {
	value, _ := ctx.Get(middleware.Authorization)
	claims := value.(*middleware.Claims)

	_ = cache.Redis.Del(getAccessTokenKey(claims.TenantId, claims.UserId, claims.Random)).Err()
	service.OperationHisService.Save(claims.UserId, claims.TenantId, common.OperationLogout, common.ModuleTypeDefault,
		common.DeviceTypeDefault, "", common.OperationSuccess)
	ctx.JSON(http.StatusOK, common.SuccessResponse("", nil))
}

func (c *auth) Captcha(ctx *gin.Context) {
	id := uuid.NewV1().String()
	code := common.RandomString2(5)
	gotItem, _ := driver.DrawCaptcha(code)
	image := gotItem.EncodeB64string()
	rsp := model.RspCaptcha{
		Key:   id,
		Image: image,
	}
	cache.Redis.Set(getCaptchaKey(id), code, 5*time.Minute)
	ctx.JSON(http.StatusOK, rsp)
}

func (c *auth) Clear(ctx *gin.Context) {
	//todo clear redis
	ctx.JSON(http.StatusOK, common.SuccessResponse(common.Success, nil))
}

func (c *auth) Login(ctx *gin.Context) {
	passKey := ctx.Query("passKey")
	tenant, err := service.TenantService.GetTenantByPasskey(passKey)
	if err != nil {
		ctx.JSON(http.StatusOK, common.SuccessResponse(model.TenantNotFound, nil))
		return
	}
	rsp := model.RspLogin{
		ID:            tenant.TenantId,
		Name:          tenant.LoginDisplayName,
		BackgroundUrl: tenant.BackgroundUrl,
		SysLogoUrl:    tenant.SysLogoUrl,
	}
	ctx.JSON(http.StatusOK, common.SuccessResponse(common.Success, rsp))
}

//checkLock 校验用户登录失败次数
func checkLock() {

}

func getRefreshToken(info model.UserInfo) string {
	claims := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{
		model.Iss:       middleware.Issuer,
		model.Aud:       middleware.Audience,
		model.ClientId:  model.Saber,
		model.TokenType: model.RefreshToken,
		model.UserId:    info.ID,
		model.Exp:       time.Now().Add(7 * 24 * time.Hour).Unix(),
		model.Nbf:       time.Now().Unix(),
	})
	token, _ := claims.SignedString([]byte(config.Instance().Server.TokenSign))
	return token
}

func parseRefreshToken(refreshToken string) *jwt.MapClaims {
	token, err := jwt.ParseWithClaims(refreshToken, &jwt.MapClaims{}, middleware.EmptyKeyFunc)
	if err != nil {
		return nil
	}
	return token.Claims.(*jwt.MapClaims)
}

func captchaGrant(token model.Token, ctx *gin.Context) (*model.UserInfo, *common.Errors) {
	info := &model.UserInfo{}
	key := ctx.GetHeader(model.CaptchaHeaderKey)
	code := ctx.GetHeader(model.CaptchaHeaderCode)
	// 获取验证码
	result, err := cache.Redis.Get(getCaptchaKey(key)).Result()
	if err != nil {
		return nil, common.NormalResponse(http.StatusBadRequest, model.CaptchaNotCorrect, nil)
	}
	redisCode := result
	// 判断验证码
	if config.Instance().Server.CodeEnabled && (key == "" || code == "" || !strings.EqualFold(redisCode, code)) {
		return nil, common.NormalResponse(http.StatusBadRequest, model.CaptchaNotCorrect, nil)
	}

	if token.UserName != "" && token.Password != "" {
		// 获取租户信息
		tenant, err := service.TenantService.GetOne(token.TenantId)
		if err != nil || tenant == nil {
			return nil, common.NormalResponse(http.StatusOK, model.UserHasNoTenant, nil)
		}
		if judgeTenant(tenant) {
			return nil, common.NormalResponse(http.StatusOK, model.UserHasNoTenantPermission, nil)
		}
		// 获取用户类型
		// 根据不同用户类型调用对应的接口返回数据,用户可自行拓展
		info.User = service.UserService.GetOne(token.TenantId, token.UserName, token.Password)
		info.TenantId = token.TenantId
		info.Roles = []string{"admin"}
		if info.User != nil {
			service.OperationHisService.Save(info.User.ID, info.TenantId, common.OperationLogin,
				common.ModuleTypeDefault, common.DeviceTypeDefault, "", common.OperationSuccess)
		}
	}
	return info, nil
}

func refreshGrant(token model.Token) (*model.UserInfo, *common.Errors) {
	info := &model.UserInfo{}
	jwtToken := parseRefreshToken(token.RefreshToken)
	if jwtToken == nil || len(*jwtToken) != 7 {
		return nil, common.NormalResponse(http.StatusOK, model.UserHasNoTenantPermission, nil)
	}

	// 获取租户信息
	tenant, _ := service.TenantService.GetOne(token.TenantId)
	if tenant == nil {
		return nil, common.NormalResponse(http.StatusOK, model.UserHasNoTenant, nil)
	}
	if judgeTenant(tenant) {
		return nil, common.NormalResponse(http.StatusOK, model.UserHasNoTenantPermission, nil)
	}

	info.User = service.UserService.GetOneByTenantId(token.TenantId)
	info.Roles = []string{"admin"}
	return info, nil
}

func judgeTenant(tenant *dao.Tenant) bool {
	if tenant.TenantId == model.AdminTenantId {
		return false
	}
	if tenant.ExpireTime.IsZero() || !tenant.ExpireTime.Before(time.Now()) {
		return false
	}
	return true
}

func getAccessTokenKey(tenantId int, uId int64, random string) string {
	return fmt.Sprintf("access_token_%d_%d_%s", tenantId, uId, random)
}

func getCaptchaKey(uId string) string {
	return fmt.Sprintf("auth:captcha:%s", uId)
}