package com.ema.common.tools.redis.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.ema.common.tools.redis.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class RedisServiceImpl implements RedisService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public RedisTemplate<String, Object> getRredisTemplate() {
        return redisTemplate;
    }

    @Override
    public boolean set(String prefix, String key, Object value, long expire) {
        String redisKey = StrUtil.isBlank(prefix) ? key : StrUtil.builder(prefix, ":", key).toString();
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        operations.set(redisKey, value);
        if (expire > 0) {
            return Boolean.TRUE.equals(redisTemplate.expire(redisKey, expire, TimeUnit.SECONDS));
        }
        return true;
    }

    @Override
    public boolean set(String prefix, String key, Object value) {
        String redisKey = StrUtil.isBlank(prefix) ? key : StrUtil.builder(prefix, ":", key).toString();
        Long expire = redisTemplate.getExpire(redisKey, TimeUnit.SECONDS);
        if (expire != null && expire > 0) {
            redisTemplate.opsForValue().set(redisKey, value, expire, TimeUnit.SECONDS);
        }
        return true;
    }

    @Override
    public boolean exists(String prefix, String key) {
        String redisKey = StrUtil.isBlank(prefix) ? key : StrUtil.builder(prefix, ":", key).toString();
        return Boolean.TRUE.equals(redisTemplate.hasKey(redisKey));
    }

    @Override
    public void delete(String prefix, String key) {
        String redisKey = StrUtil.isBlank(prefix) ? key : StrUtil.builder(prefix, ":", key).toString();
        if (Boolean.TRUE.equals(redisTemplate.hasKey(redisKey))) {
            redisTemplate.delete(redisKey);
        }
    }

    @Override
    public void delete(Set<String> keys) {
        if (CollUtil.isNotEmpty(keys)) {
            redisTemplate.delete(keys);
        }
    }

    @Override
    public void deletePattern(String pattern) {
        Set<String> keys = redisTemplate.keys(pattern);
        if (CollUtil.isNotEmpty(keys)) {
            assert keys != null;
            redisTemplate.delete(keys);
        }
    }

    @Override
    public String getStr(String prefix, String key) {
        Object obj = getObj(prefix, key);
        return getBean(obj, String.class);
    }

    @Override
    public Object getObj(String prefix, String key) {
        String redisKey = StrUtil.isBlank(prefix) ? key : StrUtil.builder(prefix, ":", key).toString();
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        return operations.get(redisKey);
    }

    @Override
    public <T> T getBean(String prefix, String key, Class<T> clazz) {
        Object obj = getObj(prefix, key);
        return getBean(obj, clazz);
    }

    @Override
    public <T> List<T> getList(String prefix, String key, Class<T> clazz) {
        Object obj = getObj(prefix, key);
        if (obj == null) {
            return null;
        }
        return JSONUtil.toList(new JSONArray(obj), clazz);
    }

    @Async
    @Override
    public void expire(String prefix, String key, long timeout) {
        String redisKey = StrUtil.isBlank(prefix) ? key : StrUtil.builder(prefix, ":", key).toString();
        redisTemplate.expire(redisKey, timeout, TimeUnit.SECONDS);
    }

    @Override
    public Long incr(String prefix, String key) {
        String redisKey = StrUtil.isBlank(prefix) ? key : StrUtil.builder(prefix, ":", key).toString();
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        return operations.increment(redisKey);
    }

    @Override
    public Long decr(String prefix, String key) {
        String redisKey = StrUtil.isBlank(prefix) ? key : StrUtil.builder(prefix, ":", key).toString();
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        return operations.decrement(redisKey);
    }

    @Override
    public void trim(String prefix, String key, long start, long end) {
        String redisKey = StrUtil.isBlank(prefix) ? key : StrUtil.builder(prefix, ":", key).toString();
        redisTemplate.opsForList().trim(redisKey, start, end);
    }

    private <T> T getBean(Object obj, Class<T> clazz) {
        if (obj == null) {
            return null;
        }
        if (obj instanceof Number) {
            return clazz.cast(obj);
        } else if (clazz.isAssignableFrom(String.class)) {
            return clazz.cast(obj);
        }
        return BeanUtil.toBean(obj, clazz);
    }

    private static final String LOCK_PREFIX = "lock:";

    // 加锁（带UUID防误删）
    @Override
    public String tryLock(String lockKey, long expire) {
        String lockValue = UUID.randomUUID().toString();
        Boolean success = redisTemplate.opsForValue().setIfAbsent(
                LOCK_PREFIX + lockKey,
                lockValue,
                expire,
                TimeUnit.MILLISECONDS
        );
        return success != null && success ? lockValue : null;
    }

    // 解锁（Lua脚本保证原子性）
    @Override
    public boolean unlock(String lockKey, String lockValue) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "   return redis.call('del', KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";
        Long result = redisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class),
                Arrays.asList(LOCK_PREFIX + lockKey),
                lockValue
        );
        return result != null && result == 1L;
    }

    // 续期（看门狗机制）
    @Override
    public boolean renewLock(String lockKey, String lockValue, long expire) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "   return redis.call('pexpire', KEYS[1], ARGV[2]) " +
                "else " +
                "   return 0 " +
                "end";
        Long result= redisTemplate.execute(
                new DefaultRedisScript<>(script, Long.class),
                Arrays.asList(LOCK_PREFIX + lockKey),
                lockValue, expire
        ) ;
        return result!= null && result == 1L;
    }

}
