package user

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/perfect-panel/server/internal/config"
	"github.com/perfect-panel/server/internal/svc"
	"github.com/perfect-panel/server/internal/types"
	"github.com/perfect-panel/server/pkg/logger"
	"github.com/perfect-panel/server/pkg/xerr"
	"github.com/pkg/errors"
)

type KickOfflineByUserDeviceLogic struct {
	logger.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

// kick offline user device
func NewKickOfflineByUserDeviceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *KickOfflineByUserDeviceLogic {
	return &KickOfflineByUserDeviceLogic{
		Logger: logger.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *KickOfflineByUserDeviceLogic) KickOfflineByUserDevice(req *types.KickOfflineRequest) error {
	device, err := l.svcCtx.UserModel.FindOneDevice(l.ctx, req.Id)
	if err != nil {
		return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "get Device  error: %v", err.Error())
	}
	l.svcCtx.DeviceManager.KickDevice(device.UserId, device.Identifier)
	device.Online = false
	err = l.svcCtx.UserModel.UpdateDevice(l.ctx, device)
	if err != nil {
		l.Logger.Error("[KickOfflineByUserDeviceLogic] Update Device Error:", logger.Field("err", err.Error()))
		return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update Device error: %v", err.Error())
	}

	// 清除该用户的所有会话，确保旧 token 失效
	l.clearAllSessions(device.UserId)

	return nil
}

// clearAllSessions 清除指定用户的所有会话（通过 SCAN 查找，不依赖 sorted set）
func (l *KickOfflineByUserDeviceLogic) clearAllSessions(userId int64) {
	sessionSet := make(map[string]struct{})

	userIDText := strconv.FormatInt(userId, 10)
	pattern := fmt.Sprintf("%s:*", config.SessionIdKey)
	var cursor uint64
	for {
		keys, nextCursor, scanErr := l.svcCtx.Redis.Scan(l.ctx, cursor, pattern, 200).Result()
		if scanErr != nil {
			l.Errorw("扫描会话键失败", logger.Field("user_id", userId), logger.Field("error", scanErr.Error()))
			break
		}
		for _, sessionKey := range keys {
			value, getErr := l.svcCtx.Redis.Get(l.ctx, sessionKey).Result()
			if getErr != nil || value != userIDText {
				continue
			}
			sessionID := strings.TrimPrefix(sessionKey, config.SessionIdKey+":")
			if sessionID == "" || strings.HasPrefix(sessionID, "detail:") {
				continue
			}
			sessionSet[sessionID] = struct{}{}
		}
		cursor = nextCursor
		if cursor == 0 {
			break
		}
	}

	deviceKeySet := make(map[string]struct{})
	devicePattern := fmt.Sprintf("%s:*", config.DeviceCacheKeyKey)
	cursor = 0
	for {
		keys, nextCursor, scanErr := l.svcCtx.Redis.Scan(l.ctx, cursor, devicePattern, 200).Result()
		if scanErr != nil {
			l.Errorw("扫描设备会话映射失败", logger.Field("user_id", userId), logger.Field("error", scanErr.Error()))
			break
		}
		for _, deviceKey := range keys {
			sessionID, getErr := l.svcCtx.Redis.Get(l.ctx, deviceKey).Result()
			if getErr != nil {
				continue
			}
			if _, exists := sessionSet[sessionID]; exists {
				deviceKeySet[deviceKey] = struct{}{}
			}
		}
		cursor = nextCursor
		if cursor == 0 {
			break
		}
	}

	if len(sessionSet) == 0 {
		return
	}

	sessionsKey := fmt.Sprintf("%s%v", config.UserSessionsKeyPrefix, userId)
	pipe := l.svcCtx.Redis.TxPipeline()
	for sessionID := range sessionSet {
		pipe.Del(l.ctx, fmt.Sprintf("%v:%v", config.SessionIdKey, sessionID))
		pipe.Del(l.ctx, fmt.Sprintf("%s:detail:%s", config.SessionIdKey, sessionID))
		pipe.ZRem(l.ctx, sessionsKey, sessionID)
	}
	pipe.Del(l.ctx, sessionsKey)

	for deviceKey := range deviceKeySet {
		pipe.Del(l.ctx, deviceKey)
	}

	if _, err := pipe.Exec(l.ctx); err != nil {
		l.Errorw("清理会话缓存失败",
			logger.Field("user_id", userId),
			logger.Field("error", err.Error()),
		)
	}

	l.Infow("[KickOffline] 管理员踢设备-清除所有Session",
		logger.Field("user_id", userId),
		logger.Field("count", len(sessionSet)),
	)
}
