package com.huangzj.server.redis;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Range;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

/**
 * 基于 {@link StringRedisTemplate} 的 Redis 常用数据结构封装，供业务注入使用。
 *
 * <p>约定：所有 value / member / field 均按 <strong>字符串</strong> 与 Redis 交互；若需存对象，请用 {@link
 * #setJson}/{@link #getJson} 或 Hash 字段级 {@link #hashSetJson}/{@link #hashGetJson}（Jackson 序列化）。
 *
 * <p>方法名前缀含义：{@code str} 字符串、{@code hash} 哈希表、{@code list} 列表、{@code set} 集合、{@code zSet}
 * 有序集合；未命中 key 时返回值多为 {@code null}，与 Spring Data Redis 行为一致。
 */
@Component
@RequiredArgsConstructor
public class RedisUtil {

    private final StringRedisTemplate redis;
    private final ObjectMapper objectMapper;

    // region String（Redis String / Value）

    /** 写入字符串，无过期时间。 */
    public void strSet(String key, String value) {
        redis.opsForValue().set(key, value);
    }

    /** 写入字符串并设置存活时间。 */
    public void strSet(String key, String value, Duration ttl) {
        redis.opsForValue().set(key, value, ttl);
    }

    /** 仅当 key 不存在时写入；成功返回 true。 */
    public boolean strSetIfAbsent(String key, String value) {
        return Boolean.TRUE.equals(redis.opsForValue().setIfAbsent(key, value));
    }

    /** 仅当 key 不存在时写入并带 TTL；成功返回 true。 */
    public boolean strSetIfAbsent(String key, String value, Duration ttl) {
        return Boolean.TRUE.equals(redis.opsForValue().setIfAbsent(key, value, ttl));
    }

    /** 读取字符串；不存在返回 {@code null}。 */
    public String strGet(String key) {
        return redis.opsForValue().get(key);
    }

    /** 原子地读出旧值并写入新值。 */
    public String strGetAndSet(String key, String value) {
        return redis.opsForValue().getAndSet(key, value);
    }

    /** 将数字字符串自增 1；key 不存在时先按 0 再增。 */
    public Long strIncrement(String key) {
        return redis.opsForValue().increment(key);
    }

    /** 将数字字符串自增 {@code delta}。 */
    public Long strIncrement(String key, long delta) {
        return redis.opsForValue().increment(key, delta);
    }

    /** 将数字字符串自减 1。 */
    public Long strDecrement(String key) {
        return redis.opsForValue().decrement(key);
    }

    /** 将数字字符串自减 {@code delta}。 */
    public Long strDecrement(String key, long delta) {
        return redis.opsForValue().decrement(key, delta);
    }

    /** 设置位图中某一位；对应 Redis SETBIT。 */
    public Boolean strSetBit(String key, long offset, boolean value) {
        return redis.opsForValue().setBit(key, offset, value);
    }

    /** 读取位图中某一位；对应 Redis GETBIT。 */
    public Boolean strGetBit(String key, long offset) {
        return redis.opsForValue().getBit(key, offset);
    }

    /**
     * 将对象序列化为 JSON 字符串后写入；序列化失败抛出 {@link IllegalStateException}。
     */
    public void setJson(String key, Object value) {
        try {
            strSet(key, objectMapper.writeValueAsString(value));
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("Redis JSON encode: " + key, e);
        }
    }

    /**
     * 将对象序列化为 JSON 字符串后写入并带 TTL。
     *
     * @throws IllegalStateException JSON 序列化失败
     */
    public void setJson(String key, Object value, Duration ttl) {
        try {
            strSet(key, objectMapper.writeValueAsString(value), ttl);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("Redis JSON encode: " + key, e);
        }
    }

