package system

import (
	"sync"
	"time"

	"kra/internal/biz/system"
	"kra/pkg/jwt"
	"kra/pkg/response"
	"kra/pkg/utils"

	"github.com/gin-gonic/gin"
	"github.com/mojocn/base64Captcha"
	"github.com/redis/go-redis/v9"
)

type BaseApi struct{}

// LoginRequest 登录请求
type LoginRequest struct {
	Username  string `json:"username" binding:"required"`
	Password  string `json:"password" binding:"required"`
	Captcha   string `json:"captcha"`
	CaptchaId string `json:"captchaId"`
}

// LoginResponse 登录响应
type LoginResponse struct {
	User      interface{} `json:"user"`
	Token     string      `json:"token"`
	ExpiresAt int64       `json:"expiresAt"`
}

// JWT实例（需要在初始化时设置）
var jwtInstance *jwt.JWT

// 验证码存储
var captchaStore base64Captcha.Store = base64Captcha.DefaultMemStore

// 验证码配置
var (
	openCaptcha        int  = 0   // 防爆次数，0表示关闭
	openCaptchaTimeOut int  = 300 // 缓存超时时间(秒)
	useMultipoint      bool       // 是否开启多点登录
)

// 黑名单缓存
var blackCache = NewLoginBlackCache()

// LoginBlackCache 登录黑名单缓存
type LoginBlackCache struct {
	mu    sync.RWMutex
	items map[string]*loginCacheItem
}

type loginCacheItem struct {
	value      int
	expiration time.Time
}

// NewLoginBlackCache 创建登录黑名单缓存
func NewLoginBlackCache() *LoginBlackCache {
	bc := &LoginBlackCache{
		items: make(map[string]*loginCacheItem),
	}
	go bc.cleanup()
	return bc
}

func (c *LoginBlackCache) Set(key string, value int, duration time.Duration) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.items[key] = &loginCacheItem{
		value:      value,
		expiration: time.Now().Add(duration),
	}
}

func (c *LoginBlackCache) Get(key string) (int, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	item, ok := c.items[key]
	if !ok || time.Now().After(item.expiration) {
		return 0, false
	}
	return item.value, true
}

func (c *LoginBlackCache) Increment(key string, delta int) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if item, ok := c.items[key]; ok {
		item.value += delta
	}
}

func (c *LoginBlackCache) cleanup() {
	ticker := time.NewTicker(time.Minute)
	for range ticker.C {
		c.mu.Lock()
		now := time.Now()
		for k, v := range c.items {
			if now.After(v.expiration) {
				delete(c.items, k)
			}
		}
		c.mu.Unlock()
	}
}

// SetJWTInstance 设置JWT实例
func SetJWTInstance(j *jwt.JWT) {
	jwtInstance = j
}

// SetCaptchaStore 设置验证码存储
func SetCaptchaStore(store base64Captcha.Store) {
	captchaStore = store
}

// SetCaptchaConfig 设置验证码配置
func SetCaptchaConfig(open, timeout int) {
	openCaptcha = open
	openCaptchaTimeOut = timeout
}

// SetUseMultipoint 设置是否开启多点登录
func SetUseMultipoint(use bool) {
	useMultipoint = use
}

// Login
// @Tags     Base
// @Summary  用户登录
// @Produce  application/json
// @Param    data  body      LoginRequest                                             true  "用户名, 密码, 验证码"
// @Success  200   {object}  response.Response{data=LoginResponse,msg=string}  "返回包括用户信息,token,过期时间"
// @Router   /base/login [post]
func (b *BaseApi) Login(c *gin.Context) {
	var req LoginRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		response.FailWithMessage(err.Error(), c)
		return
	}

	key := c.ClientIP()

	// 判断验证码是否开启
	v, ok := blackCache.Get(key)
	if !ok {
		blackCache.Set(key, 1, time.Second*time.Duration(openCaptchaTimeOut))
	}

	// 检查是否需要验证码
	var needCaptcha bool = openCaptcha == 0 || openCaptcha < v
	if needCaptcha && (req.Captcha == "" || req.CaptchaId == "" || !captchaStore.Verify(req.CaptchaId, req.Captcha, true)) {
		// 验证码次数+1
		blackCache.Increment(key, 1)
		response.FailWithMessage("验证码错误", c)
		return
	}

	user, err := userUsecase.Login(c, req.Username, req.Password)
	if err != nil {
		// 验证码次数+1
		blackCache.Increment(key, 1)
		response.FailWithMessage("用户名不存在或者密码错误", c)
		return
	}

	if user.Enable != 1 {
		// 验证码次数+1
		blackCache.Increment(key, 1)
		response.FailWithMessage("用户被禁止登录", c)
		return
	}

	// 检查用户角色默认路由
	if user.Authority != nil {
		defaultRouter := menuUsecase.UserAuthorityDefaultRouter(c, user.AuthorityId, user.Authority.DefaultRouter)
		user.Authority.DefaultRouter = defaultRouter
	}

	b.TokenNext(c, user)
}

