package tech.easyflow.common.cache;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 定时任务分布式锁切面。
 */
@Aspect
@Component
public class DistributedScheduledLockAspect {

    private static final Logger LOG = LoggerFactory.getLogger(DistributedScheduledLockAspect.class);

    private final RedisLockExecutor redisLockExecutor;
    private final ScheduledExecutorService renewExecutor;

    /**
     * 创建定时任务分布式锁切面。
     *
     * @param redisLockExecutor Redis 分布式锁执行器
     */
    public DistributedScheduledLockAspect(RedisLockExecutor redisLockExecutor) {
        this.redisLockExecutor = redisLockExecutor;
        this.renewExecutor = Executors.newScheduledThreadPool(
            1,
            new DistributedScheduledLockThreadFactory()
        );
    }

    /**
     * 拦截带分布式调度锁的定时任务。
     *
     * @param joinPoint 切点
     * @param lock 锁注解
     * @return 原方法返回值；未抢到锁时返回 null
     * @throws Throwable 原方法执行异常或 Redis 访问异常
     */
    @Around("@annotation(lock)")
    public Object around(ProceedingJoinPoint joinPoint, DistributedScheduledLock lock) throws Throwable {
        Duration waitTimeout = Duration.ofSeconds(Math.max(lock.waitSeconds(), 0L));
        Duration leaseTimeout = Duration.ofSeconds(Math.max(lock.leaseSeconds(), 1L));
        RedisLockExecutor.LockHandle handle = redisLockExecutor.tryAcquire(lock.key(), waitTimeout, leaseTimeout);
        if (handle == null) {
            LOG.info("定时任务分布式锁已被其他实例持有，跳过本轮执行: lockKey={}, method={}",
                lock.key(), joinPoint.getSignature().toShortString());
            return null;
        }
        ScheduledFuture<?> renewTask = scheduleRenew(lock.key(), handle, leaseTimeout);
        try {
            return joinPoint.proceed();
        } finally {
            renewTask.cancel(false);
            handle.release();
        }
    }

    private ScheduledFuture<?> scheduleRenew(String lockKey,
                                             RedisLockExecutor.LockHandle handle,
                                             Duration leaseTimeout) {
        long renewIntervalMillis = Math.max(leaseTimeout.toMillis() / 3L, 1000L);
        return renewExecutor.scheduleWithFixedDelay(() -> {
            if (!handle.renew()) {
                LOG.warn("定时任务分布式锁续期失败: lockKey={}", lockKey);
            }
        }, renewIntervalMillis, renewIntervalMillis, TimeUnit.MILLISECONDS);
    }

    /**
     * 关闭调度锁续期线程池。
     */
    @PreDestroy
    public void destroy() {
        renewExecutor.shutdownNow();
    }

    /**
     * 调度锁续期线程工厂。
     */
    private static final class DistributedScheduledLockThreadFactory implements ThreadFactory {

        private final AtomicInteger index = new AtomicInteger(1);

        /**
         * 创建续期线程。
         *
         * @param runnable 线程任务
         * @return 续期线程
         */
        @Override
        public Thread newThread(Runnable runnable) {
            Thread thread = new Thread(runnable);
            thread.setName("distributed-scheduled-lock-renew-" + index.getAndIncrement());
            thread.setDaemon(true);
            return thread;
        }
    }
}