    /**
     * 读出 JSON 并反序列化为指定类型；key 不存在返回 {@code null}。
     *
     * @throws IllegalStateException 反序列化失败
     */
    public <T> T getJson(String key, Class<T> type) {
        String raw = strGet(key);
        if (raw == null) {
            return null;
        }
        try {
            return objectMapper.readValue(raw, type);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("Redis JSON decode: " + key, e);
        }
    }

    /**
     * 读出 JSON 并反序列化（支持泛型，如 {@code List<String>}）；key 不存在返回 {@code null}。
     *
     * @throws IllegalStateException 反序列化失败
     */
    public <T> T getJson(String key, TypeReference<T> typeRef) {
        String raw = strGet(key);
        if (raw == null) {
            return null;
        }
        try {
            return objectMapper.readValue(raw, typeRef);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("Redis JSON decode: " + key, e);
        }
    }

    // endregion

    // region Hash

    /** 哈希表设置单个 field。 */
    public void hashSet(String key, String field, String value) {
        redis.opsForHash().put(key, field, value);
    }

    /** 哈希表批量写入；{@code map} 为空则不做任何事。 */
    public void hashSetAll(String key, Map<String, String> map) {
        if (map == null || map.isEmpty()) {
            return;
        }
        redis.opsForHash().putAll(key, map);
    }

    /** 读取单个 field；不存在返回 {@code null}。 */
    public String hashGet(String key, String field) {
        Object v = redis.opsForHash().get(key, field);
        return v == null ? null : String.valueOf(v);
    }

    /**
     * 批量读 field，返回列表顺序与入参 {@code fields} 一致；某 field 不存在对应位置为 {@code null}。
     */
    public List<String> hashMultiGet(String key, Collection<String> fields) {
        if (fields == null || fields.isEmpty()) {
            return List.of();
        }
        List<Object> raw = redis.opsForHash().multiGet(key, new ArrayList<>(fields));
        List<String> out = new ArrayList<>(raw.size());
        for (Object o : raw) {
            out.add(o == null ? null : String.valueOf(o));
        }
        return out;
    }