// TokenNext 登录以后签发jwt
func (b *BaseApi) TokenNext(c *gin.Context, user *system.User) {
	// 生成token
	claims := jwtInstance.CreateClaims(jwt.BaseClaims{
		UUID:        user.UUID.String(),
		ID:          user.ID,
		Username:    user.Username,
		NickName:    user.NickName,
		AuthorityID: user.AuthorityId,
	})

	token, err := jwtInstance.CreateToken(claims.BaseClaims)
	if err != nil {
		response.FailWithMessage("获取token失败", c)
		return
	}

	if !useMultipoint {
		utils.SetToken(c, token, int(claims.ExpiresAt.Unix()-time.Now().Unix()))
		response.OkWithDetailed(LoginResponse{
			User:      toUserResponse(user),
			Token:     token,
			ExpiresAt: claims.ExpiresAt.UnixMilli(),
		}, "登录成功", c)
		return
	}

	// 多点登录处理
	if jwtStr, err := jwtBlacklistUsecase.GetRedisJWT(c, user.Username); err == redis.Nil {
		// 没有旧token，直接设置新token
		if err := utils.SetRedisJWT(token, user.Username); err != nil {
			response.FailWithMessage("设置登录状态失败", c)
			return
		}
		utils.SetToken(c, token, int(claims.ExpiresAt.Unix()-time.Now().Unix()))
		response.OkWithDetailed(LoginResponse{
			User:      toUserResponse(user),
			Token:     token,
			ExpiresAt: claims.ExpiresAt.UnixMilli(),
		}, "登录成功", c)
	} else if err != nil {
		response.FailWithMessage("设置登录状态失败", c)
	} else {
		// 有旧token，将旧token加入黑名单
		if err := jwtBlacklistUsecase.JsonInBlacklist(c, jwtStr); err != nil {
			response.FailWithMessage("jwt作废失败", c)
			return
		}
		if err := utils.SetRedisJWT(token, user.Username); err != nil {
			response.FailWithMessage("设置登录状态失败", c)
			return
		}
		utils.SetToken(c, token, int(claims.ExpiresAt.Unix()-time.Now().Unix()))
		response.OkWithDetailed(LoginResponse{
			User:      toUserResponse(user),
			Token:     token,
			ExpiresAt: claims.ExpiresAt.UnixMilli(),
		}, "登录成功", c)
	}
}

// CaptchaResponse 验证码响应
type CaptchaResponse struct {
	CaptchaId     string `json:"captchaId"`
	PicPath       string `json:"picPath"`
	CaptchaLength int    `json:"captchaLength"`
	OpenCaptcha   bool   `json:"openCaptcha"`
}

// 验证码图片配置
var (
	captchaImgHeight int = 80
	captchaImgWidth  int = 240
	captchaKeyLong   int = 6
)

// SetCaptchaImgConfig 设置验证码图片配置
func SetCaptchaImgConfig(height, width, keyLong int) {
	captchaImgHeight = height
	captchaImgWidth = width
	captchaKeyLong = keyLong
}

// Captcha
// @Tags     Base
// @Summary  生成验证码
// @Produce  application/json
// @Success  200  {object}  response.Response{data=CaptchaResponse,msg=string}  "生成验证码,返回包括随机数id,base64,验证码长度,是否开启验证码"
// @Router   /base/captcha [post]
func (b *BaseApi) Captcha(c *gin.Context) {
	key := c.ClientIP()
	v, ok := blackCache.Get(key)
	if !ok {
		blackCache.Set(key, 1, time.Second*time.Duration(openCaptchaTimeOut))
	}

	var oc bool
	if openCaptcha == 0 || openCaptcha < v {
		oc = true
	}

	// 生成数字验证码
	driver := base64Captcha.NewDriverDigit(captchaImgHeight, captchaImgWidth, captchaKeyLong, 0.7, 80)
	cp := base64Captcha.NewCaptcha(driver, captchaStore)
	id, b64s, _, err := cp.Generate()
	if err != nil {
		response.FailWithMessage("验证码获取失败", c)
		return
	}

	response.OkWithDetailed(CaptchaResponse{
		CaptchaId:     id,
		PicPath:       b64s,
		CaptchaLength: captchaKeyLong,
		OpenCaptcha:   oc,
	}, "验证码获取成功", c)
}
