package com.yunfeiyun.agmp.common.aspect;

import com.yunfeiyun.agmp.common.exception.BaseException;
import com.yunfeiyun.agmp.common.annotation.RedisLock;
import com.yunfeiyun.agmp.common.enums.RedisCacheKey;
import com.yunfeiyun.agmp.common.framework.manager.RedisCacheManager;
import com.yunfeiyun.agmp.common.utils.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.*;

@Component
@Aspect
public class RedisLockAspect {
    private static final Logger log = LoggerFactory.getLogger(RedisLockAspect.class);
    @Resource
    private RedisCacheManager redisCacheManager;


    
    @Around(value = "@annotation(com.yunfeiyun.agmp.common.annotation.RedisLock)")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        RedisLock redisLock = signature.getMethod().getAnnotation(RedisLock.class);
        String value =redisLock.value();
        if(StringUtils.isEmpty(value)){
            value = UUID.randomUUID().toString();
        }
        try {
            if(!lock(redisLock.key().getRedisCacheKey(),"", value,redisLock.expireTime(),redisLock.acquireTime(),redisLock.sleepTime(),redisLock.stopWhenFail())){
                throw new BaseException(redisLock.errorCode());
            }
            return joinPoint.proceed();
        } finally {
            unlock(redisLock.key().getRedisCacheKey(),"",value);
        }
    }

    /**
     * 获取分布式锁
     *
     * @param key          锁名称
     * @param value        锁定的值
     * @param expireTime   锁过期时间（单位：毫秒）
     * @param acquireTime  尝试获取锁的超时时间（单位：毫秒）
     * @param sleepTime    每次尝试获取锁的间隔时间（单位：毫秒）
     * @param stopWhenFail 是否在获取锁失败后立即退出尝试
     * @return 是否获取成功
     */
    public boolean lock(RedisCacheKey redisCacheKey,String keyId, String value, long expireTime, long acquireTime, long sleepTime, boolean stopWhenFail) {

        String code = UUID.randomUUID().toString();
        try {
            log.info("尝试获取锁，key:{},Code:{}",redisCacheKey.formatter(keyId),code);
            long start = System.currentTimeMillis();

            while (true){
                // 使用 SETNX 命令设置锁
                Boolean success = redisCacheManager.setIfAbsent(redisCacheKey,keyId, value, Duration.ofMillis(expireTime));
                if(null!=success&&success){
                    log.info("获取锁成功,Code:{}",code);
                    return true;
                }else if(stopWhenFail){
                    log.info("获取锁失败,Code:{}",code);
                    return false;
                }
                if(System.currentTimeMillis()-start>acquireTime){
                    log.info("获取锁超时,Code:{}",code);
                    return false;
                }
                Thread.currentThread().join(sleepTime);
            }
        } catch (Exception e) {
            log.error("获取锁异常,Code:"+code, e);
            return false;
        }
    }

    /**
     * 释放分布式锁
     *
     * @param key   锁名称
     * @return 是否释放成功
     */
    public boolean unlock(RedisCacheKey key,String keyId,String value) {
        try {
            // 使用 Lua 脚本删除锁，避免误删其他线程的锁
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
            Map<RedisCacheKey, Collection<String>> redisCacheKeyListMap = new HashMap<>();
            redisCacheKeyListMap.put(key,Collections.singletonList(keyId));
            Object result = redisCacheManager.execute(redisScript, redisCacheKeyListMap, value);
            if(result != null && ((Long) result).intValue() == 1){
                log.info("释放锁成功");
                return true;
            }else {
                log.error("释放锁失败");
                return false;
            }
        } catch (Exception e) {
            log.error("释放分布式锁失败", e);
            return false;
        }
    }
}