    /** 返回哈希表全部 field-value；空表返回空 Map（非 {@code null}）。 */
    public Map<String, String> hashGetAll(String key) {
        Map<Object, Object> entries = redis.opsForHash().entries(key);
        if (entries.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<String, String> out = new LinkedHashMap<>(entries.size());
        entries.forEach((k, v) -> out.put(String.valueOf(k), v == null ? null : String.valueOf(v)));
        return out;
    }

    /** 删除一个或多个 field，返回实际删除个数。 */
    public Long hashDelete(String key, Object... fields) {
        if (fields == null || fields.length == 0) {
            return 0L;
        }
        return redis.opsForHash().delete(key, fields);
    }

    /** field 是否存在。 */
    public Boolean hashHasKey(String key, String field) {
        return redis.opsForHash().hasKey(key, field);
    }

    /** 哈希表 field 个数。 */
    public Long hashSize(String key) {
        return redis.opsForHash().size(key);
    }

    /** 将 field 上数字字符串按 long 自增。 */
    public Long hashIncrement(String key, String field, long delta) {
        return redis.opsForHash().increment(key, field, delta);
    }

    /** 将 field 上数字按 double 自增（可存浮点计数）。 */
    public Double hashIncrement(String key, String field, double delta) {
        return redis.opsForHash().increment(key, field, delta);
    }

    /** 将对象 JSON 化后写入指定 field。 */
    public void hashSetJson(String key, String field, Object value) {
        try {
            hashSet(key, field, objectMapper.writeValueAsString(value));
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("Redis hash JSON encode: " + key + "#" + field, e);
        }
    }

    /** 读出 field 的 JSON 并反序列化；field 不存在返回 {@code null}。 */
    public <T> T hashGetJson(String key, String field, Class<T> type) {
        String raw = hashGet(key, field);
        if (raw == null) {
            return null;
        }
        try {
            return objectMapper.readValue(raw, type);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("Redis hash JSON decode: " + key + "#" + field, e);
        }
    }

    // endregion

    // region List

    /** 从列表左侧压入一个元素，返回列表当前长度。 */
    public Long listLeftPush(String key, String value) {
        return redis.opsForList().leftPush(key, value);
    }

    /** 从左侧一次压入多个元素。 */
    public Long listLeftPushAll(String key, String... values) {
        if (values == null || values.length == 0) {
            return 0L;
        }
        return redis.opsForList().leftPushAll(key, values);
    }

    /** 从列表右侧压入一个元素。 */
    public Long listRightPush(String key, String value) {
        return redis.opsForList().rightPush(key, value);
    }

    /** 从右侧一次压入多个元素。 */
    public Long listRightPushAll(String key, String... values) {
        if (values == null || values.length == 0) {
            return 0L;
        }
        return redis.opsForList().rightPushAll(key, values);
    }

    /** 非阻塞：从左侧弹出一个元素。 */
    public String listLeftPop(String key) {
        return redis.opsForList().leftPop(key);
    }

    /** 非阻塞：从右侧弹出一个元素。 */
    public String listRightPop(String key) {
        return redis.opsForList().rightPop(key);
    }

    /**
     * 阻塞版左侧弹出：在 {@code timeout} 内等待直到有元素或超时；超时返回 {@code null}。
     */
    public String listLeftPop(String key, Duration timeout) {
        return redis.opsForList().leftPop(key, timeout);
    }

    /**
     * 阻塞版右侧弹出。
     */
    public String listRightPop(String key, Duration timeout) {
        return redis.opsForList().rightPop(key, timeout);
    }

    /**
     * 按闭区间下标取子列表；{@code end} 可为 -1 表示到表尾。
     */
    public List<String> listRange(String key, long start, long end) {
        return redis.opsForList().range(key, start, end);
    }

    /** 列表长度。 */
    public Long listSize(String key) {
        return redis.opsForList().size(key);
    }

    /** 按下标取值；越界返回 {@code null}。 */
    public String listIndex(String key, long index) {
        return redis.opsForList().index(key, index);
    }

    /** 按下标覆盖元素。 */
    public void listSet(String key, long index, String value) {
        redis.opsForList().set(key, index, value);
    }

    /** 裁剪列表，只保留闭区间 {@code [start, end]} 内元素。 */
    public void listTrim(String key, long start, long end) {
        redis.opsForList().trim(key, start, end);
    }

    /**
     * 删除与 {@code value} 相等的元素；{@code count > 0} 从左删 {@code count} 个，{@code count < 0} 从右删，{@code
     * count = 0} 删除全部相等项。
     */
    public Long listRemove(String key, long count, String value) {
        return redis.opsForList().remove(key, count, value);
    }

    // endregion

    // region Set

    /** 向集合添加成员，返回新增个数（已存在的不计）。 */
    public Long setAdd(String key, String... members) {
        if (members == null || members.length == 0) {
            return 0L;
        }
        return redis.opsForSet().add(key, members);
    }

    /** 从集合移除成员，返回实际移除个数。 */
    public Long setRemove(String key, Object... members) {
        if (members == null || members.length == 0) {
            return 0L;
        }
        return redis.opsForSet().remove(key, members);
    }

    /** 返回集合全部成员；空集合返回空 Set（非 {@code null}）。 */
    public Set<String> setMembers(String key) {
        Set<String> m = redis.opsForSet().members(key);
        return m == null ? Collections.emptySet() : m;
    }

    /** 判断 member 是否在集合中。 */
    public Boolean setIsMember(String key, String member) {
        return redis.opsForSet().isMember(key, member);
    }

    /** 集合元素个数。 */
    public Long setSize(String key) {
        return redis.opsForSet().size(key);
    }

    /** 计算 {@code key} 与 {@code otherKey} 两个集合的交集。 */
    public Set<String> setIntersect(String key, String otherKey) {
        Set<String> s = redis.opsForSet().intersect(key, List.of(otherKey));
        return s == null ? Collections.emptySet() : s;
    }

    /** 计算两个集合的并集。 */
    public Set<String> setUnion(String key, String otherKey) {
        Set<String> s = redis.opsForSet().union(key, List.of(otherKey));
        return s == null ? Collections.emptySet() : s;
    }

    /** 计算两个集合的差集（存在于 key 且不存在于 otherKey）。 */
    public Set<String> setDifference(String key, String otherKey) {
        Set<String> s = redis.opsForSet().difference(key, List.of(otherKey));
        return s == null ? Collections.emptySet() : s;
    }

    // endregion

    // region Sorted set（ZSet）

    /** 添加成员及其分数；新增返回 true，仅更新分数可能返回 false（与 Redis 版本/实现有关）。 */
    public Boolean zSetAdd(String key, String member, double score) {
        return redis.opsForZSet().add(key, member, score);
    }

    /** 按成员删除，返回删除个数。 */
    public Long zSetRemove(String key, Object... members) {
        if (members == null || members.length == 0) {
            return 0L;
        }
        return redis.opsForZSet().remove(key, members);
    }

    /** 将成员分数增加 {@code delta}，返回新分数。 */
    public Double zSetIncrementScore(String key, String member, double delta) {
        return redis.opsForZSet().incrementScore(key, member, delta);
    }

    /** 有序集合成员个数（基数）。 */
    public Long zSetCard(String key) {
        return redis.opsForZSet().zCard(key);
    }

    /** 分数在闭区间 {@code [min, max]} 内的成员数量。 */
    public Long zSetCount(String key, double min, double max) {
        return redis.opsForZSet().count(key, min, max);
    }

    /** 查询成员分数；不存在返回 {@code null}。 */
    public Double zSetScore(String key, String member) {
        return redis.opsForZSet().score(key, member);
    }

    /** 按分数从低到高的排名（0 起）；不存在返回 {@code null}。 */
    public Long zSetRank(String key, String member) {
        return redis.opsForZSet().rank(key, member);
    }

    /** 按分数从高到低的排名。 */
    public Long zSetReverseRank(String key, String member) {
        return redis.opsForZSet().reverseRank(key, member);
    }

    /** 按排名闭区间取成员（分数升序）。 */
    public Set<String> zSetRange(String key, long start, long end) {
        return redis.opsForZSet().range(key, start, end);
    }

    /** 按排名闭区间取成员（分数降序）。 */
    public Set<String> zSetReverseRange(String key, long start, long end) {
        return redis.opsForZSet().reverseRange(key, start, end);
    }

    /** 按分数闭区间 {@code [min, max]} 取成员。 */
    public Set<String> zSetRangeByScore(String key, double min, double max) {
        return redis.opsForZSet().rangeByScore(key, min, max);
    }

    /** 按排名区间取成员并带上分数。 */
    public Set<ZSetOperations.TypedTuple<String>> zSetRangeWithScores(String key, long start, long end) {
        Set<ZSetOperations.TypedTuple<String>> s = redis.opsForZSet().rangeWithScores(key, start, end);
        return s == null ? Collections.emptySet() : s;
    }

    /** 按排名删除闭区间内的成员，返回删除个数。 */
    public Long zSetRemoveRangeByRank(String key, long start, long end) {
        return redis.opsForZSet().removeRange(key, start, end);
    }

    /** 按分数闭区间删除成员（含边界）。 */
    public Long zSetRemoveRangeByScore(String key, double min, double max) {
        return redis.opsForZSet().removeRangeByScore(key, min, max);
    }

    // endregion

    // region HyperLogLog

    /** 向 HyperLogLog 添加元素，返回内部认为新增了多少（近似 0/1）。 */
    public Long hyperLogLogAdd(String key, String... elements) {
        if (elements == null || elements.length == 0) {
            return 0L;
        }
        return redis.opsForHyperLogLog().add(key, elements);
    }

    /** 估算基数（去重后大约多少个不同元素）。 */
    public Long hyperLogLogSize(String key) {
        return redis.opsForHyperLogLog().size(key);
    }

    /** 将多个 HyperLogLog 合并到 {@code destination}。 */
    public void hyperLogLogUnion(String destination, String... sourceKeys) {
        if (sourceKeys == null || sourceKeys.length == 0) {
            return;
        }
        redis.opsForHyperLogLog().union(destination, sourceKeys);
    }

    // endregion

    // region Key 与通用

    /** 删除单个 key；不存在返回 false。 */
    public Boolean delete(String key) {
        return redis.delete(key);
    }

    /** 批量删除，返回成功删除的 key 数量。 */
    public Long delete(Collection<String> keys) {
        if (keys == null || keys.isEmpty()) {
            return 0L;
        }
        return redis.delete(keys);
    }

    /** key 是否存在。 */
    public Boolean hasKey(String key) {
        return redis.hasKey(key);
    }

    /** 设置存活时间；key 不存在返回 false。 */
    public Boolean expire(String key, Duration ttl) {
        return redis.expire(key, ttl);
    }

    /** 去掉 TTL，使 key 持久存在（若支持）。 */
    public Boolean persist(String key) {
        return redis.persist(key);
    }

    /** 剩余过期时间（秒），-1 表示无过期，-2 表示 key 不存在。 */
    public Long getExpire(String key) {
        return redis.getExpire(key);
    }

    /** 剩余过期时间，单位由 {@code unit} 指定。 */
    public Long getExpire(String key, TimeUnit unit) {
        return redis.getExpire(key, unit);
    }

    /**
     * 使用 SCAN 迭代匹配 {@code pattern} 的 key（非阻塞）；{@code count} 为单次提示扫描条数，非精确上限。
     *
     * <p>适合生产环境替代 {@code KEYS}。
     */
    public List<String> scanKeys(String pattern, long count) {
        String p = Objects.requireNonNull(pattern, "pattern");
        ScanOptions options = ScanOptions.scanOptions().match(p).count(count).build();
        List<String> keys = new ArrayList<>();
        try (Cursor<String> cursor = redis.scan(options)) {
            while (cursor.hasNext()) {
                keys.add(cursor.next());
            }
        }
        return keys;
    }

    /**
     * 将 {@link ZSetOperations.TypedTuple} 集合转为 {@code {member, score}} 的 Map 列表，便于直接 JSON 输出。
     */
    public List<Map<String, Object>> zSetTuplesToRows(Set<ZSetOperations.TypedTuple<String>> tuples) {
        if (tuples == null || tuples.isEmpty()) {
            return List.of();
        }
        return tuples.stream()
                .map(t -> {
                    Map<String, Object> row = new LinkedHashMap<>(2);
                    row.put("member", t.getValue());
                    row.put("score", t.getScore());
                    return row;
                })
                .collect(Collectors.toList());
    }

    /**
     * 单次请求批量读取多个 key 的字符串值；结果顺序与 {@code keys} 一致，不存在的 key 对应位置为 {@code null}。
     */
    public List<String> strMultiGet(List<String> keys) {
        if (keys == null || keys.isEmpty()) {
            return List.of();
        }
        return redis.opsForValue().multiGet(keys);
    }

    /** 批量写入多个 key-value，对应 Redis MSET。 */
    public void strMultiSet(Map<String, String> map) {
        if (map == null || map.isEmpty()) {
            return;
        }
        redis.opsForValue().multiSet(map);
    }

    /**
     * 返回底层 {@link StringRedisTemplate}，用于执行 Lua、自定义 {@link org.springframework.data.redis.core.script.RedisScript}
     * 等本类未封装的能力。
     */
    public StringRedisTemplate template() {
        return redis;
    }

    // endregion
}
