package tech.easyflow.agent.support;

import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import tech.easyflow.common.cache.RedisLockExecutor;

import java.math.BigInteger;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * {@link AgentBindingLockExecutor} 事务锁生命周期测试。
 */
public class AgentBindingLockExecutorTest {

    /**
     * 验证活动事务内的绑定锁延迟到事务完成后释放。
     */
    @Test
    public void executeShouldReleaseLockAfterTransactionCompletion() {
        RedisLockExecutor redisLockExecutor = Mockito.mock(RedisLockExecutor.class);
        RedisLockExecutor.LockHandle lockHandle = Mockito.mock(RedisLockExecutor.LockHandle.class);
        ScheduledExecutorService renewExecutor = Mockito.mock(ScheduledExecutorService.class);
        ScheduledFuture<?> renewTask = Mockito.mock(ScheduledFuture.class);
        Mockito.when(redisLockExecutor.acquire(
                Mockito.anyString(),
                Mockito.any(Duration.class),
                Mockito.any(Duration.class)
        )).thenReturn(lockHandle);
        Mockito.doReturn(renewTask).when(renewExecutor).scheduleWithFixedDelay(
                Mockito.any(Runnable.class),
                Mockito.anyLong(),
                Mockito.anyLong(),
                Mockito.eq(TimeUnit.MILLISECONDS)
        );
        Mockito.when(lockHandle.renew()).thenReturn(true);
        AgentBindingLockExecutor executor = new AgentBindingLockExecutor(redisLockExecutor, renewExecutor);
        TransactionSynchronizationManager.initSynchronization();
        TransactionSynchronizationManager.setActualTransactionActive(true);
        try {
            String result = executor.execute(BigInteger.ONE, () -> "ok");

            Assert.assertEquals("ok", result);
            Mockito.verify(lockHandle, Mockito.never()).release();
            Mockito.verify(renewTask, Mockito.never()).cancel(false);
            ArgumentCaptor<Runnable> renewCaptor = ArgumentCaptor.forClass(Runnable.class);
            Mockito.verify(renewExecutor).scheduleWithFixedDelay(
                    renewCaptor.capture(),
                    Mockito.anyLong(),
                    Mockito.anyLong(),
                    Mockito.eq(TimeUnit.MILLISECONDS)
            );
            renewCaptor.getValue().run();
            Mockito.verify(lockHandle).renew();
            List<TransactionSynchronization> synchronizations =
                    TransactionSynchronizationManager.getSynchronizations();
            Assert.assertEquals(1, synchronizations.size());
            synchronizations.get(0).afterCompletion(TransactionSynchronization.STATUS_COMMITTED);
            Mockito.verify(renewTask).cancel(false);
            Mockito.verify(lockHandle).release();
        } finally {
            TransactionSynchronizationManager.setActualTransactionActive(false);
            TransactionSynchronizationManager.clearSynchronization();
            executor.destroy();
        }
        Mockito.verify(renewExecutor).shutdownNow();
    }

    /**
     * 同一事务内重复进入相同 Agent 锁时只能获取一次 Redis 锁。
     */
    @Test
    public void executeShouldReuseSameAgentLockWithinTransaction() {
        RedisLockExecutor redisLockExecutor = Mockito.mock(RedisLockExecutor.class);
        RedisLockExecutor.LockHandle lockHandle = Mockito.mock(RedisLockExecutor.LockHandle.class);
        ScheduledExecutorService renewExecutor = Mockito.mock(ScheduledExecutorService.class);
        ScheduledFuture<?> renewTask = Mockito.mock(ScheduledFuture.class);
        Mockito.when(redisLockExecutor.acquire(
                Mockito.anyString(),
                Mockito.any(Duration.class),
                Mockito.any(Duration.class)
        )).thenReturn(lockHandle);
        Mockito.doReturn(renewTask).when(renewExecutor).scheduleWithFixedDelay(
                Mockito.any(Runnable.class),
                Mockito.anyLong(),
                Mockito.anyLong(),
                Mockito.eq(TimeUnit.MILLISECONDS)
        );
        AgentBindingLockExecutor executor = new AgentBindingLockExecutor(redisLockExecutor, renewExecutor);
        TransactionSynchronizationManager.initSynchronization();
        TransactionSynchronizationManager.setActualTransactionActive(true);
        try {
            String result = executor.execute(
                    BigInteger.ONE,
                    () -> executor.execute(BigInteger.ONE, () -> "nested")
            );

            Assert.assertEquals("nested", result);
            Mockito.verify(redisLockExecutor, Mockito.times(1)).acquire(
                    Mockito.anyString(),
                    Mockito.any(Duration.class),
                    Mockito.any(Duration.class)
            );
            List<TransactionSynchronization> synchronizations =
                    TransactionSynchronizationManager.getSynchronizations();
            Assert.assertEquals(1, synchronizations.size());
            synchronizations.get(0).afterCompletion(TransactionSynchronization.STATUS_COMMITTED);
        } finally {
            TransactionSynchronizationManager.unbindResourceIfPossible(
                    "easyflow:lock:agent:binding:" + BigInteger.ONE
            );
            TransactionSynchronizationManager.setActualTransactionActive(false);
            TransactionSynchronizationManager.clearSynchronization();
            executor.destroy();
        }
    }

    /**
     * 锁续期失败后事务提交必须被阻止。
     */
    @Test
    public void renewalFailureShouldPreventTransactionCommit() {
        RedisLockExecutor redisLockExecutor = Mockito.mock(RedisLockExecutor.class);
        RedisLockExecutor.LockHandle lockHandle = Mockito.mock(RedisLockExecutor.LockHandle.class);
        ScheduledExecutorService renewExecutor = Mockito.mock(ScheduledExecutorService.class);
        ScheduledFuture<?> renewTask = Mockito.mock(ScheduledFuture.class);
        Mockito.when(redisLockExecutor.acquire(
                Mockito.anyString(),
                Mockito.any(Duration.class),
                Mockito.any(Duration.class)
        )).thenReturn(lockHandle);
        ArgumentCaptor<Runnable> renewCaptor = ArgumentCaptor.forClass(Runnable.class);
        Mockito.doReturn(renewTask).when(renewExecutor).scheduleWithFixedDelay(
                renewCaptor.capture(),
                Mockito.anyLong(),
                Mockito.anyLong(),
                Mockito.eq(TimeUnit.MILLISECONDS)
        );
        Mockito.when(lockHandle.renew()).thenReturn(false);
        AgentBindingLockExecutor executor = new AgentBindingLockExecutor(redisLockExecutor, renewExecutor);
        TransactionSynchronizationManager.initSynchronization();
        TransactionSynchronizationManager.setActualTransactionActive(true);
        try {
            executor.execute(BigInteger.ONE, () -> "ok");
            renewCaptor.getValue().run();
            TransactionSynchronization synchronization =
                    TransactionSynchronizationManager.getSynchronizations().get(0);

            IllegalStateException exception = Assert.assertThrows(
                    IllegalStateException.class,
                    () -> synchronization.beforeCommit(false)
            );

            Assert.assertEquals("Agent 绑定锁已失效，事务禁止提交", exception.getMessage());
            synchronization.afterCompletion(TransactionSynchronization.STATUS_ROLLED_BACK);
        } finally {
            TransactionSynchronizationManager.unbindResourceIfPossible(
                    "easyflow:lock:agent:binding:" + BigInteger.ONE
            );
            TransactionSynchronizationManager.setActualTransactionActive(false);
            TransactionSynchronizationManager.clearSynchronization();
            executor.destroy();
        }
    }
}
