package com.luojia_channel.common.utils;

import com.luojia_channel.common.domain.page.PageRequest;
import com.luojia_channel.common.domain.page.PageResponse;
import com.luojia_channel.common.domain.page.ScrollPageRequest;
import com.luojia_channel.common.domain.page.ScrollPageResponse;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static com.luojia_channel.common.constants.RedisConstant.SAFE_GET_LOCK_KEY_PREFIX;

@Component
@RequiredArgsConstructor
public class RedisUtil {

    private final RedisTemplate<String, Object> redisTemplate;
    private final RedissonClient redissonClient;
    private static final Long DEFAULT_TIMEOUT = 30000L;
    private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.MILLISECONDS;


    // 获取redisTemplate,因为复杂的redis操作不便于封装
    public RedisTemplate<String, Object> getInstance(){
        return redisTemplate;
    }

    /**
     * redis基础操作
     */

    public <T> T get(String key, Class<T> type) {
        Object value = redisTemplate.opsForValue().get(key);
        return value != null ? type.cast(value) : null;
    }

    // 安全地从缓存中取值
    public <T> T safeGet(String key, Class<T> type, Supplier<T> cacheLoader,
                         long timeout, TimeUnit timeUnit) {
        T result = get(key, type);
        if(result != null){
            return result;
        }
        // 重建缓存
        RLock lock = redissonClient.getLock(SAFE_GET_LOCK_KEY_PREFIX + key);
        lock.lock();
        try{
            if(get(key, type) == null){
                T dbResult = cacheLoader.get();
                if(dbResult == null){
                    return null;
                }
                set(key, dbResult, timeout, timeUnit);
                return dbResult;
            }
        }finally {
            lock.unlock();
        }
        return get(key, type);
    }

