package com.fs.course.config;

import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

@Component
public class RedisKeyScanner {

    private final StringRedisTemplate redisTemplate;

    public RedisKeyScanner(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public Set<String> scan(String pattern) {
        Set<String> keys = new HashSet<>();

        // 这里指定 match + count
        ScanOptions options = ScanOptions.scanOptions()
                .match(pattern)
                .count(1000)
                .build();

        // 使用 RedisConnection 提供的 scan
        redisTemplate.execute((RedisConnection connection) -> {
            try (Cursor<byte[]> cursor = connection.scan(options)) {
                RedisSerializer<String> keySerializer = redisTemplate.getStringSerializer();
                cursor.forEachRemaining(item -> keys.add(keySerializer.deserialize(item)));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return null;
        });

        return keys;
    }

    public Set<String> scanKeys(String pattern) {
        Set<String> keys = new HashSet<>();
        ScanOptions options = ScanOptions.scanOptions().match(pattern).count(1000).build();

        redisTemplate.execute((RedisConnection connection) -> {
            try (Cursor<byte[]> cursor = connection.scan(options)) {
                while (cursor.hasNext()) {
                    byte[] rawKey = cursor.next();
                    String key = (String) redisTemplate.getKeySerializer().deserialize(rawKey);
                    keys.add(key);
                }
            } catch (IOException e) {
                throw new RuntimeException("Error during scan", e);
            }
            return null;
        });

        return keys;
    }

    /**
     * 使用 SCAN 替代 KEYS，避免阻塞 Redis
     *
     * @param pattern key 匹配模式，例如 "h5user:watch:duration:*"
     * @return 匹配到的 key 集合
     */
    public Collection<String> scanPatternKeys(final String pattern) {
        Set<String> keys = new HashSet<>();

        // 每次扫描的数量，可根据实际情况调整（越大速度越快，但对 CPU 影响越大）
        ScanOptions options = ScanOptions.scanOptions()
                .match(pattern)
                .count(1000)
                .build();

        // 使用 redisTemplate 的 execute 保证连接获取 & 释放正确
        redisTemplate.execute((RedisConnection connection) -> {
            try (Cursor<byte[]> cursor = connection.scan(options)) {
                RedisSerializer<String> keySerializer = redisTemplate.getStringSerializer();
                while (cursor.hasNext()) {
                    keys.add(keySerializer.deserialize(cursor.next()));
                }
            } catch (IOException e) {
                throw new RuntimeException("Error during redis SCAN", e);
            }
            return null;
        });

        return keys;
    }

    public Set<String> scanMatchKey(String pattern) {
        return (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            Set<String> keys = new HashSet<>();
            // 使用 try-with-resources 确保 Cursor 无论是否异常都会被关闭
            try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions()
                    .match(pattern)
                    .count(1000) // 每次扫描的批量大小，可根据实际情况调整
                    .build())) {
                while (cursor.hasNext()) {
                    keys.add(new String(cursor.next(), StandardCharsets.UTF_8)); // 显式指定字符集，避免依赖默认值
                }
            } catch (IOException e) {
                // 在实际项目中，应该使用更合适的日志记录和异常处理方式
                // 例如抛出自定义异常或记录错误日志
                throw new RuntimeException("Error during SCAN operation", e);
            }
            return keys;
        });
    }

}
