package service

import (
	"log"
	"time"

	"metazone.cc/metalab/internal/common"
	"metazone.cc/metalab/internal/config"
	"metazone.cc/metalab/internal/model"

	"github.com/golang-jwt/jwt/v5"
)

// TokenService JWT 令牌签发与刷新
// 独立于认证业务逻辑，供 AuthService 和其他需要签发令牌的服务使用
type TokenService struct {
	cfg      *config.Config
	userRepo userTokenStore
}

// NewTokenService 构造函数
func NewTokenService(cfg *config.Config, userRepo userTokenStore) *TokenService {
	return &TokenService{cfg: cfg, userRepo: userRepo}
}

// BuildAccessToken 构建 access JWT（含 uid/email/username/role/ver/exp/iat）
// ver = user.TokenVersion，用于即时吊销：版本号不匹配则拒绝
func (ts *TokenService) BuildAccessToken(user *model.User) (string, error) {
	expire := time.Duration(ts.cfg.JWT.AccessExpire) * time.Minute
	now := time.Now()
	expAt := now.Add(expire)
	log.Printf("[BuildAccessToken] AccessExpire=%d min → expire=%v | now=%v | exp=%v (%d) | iat=%d ver=%d",
		ts.cfg.JWT.AccessExpire, expire, now, expAt, expAt.Unix(), now.Unix(), user.TokenVersion)
	claims := jwt.MapClaims{
		"uid":      user.ID,
		"email":    user.Email,
		"username": user.Username,
		"role":     user.Role,
		"ver":      user.TokenVersion,
		"exp":      expAt.Unix(),
		"iat":      now.Unix(),
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(ts.cfg.JWT.Secret))
}

// BuildRefreshToken 构建 refresh JWT（含 purpose:"refresh" 防止 access 冒充）
// rememberMe=true → 使用 remember_expire（30天）；false → 使用 refresh_expire（7天，session 模式）
// ver = user.TokenVersion，刷新时校验：版本号不匹配则拒绝
func (ts *TokenService) BuildRefreshToken(user *model.User, rememberMe bool) (string, error) {
	var expireHours int
	if rememberMe {
		expireHours = ts.cfg.JWT.RememberExpire
	} else {
		expireHours = ts.cfg.JWT.RefreshExpire
	}
	expire := time.Duration(expireHours) * time.Hour
	now := time.Now()
	claims := jwt.MapClaims{
		"uid":      user.ID,
		"email":    user.Email,
		"username": user.Username,
		"role":     user.Role,
		"ver":      user.TokenVersion,
		"exp":      now.Add(expire).Unix(),
		"iat":      now.Unix(),
		"purpose":  "refresh",
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(ts.cfg.JWT.Secret))
}

// RefreshAccessToken 用 refresh token 换取新的 access token
// 校验 token_version：若 DB 中版本已递增，拒绝刷新 → 即时吊销
func (ts *TokenService) RefreshAccessToken(refreshTokenStr string) (string, *model.User, error) {
	if refreshTokenStr == "" {
		return "", nil, common.ErrTokenInvalid
	}

	token, err := jwt.Parse(refreshTokenStr, func(t *jwt.Token) (interface{}, error) {
		return []byte(ts.cfg.JWT.Secret), nil
	})
	if err != nil || !token.Valid {
		return "", nil, common.ErrTokenExpired
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return "", nil, common.ErrTokenInvalid
	}

	// 只接受 refresh 用途的 token，防止 access token 被用于刷新
	if purpose, _ := claims["purpose"].(string); purpose != "refresh" {
		return "", nil, common.ErrTokenInvalid
	}

	uid := uint(claims["uid"].(float64))
	tokenVer := int(claims["ver"].(float64))

	// 即时吊销检查 + 获取最新用户数据（角色/状态可能在 JWT 签发后已变更）
	// 用 FindByIDForAuth 而非从 claims 重建，确保 access token 承载最新数据
	user, err := ts.userRepo.FindByIDForAuth(uid)
	if err != nil {
		return "", nil, common.ErrTokenRevoked
	}

	if tokenVer != user.TokenVersion {
		log.Printf("[RefreshAccessToken] REVOKED: uid=%d tokenVer=%d dbVer=%d", uid, tokenVer, user.TokenVersion)
		return "", nil, common.ErrTokenRevoked
	}

	// 防止封禁用户通过 refresh 续期
	if user.Status == model.StatusBanned {
		return "", nil, common.ErrUserBanned
	}

	accessToken, err := ts.BuildAccessToken(user)
	if err != nil {
		return "", nil, err
	}
	return accessToken, user, nil
}