    // 封装基于redis zset的滚动分页查询
    public <T> ScrollPageResponse<T> scrollPageQuery(String key, Class<T> type,
                                               ScrollPageRequest pageRequest,
                                               Function<List<Long>, List<T>> dbFallback) {
        long max = pageRequest.getLastVal();
        long offset = pageRequest.getOffset();
        long size = pageRequest.getSize();
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key, 0, max, offset, size);
        if(typedTuples == null || typedTuples.isEmpty()){
            return ScrollPageResponse.<T>builder().build();
        }
        // 获取返回的offset与minval
        List<Long> ids = new ArrayList<>();
        int returnOffset = 1;
        long min = 0;
        for (ZSetOperations.TypedTuple<Object> tuple : typedTuples) {
            Long id = (Long)tuple.getValue();
            ids.add(id);
            long lastVal = tuple.getScore().longValue();
            if(lastVal == min){
                returnOffset++;
            }else{
                returnOffset = 1;
                min = lastVal;
            }
        }
        List<T> dbList = dbFallback.apply(ids);
        return ScrollPageResponse.<T>builder()
                .records(dbList)
                .size(pageRequest.getSize())
                .offset(returnOffset)
                .lastVal(min)
                .build();
    }



    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value, DEFAULT_TIMEOUT, DEFAULT_TIME_UNIT);
    }

    public void set(String key, Object value, long timeout, TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    public void expire(String key, long time, TimeUnit timeUnit) {
        redisTemplate.expire(key, time, timeUnit);
    }

    public void delete(String key) {
        redisTemplate.delete(key);
    }

    public boolean hasKey(String key) {
        Boolean result = redisTemplate.hasKey(key);
        return result != null && result;
    }

    public Long getExpire(String key, TimeUnit timeUnit){
        return redisTemplate.getExpire(key, timeUnit);
    }

    /**
     * set操作
     */
    private <T> List<T> convertSetToList(Set<Object> set) {
        if(set == null || set.isEmpty()){
            return Collections.emptyList();
        }
        return set.stream().map(obj -> (T) obj).collect(Collectors.toList());
    }

    public void sAdd(String key, Object... values) {
        redisTemplate.opsForSet().add(key, values);
    }

    public void sRemove(String key, Object... values) {
        redisTemplate.opsForSet().remove(key, values);
    }

    public <T> List<T> sGet(String key) {
        Set<Object> members = redisTemplate.opsForSet().members(key);
        return convertSetToList(members);
    }

    public Boolean sIsMember(String key, Object value){
        return redisTemplate.opsForSet().isMember(key, value);
    }

    public <T> List<T> sCommon(String key, String otherKey){
        Set<Object> intersect = redisTemplate.opsForSet().intersect(key, otherKey);
        return convertSetToList(intersect);
    }


    /**
     * zSet操作
     */
    // 带分数的结果包装类
    @Data
    @AllArgsConstructor
    public static class ZSetItem<T> {
        private T value;
        private Double score;
    }
    private <T> List<ZSetItem<T>> convertTuples(Set<ZSetOperations.TypedTuple<Object>> tuples) {
        if(tuples == null || tuples.isEmpty()){
            return Collections.emptyList();
        }
        return tuples.stream()
                .map(tuple -> new ZSetItem<T>(
                        (T) tuple.getValue(),
                        tuple.getScore() != null ? tuple.getScore() : 0.0
                ))
                .collect(Collectors.toList());
    }

    // 添加元素
    public Boolean zAdd(String key, Object value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    public Boolean zAdd(String key, Object value, double score, long timeout, TimeUnit unit) {
        Boolean result = redisTemplate.opsForZSet().add(key, value, score);
        if (Boolean.TRUE.equals(result)) {
            redisTemplate.expire(key, timeout, unit);
        }
        return result;
    }

    // 获取范围
    public <T> List<T> zRange(String key, long start, long end) {
        Set<Object> values = redisTemplate.opsForZSet().range(key, start, end);
        return convertSetToList(values);
    }

    public <T> List<T> zRevRange(String key, long start, long end) {
        Set<Object> values = redisTemplate.opsForZSet().reverseRange(key, start, end);
        return convertSetToList(values);
    }

    // 带分数查询（用于热度计算）
    public <T> List<ZSetItem<T>> zRangeWithScores(String key, long start, long end) {
        Set<ZSetOperations.TypedTuple<Object>> typedTuples = redisTemplate.opsForZSet().rangeWithScores(key, start, end);
        return convertTuples(typedTuples);
    }

    // 增减分数（用于点赞数统计）
    public Double zIncrScore(String key, Object value, double delta) {
        return redisTemplate.opsForZSet().incrementScore(key, value, delta);
    }

    // 删除元素
    public Long zRemove(String key, Object... values) {
        return redisTemplate.opsForZSet().remove(key, values);
    }

    // 获取排名（用于热榜）
    public Long zRank(String key, Object value) {
        return redisTemplate.opsForZSet().rank(key, value);
    }

    public Long zRevRank(String key, Object value) {
        return redisTemplate.opsForZSet().reverseRank(key, value);
    }

    // 获取集合大小
    public Long zCard(String key) {
        return redisTemplate.opsForZSet().zCard(key);
    }


    public <T> List<ZSetItem<T>> zRevRangeWithScores(String key, long count) {
        return zRevRangeWithScores(key, 0, count - 1);
    }


    public <T> List<ZSetItem<T>> zRevRangeWithScores(String key, long start, long end) {
        Set<ZSetOperations.TypedTuple<Object>> tuples = redisTemplate.opsForZSet().reverseRangeWithScores(key, start, end);
        return convertTuples(tuples);
    }


    public <T> T zRevMaxValue(String key) {
        List<ZSetItem<T>> items = zRevRangeWithScores(key, 1);
        return items.isEmpty() ? null : items.get(0).getValue();
    }


    public <T> ZSetItem<T> zRevMaxItem(String key) {
        List<ZSetItem<T>> items = zRevRangeWithScores(key, 1);
        return items.isEmpty() ? null : items.get(0);
    }


}