package com.ruoyi.app.order;

import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.spring.SpringUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.List;
import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class RiderDeliveryLockServiceTest {
    private static ConfigurableListableBeanFactory originalBeanFactory;

    @BeforeAll
    static void initializeMessages() {
        originalBeanFactory = (ConfigurableListableBeanFactory)
                ReflectionTestUtils.getField(SpringUtils.class, "beanFactory");
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        StaticMessageSource messageSource = new StaticMessageSource();
        messageSource.setUseCodeAsDefaultMessage(true);
        beanFactory.registerSingleton("messageSource", messageSource);
        new SpringUtils().postProcessBeanFactory(beanFactory);
    }

    @AfterAll
    static void restoreMessages() {
        new SpringUtils().postProcessBeanFactory(originalBeanFactory);
    }

    @Test
    void usesSharedRiderLockWithWatchdogAndReleasesOutsideTransaction() throws Exception {
        RedissonClient redisson = mock(RedissonClient.class);
        RLock lock = mock(RLock.class);
        when(redisson.getLock("lock:delivery:rider:123")).thenReturn(lock);
        when(lock.tryLock(3L, TimeUnit.SECONDS)).thenReturn(true);
        when(lock.isHeldByCurrentThread()).thenReturn(true);
        RiderDeliveryLockService service = new RiderDeliveryLockService(redisson);

        String result = service.withLock(123L, () -> "accepted");

        assertEquals("accepted", result);
        verify(lock).tryLock(3L, TimeUnit.SECONDS);
        verify(lock).unlock();
    }

    @Test
    void defersUnlockUntilTransactionCompletion() throws Exception {
        RedissonClient redisson = mock(RedissonClient.class);
        RLock lock = mock(RLock.class);
        when(redisson.getLock("lock:delivery:rider:123")).thenReturn(lock);
        when(lock.tryLock(3L, TimeUnit.SECONDS)).thenReturn(true);
        when(lock.isHeldByCurrentThread()).thenReturn(true);
        RiderDeliveryLockService service = new RiderDeliveryLockService(redisson);
        TransactionSynchronizationManager.initSynchronization();
        try {
            assertEquals("accepted", service.withLock(123L, () -> "accepted"));
            verify(lock, never()).unlock();
            List<TransactionSynchronization> synchronizations =
                    TransactionSynchronizationManager.getSynchronizations();
            assertEquals(1, synchronizations.size());

            synchronizations.get(0).afterCompletion(TransactionSynchronization.STATUS_COMMITTED);

            verify(lock).unlock();
        } finally {
            TransactionSynchronizationManager.clearSynchronization();
        }
    }

    @Test
    void rejectsTimeoutInterruptionAndRedisFailure() throws Exception {
        RedissonClient timeoutRedisson = mock(RedissonClient.class);
        RLock timeoutLock = mock(RLock.class);
        when(timeoutRedisson.getLock("lock:delivery:rider:123")).thenReturn(timeoutLock);
        when(timeoutLock.tryLock(3L, TimeUnit.SECONDS)).thenReturn(false);
        assertThrows(ServiceException.class,
                () -> new RiderDeliveryLockService(timeoutRedisson).withLock(123L, () -> "ignored"));

        RedissonClient interruptedRedisson = mock(RedissonClient.class);
        RLock interruptedLock = mock(RLock.class);
        when(interruptedRedisson.getLock("lock:delivery:rider:123")).thenReturn(interruptedLock);
        when(interruptedLock.tryLock(3L, TimeUnit.SECONDS)).thenThrow(new InterruptedException());
        assertThrows(ServiceException.class,
                () -> new RiderDeliveryLockService(interruptedRedisson).withLock(123L, () -> "ignored"));
        assertTrue(Thread.interrupted());

        RedissonClient failedRedisson = mock(RedissonClient.class);
        when(failedRedisson.getLock("lock:delivery:rider:123")).thenThrow(new IllegalStateException("redis down"));
        assertThrows(ServiceException.class,
                () -> new RiderDeliveryLockService(failedRedisson).withLock(123L, () -> "ignored"));
    }
}
