package shop.alien.lawyer.config;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author ssk
 * @version 1.0
 * @date 2022/3/14 15:14
 */
@Component
@RequiredArgsConstructor
public class BaseRedisService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 添加List值, 向右
     *
     * @param key   键
     * @param value 值
     */
    public void setListRight(String key, String value) {
        stringRedisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * 取出并删除列表中的所有元素（原子操作）
     */
    public List<String> popBatchFromList(String key) {
        String luaScript =
                "local elements = redis.call('LRANGE', KEYS[1], 0, -1) " +
                        "redis.call('DEL', KEYS[1]) " +
                        "return elements ";

        RedisScript<List> script = RedisScript.of(luaScript, List.class);
        return stringRedisTemplate.execute(script, Collections.singletonList(key));
    }

    private static final String SAVE_OR_OVERWRITE_SCRIPT =
            "if redis.call('EXISTS', KEYS[1]) == 1 then " +
                    "   redis.call('DEL', KEYS[1]) " +
                    "end " +
                    "return redis.call('RPUSH', KEYS[1], unpack(ARGV))";

    /**
     * 如果key不存在则存入,存在则覆盖
     * @param key
     * @param values
     */
    public void setSaveOrOverwriteScriptList(String key, List<String> values) {

        // 创建DefaultRedisScript对象
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(SAVE_OR_OVERWRITE_SCRIPT);
        redisScript.setResultType(Long.class);

        // 执行脚本
        stringRedisTemplate.execute(
                redisScript,
                Collections.singletonList(key),
                values.toArray()
        );
    }

    /**
     * 判断是否存在指定key
     * @param key
     * @return
     */
    public boolean hasKey(String key) {
        Boolean exists = stringRedisTemplate.hasKey(key);
        return exists != null && exists;
    }

    /**
     * 从List中删除所有匹配的字符串
     * @param key Redis key
     * @param value 要删除的值
     * @return 删除的元素数量
     */
    public Long removeAllOccurrences(String key, String value) {
        return stringRedisTemplate.opsForList().remove(key, 0, value);
    }

    /**
     * 添加List, 所有
     *
     * @param key   键
     * @param value 值
     */
    public void setList(String key, List<String> value) {
        stringRedisTemplate.opsForList().rightPushAll(key, value);
    }

    /**
     * 从List左边取值并删除
     *
     * @param key 键
     * @return 删除的值
     */
    public String getListLeft(String key) {
        return stringRedisTemplate.opsForList().leftPop(key);
    }

    /**
     * 获取List所有
     *
     * @param key 键
     * @return List<String>
     */
    public List<String> getList(String key) {
        return stringRedisTemplate.opsForList().range(key, 0, -1);
    }


    /**
     * 添加Set
     *
     * @param key   键
     * @param value 值
     */
    public void setSetList(String key, String value) {
        stringRedisTemplate.opsForSet().add(key, value);
    }

    /**
     * 获取Set
     *
     * @param key 键
     * @return Set<String>
     */
    public Set<String> getSetList(String key) {
        return stringRedisTemplate.opsForSet().members(key);
    }

    /**
     * 添加String值, 不设置过期时间
     *
     * @param key   键
     * @param value 值
     */
    public void setString(String key, String value) {
        set(key, value, null);
    }

    /**
     * 添加String值, 并设置过期时间
     *
     * @param key     键
     * @param value   值
     * @param timeOut 时长(秒)
     */
    public void setString(String key, String value, Long timeOut) {
        set(key, value, timeOut);
    }

    /**
     * 设置超时时间
     *
     * @param key     键
     * @param timeOut 时长(秒)
     */
    public void setTimeOut(String key, Long timeOut) {
        stringRedisTemplate.expire(key, timeOut, TimeUnit.SECONDS);
    }

    /**
     * 传入object对象
     *
     * @param key     键
     * @param value   值
     * @param timeOut 时长(秒)
     */
    private void set(String key, Object value, Long timeOut) {
        if (value != null) {
            if (value instanceof String) {
                String setValue = (String) value;
                stringRedisTemplate.opsForValue().set(key, setValue);
            }
            //设置有效期
            if (timeOut != null) {
                stringRedisTemplate.expire(key, timeOut, TimeUnit.SECONDS);
            }
        }
    }

    /**
     * 使用key查找redis信息
     *
     * @param key 键
     * @return
     */
    public String getString(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 使用key删除redis信息
     *
     * @param key 键
     */
    public void delete(String key) {
        stringRedisTemplate.delete(key);
    }


    /**
     * 添加地理信息
     *
     * @param point
     * @param content
     * @param type
     * @return
     */
    public Long inGeolocation(Point point, String content, String type) {
        return stringRedisTemplate.opsForGeo().add(type, point, content);
    }


    /**
     * 计算两个位置距离
     *
     * @param content
     * @param contentII
     * @param type
     * @return
     */
    public Distance computeDistance(String content, String contentII, String type) {
        return stringRedisTemplate.opsForGeo().distance(type, content, contentII, Metrics.KILOMETERS);

    }

    /**
     * 获取成员经纬度
     *
     * @param contentArray
     * @return
     */
    public List<Point> positions(String type, String... contentArray) {
        return stringRedisTemplate.opsForGeo().position(type, contentArray);
    }


    /**
     * 根据content查询附近商家
     *
     * @param content
     * @param distance 搜索半径（单位：千米）
     * @param count    获取几条
     * @param type     name or 主键
     * @return
     */
    public GeoResults<RedisGeoCommands.GeoLocation<String>> radius(String content, double distance, long count, String type) {

        return stringRedisTemplate.opsForGeo()
                .radius(type,
                        content,
                        new Distance(distance, Metrics.KILOMETERS),
                        RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                                .includeDistance()   // 包含距离信息
                                .includeCoordinates()// 包含坐标信息
                                .sortAscending()     // 按距离升序
                                .limit(count));   // 限制返回数量
    }

    /**
     * 根据坐标查询附近商家
     *
     * @param point    经度 纬度
     * @param distance 搜索半径（单位：千米）
     * @param count    最大返回数量
     * @return 附近商家地理信息结果集
     */
    public GeoResults<RedisGeoCommands.GeoLocation<String>> radius(Point point, double distance, long count, String type) {
        return stringRedisTemplate.opsForGeo()
                .radius(type,
                        new Circle(point, new Distance(distance, Metrics.KILOMETERS)),
                        RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                                .includeDistance()
                                .includeCoordinates()
                                .sortAscending()
                                .limit(count));
    }


    /**
     * 删除GEO类型中的成员
     *
     * @param type    GEO数据对应的键名(对应你代码中的type参数)
     * @param members 要删除的成员名称
     * @return 实际删除的成员数量
     */
    public Long removeGeoMember(String type, String... members) {
        return stringRedisTemplate.opsForZSet().remove(type, (Object[]) members);
    }

    /**
     * 获取分布式锁
     * @param lockKey 锁的标识
     * @return 锁的value值，用于释放锁时验证，null表示获取失败
     */
    public String lock(String lockKey) {
        return lock(lockKey, 30000, 10000);
    }

    /**
     * 获取分布式锁
     * @param lockKey 锁的标识
     * @param expireTime 锁的过期时间(毫秒)
     * @param acquireTimeout 获取锁的超时时间(毫秒)
     * @return 锁的value值，用于释放锁时验证，null表示获取失败
     */
    public String lock(String lockKey, long expireTime, long acquireTimeout) {
        // 生成唯一标识，用于释放锁时验证
        String identifier = UUID.randomUUID().toString();
        // 完整的锁键
        String lockKeyWithPrefix = "inventory:lock:" + lockKey;
        // 超时时间戳
        long endTime = System.currentTimeMillis() + acquireTimeout;

        while (System.currentTimeMillis() < endTime) {
            // 尝试获取锁：SET NX PX
            Boolean success = stringRedisTemplate.opsForValue()
                    .setIfAbsent(lockKeyWithPrefix, identifier, expireTime, TimeUnit.MILLISECONDS);

            if (Boolean.TRUE.equals(success)) {
                // 获取锁成功
                return identifier;
            }

            try {
                // 短暂休眠，避免频繁尝试
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            }
        }

        // 获取锁超时
        return null;
    }

    /**
     * 释放分布式锁
     * @param lockKey 锁的标识
     * @param identifier 锁的value值，用于验证
     * @return 是否释放成功
     */
    public boolean unlock(String lockKey, String identifier) {
        if (identifier == null) {
            return false;
        }

        String lockKeyWithPrefix = "inventory:lock:" + lockKey;

        // 使用Lua脚本确保释放锁的原子性
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) " +
                "else " +
                "return 0 " +
                "end";

        RedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
        Long result = stringRedisTemplate.execute(script,
                Collections.singletonList(lockKeyWithPrefix),
                identifier);

        return result != null && result > 0;
    }

}
