package tech.easyflow.common.cache;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * {@link DistributedScheduledLockAspect} 回归测试。
 */
public class DistributedScheduledLockAspectTest {

    /**
     * 验证未抢到调度锁时跳过原方法。
     *
     * @throws Throwable 切面执行异常
     */
    @Test
    public void aroundShouldSkipTaskWhenLockIsHeld() throws Throwable {
        RedisLockExecutor executor = createExecutor(false);
        DistributedScheduledLockAspect aspect = new DistributedScheduledLockAspect(executor);
        AtomicInteger proceedCount = new AtomicInteger();

        Object result = aspect.around(
            mockJoinPoint(proceedCount),
            annotatedMethod("lockedTask").getAnnotation(DistributedScheduledLock.class)
        );

        Assert.assertNull(result);
        Assert.assertEquals(0, proceedCount.get());
    }

    /**
     * 验证抢到调度锁时执行原方法并释放锁。
     *
     * @throws Throwable 切面执行异常
     */
    @Test
    public void aroundShouldProceedAndReleaseWhenLockAcquired() throws Throwable {
        RedisLockExecutor executor = createExecutor(true);
        DistributedScheduledLockAspect aspect = new DistributedScheduledLockAspect(executor);
        AtomicInteger proceedCount = new AtomicInteger();

        Object result = aspect.around(
            mockJoinPoint(proceedCount),
            annotatedMethod("lockedTask").getAnnotation(DistributedScheduledLock.class)
        );

        Assert.assertEquals("ok", result);
        Assert.assertEquals(1, proceedCount.get());
    }

    @DistributedScheduledLock(key = "easyflow:test:scheduled", leaseSeconds = 30L)
    private void lockedTask() {
    }

    private Method annotatedMethod(String methodName) throws NoSuchMethodException {
        Method method = DistributedScheduledLockAspectTest.class.getDeclaredMethod(methodName);
        method.setAccessible(true);
        return method;
    }

    private ProceedingJoinPoint mockJoinPoint(AtomicInteger proceedCount) throws Throwable {
        ProceedingJoinPoint joinPoint = Mockito.mock(ProceedingJoinPoint.class);
        Signature signature = Mockito.mock(Signature.class);
        Mockito.when(signature.toShortString()).thenReturn("lockedTask()");
        Mockito.when(joinPoint.getSignature()).thenReturn(signature);
        Mockito.when(joinPoint.proceed()).thenAnswer(invocation -> {
            proceedCount.incrementAndGet();
            return "ok";
        });
        return joinPoint;
    }

    @SuppressWarnings("unchecked")
    private RedisLockExecutor createExecutor(boolean acquired) throws Exception {
        StringRedisTemplate redisTemplate = Mockito.mock(StringRedisTemplate.class);
        ValueOperations<String, String> valueOperations = Mockito.mock(ValueOperations.class);
        Mockito.when(valueOperations.setIfAbsent(
            ArgumentMatchers.anyString(),
            ArgumentMatchers.anyString(),
            ArgumentMatchers.any(Duration.class)
        )).thenReturn(acquired);
        Mockito.when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        Mockito.when(redisTemplate.execute(
            ArgumentMatchers.<RedisScript<Long>>any(),
            ArgumentMatchers.<List<String>>any(),
            ArgumentMatchers.<Object[]>any()
        )).thenReturn(1L);

        RedisLockExecutor executor = new RedisLockExecutor();
        Field field = RedisLockExecutor.class.getDeclaredField("stringRedisTemplate");
        field.setAccessible(true);
        field.set(executor, redisTemplate);
        return executor;
    }
}
