package cn.ezhizao.framework.aspectj.lang;

import cn.ezhizao.common.utils.ELUtils;
import cn.ezhizao.common.utils.SecurityUtils;
import cn.ezhizao.framework.aspectj.lang.annotation.distributedLock;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Component
@Aspect
@Log4j2
@ConditionalOnProperty(name = "cache.type", havingValue = "redis", matchIfMissing = true)
public class redisLockAspect {
    @Autowired
    private RedissonClient redissonClient;
    private final ExpressionParser parser = new SpelExpressionParser();
    private final ConcurrentHashMap<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
    @Autowired
    private ApplicationContext context;
    @Around("@annotation(distributedLock)")
    public Object around(ProceedingJoinPoint joinPoint, distributedLock distributedLock) throws Throwable {
        return distributed(joinPoint, distributedLock);
    }
    private Object distributed(ProceedingJoinPoint joinPoint, distributedLock distributedLock) throws Throwable {
        // 获取 SpEL 表达式的 key 值
        // 解析 prefix 和 key
        String prefix = ELUtils.parseExpression(distributedLock.prefix(), joinPoint);
        String key = distributedLock.key();
        if(distributedLock.key().startsWith("@")){
//            Object bean = context.getBean(key.substring(1,key.indexOf(".")));
//            Object o = SpringUtils.getBean(key.substring(1,key.indexOf(".")));
//            Method method = bean.getClass().getMethod(key.substring(key.indexOf(".")+1,key.indexOf("(")));需要参数列表放弃
//            method.invoke(MethodSignatureMatcher.splitParameters(key));
            if(key.contains("tenantId")){
                key = String.valueOf(SecurityUtils.getTenantId());
            }
        }else if(distributedLock.key().startsWith("#")){
           key = ELUtils.parseExpression(distributedLock.key(), joinPoint);
        }

        String lockKey = prefix + key; // 组合成完整的锁 key
        long leaseTime = distributedLock.leaseTime();
        TimeUnit timeUnit = distributedLock.leaseTimeUnit();
        RLock lock = redissonClient.getLock(lockKey);

        log.info("start lock {}", lockKey);
        try {
            // 尝试获取锁
            if(lock.isLocked()){
                throw new RuntimeException(distributedLock.errorMsg());
            }else{
                if(lock.tryLock(distributedLock.waitTime(), leaseTime, timeUnit)){
                    return joinPoint.proceed();
                }else{
                    throw new RuntimeException("加锁失败");
                }
            }
        } finally {
            if(lock.isHeldByCurrentThread()){
                log.info("end lock {}", lockKey);
                // 只有是当前线程才去释放锁
                lock.unlock();
            }
        }
    }
    private Object alone(ProceedingJoinPoint joinPoint, distributedLock distributedLock) throws Throwable {
        // 解析 prefix 和 key
        String prefix = ELUtils.parseExpression(distributedLock.prefix(), joinPoint);
        String key = ELUtils.parseExpression(distributedLock.key(), joinPoint);
        String lockKey = prefix + key; // 组合成完整的锁 key
        // 获取 leaseTime
        long leaseTime = distributedLock.leaseTime();
        TimeUnit timeUnit = distributedLock.leaseTimeUnit();

        // 从 map 中获取锁
        ReentrantLock lock = lockMap.computeIfAbsent(lockKey, k -> new ReentrantLock());
        boolean acquired = false;

        log.info("Attempting to lock {}", lockKey);
        try {
            // 尝试获取锁，设置等待时间
            acquired = lock.tryLock(leaseTime, timeUnit);
            if (acquired) {
                log.info("Lock acquired: {}", lockKey);
                // 获取到锁，执行方法
                return joinPoint.proceed();
            } else {
                log.warn("Unable to acquire lock for key: {}", lockKey);
                throw new RuntimeException("Unable to acquire lock for key: " + lockKey);
            }
        } finally {
            if (acquired) {
                lock.unlock();
                log.info("Lock released: {}", lockKey);
                // 锁释放后，移除锁对象，避免内存泄漏
                lockMap.remove(lockKey);
            }
        }
    }
}
