package centrifugo

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/zeromicro/go-zero/core/stores/redis"
)

// 錯誤定義
var (
	ErrBlacklistNotConfigured   = errors.New("token blacklist is not configured")
	ErrOnlineStoreNotConfigured = errors.New("online store is not configured")
)

// TokenBlacklist Token 黑名單管理器
// 提供兩種撤銷機制：
//  1. 單一 Token 撤銷：使用 JTI（JWT ID）將特定 Token 加入黑名單
//  2. 用戶全部撤銷：使用版本號機制，使用戶之前所有 Token 失效
type TokenBlacklist struct {
	redis  *redis.Redis
	prefix string
}

// NewTokenBlacklist 創建 Token 黑名單管理器
func NewTokenBlacklist(redisClient *redis.Redis) *TokenBlacklist {
	return &TokenBlacklist{
		redis:  redisClient,
		prefix: "centrifugo:blacklist:",
	}
}

// NewTokenBlacklistWithPrefix 創建帶自定義前綴的 Token 黑名單管理器
func NewTokenBlacklistWithPrefix(redisClient *redis.Redis, prefix string) *TokenBlacklist {
	return &TokenBlacklist{
		redis:  redisClient,
		prefix: prefix,
	}
}

// ==================== 撤銷操作 ====================

// RevokeToken 撤銷特定 Token（使用 JTI）
// ttl: 黑名單過期時間，應設置為 Token 的剩餘有效時間
//
// 使用場景：
//   - 用戶登出單一設備
//   - 檢測到可疑活動的特定 session
func (b *TokenBlacklist) RevokeToken(ctx context.Context, jti string, ttl time.Duration) error {
	if jti == "" {
		return errors.New("jti cannot be empty")
	}
	key := b.tokenKey(jti)
	return b.redis.SetexCtx(ctx, key, "revoked", int(ttl.Seconds()))
}

// RevokeUserTokens 撤銷用戶的所有 Token（使用版本控制）
// 通過更新版本號，使該用戶之前發出的所有 Token 失效
//
// 使用場景：
//   - 用戶被封禁
//   - 密碼變更
//   - 用戶主動登出全部設備
func (b *TokenBlacklist) RevokeUserTokens(ctx context.Context, userID string) error {
	if userID == "" {
		return errors.New("userID cannot be empty")
	}
	key := b.userVersionKey(userID)
	version := time.Now().UnixNano()
	// 設置 7 天過期，足夠長於任何 Token 的有效期
	return b.redis.SetexCtx(ctx, key, fmt.Sprintf("%d", version), 7*24*3600)
}

// ==================== 驗證操作 ====================

// IsTokenRevoked 檢查 Token 是否被撤銷（使用 JTI）
func (b *TokenBlacklist) IsTokenRevoked(ctx context.Context, jti string) (bool, error) {
	if jti == "" {
		return false, nil
	}
	key := b.tokenKey(jti)
	exists, err := b.redis.ExistsCtx(ctx, key)
	if err != nil {
		return false, err
	}
	return exists, nil
}

// GetUserTokenVersion 獲取用戶的 Token 版本
// 返回 0 表示沒有設置版本（用戶從未被撤銷過）
func (b *TokenBlacklist) GetUserTokenVersion(ctx context.Context, userID string) (int64, error) {
	if userID == "" {
		return 0, nil
	}
	key := b.userVersionKey(userID)
	val, err := b.redis.GetCtx(ctx, key)
	if err != nil {
		return 0, err
	}
	if val == "" {
		return 0, nil
	}
	var version int64
	_, err = fmt.Sscanf(val, "%d", &version)
	return version, err
}

// IsTokenVersionValid 檢查 Token 版本是否有效
// tokenVersion: Token 內嵌的版本號
// 如果 currentVersion > tokenVersion，表示 Token 已被撤銷
func (b *TokenBlacklist) IsTokenVersionValid(ctx context.Context, userID string, tokenVersion int64) (bool, error) {
	currentVersion, err := b.GetUserTokenVersion(ctx, userID)
	if err != nil {
		return false, err
	}
	// 如果沒有設置版本，或 Token 版本 >= 當前版本，則有效
	return currentVersion == 0 || tokenVersion >= currentVersion, nil
}

// ==================== Key 生成 ====================

func (b *TokenBlacklist) tokenKey(jti string) string {
	return b.prefix + "token:" + jti
}

func (b *TokenBlacklist) userVersionKey(userID string) string {
	return b.prefix + "user_version:" + userID
}
