package tech.easyflow.approval.service.impl;

import com.mybatisflex.core.query.QueryWrapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import tech.easyflow.approval.entity.ApprovalInstance;
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
import tech.easyflow.approval.mapper.ApprovalInstanceMapper;
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.service.SysAccountService;

import java.math.BigInteger;
import java.util.Locale;

import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * {@link ApprovalInstanceServiceImpl} 审批决策并发互斥测试。
 */
@RunWith(MockitoJUnitRunner.class)
public class ApprovalInstanceServiceImplConcurrencyTest {

    @Mock
    private ApprovalInstanceMapper approvalInstanceMapper;
    @Mock
    private ApprovalTaskMapper approvalTaskMapper;
    @Mock
    private SysAccountService sysAccountService;
    @InjectMocks
    private ApprovalInstanceServiceImpl service;

    /**
     * 审批实例与当前任务必须在状态判断前加行锁，避免重复执行同一决策。
     */
    @Test
    public void approvalDecisionLocksInstanceAndCurrentTask() {
        BigInteger instanceId = BigInteger.valueOf(101);
        BigInteger tenantId = BigInteger.valueOf(42);
        ApprovalInstance instance = new ApprovalInstance();
        instance.setId(instanceId);
        instance.setTenantId(tenantId);
        instance.setStatus(ApprovalInstanceStatus.PENDING.getCode());
        instance.setCurrentStepNo(1);
        SysAccount operator = new SysAccount();
        operator.setId(BigInteger.ONE);
        operator.setTenantId(tenantId);
        when(sysAccountService.getById(BigInteger.ONE)).thenReturn(operator);
        when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(instance);
        when(approvalTaskMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(null);

        assertThrows(BusinessException.class,
                () -> service.approve(instanceId, "通过", BigInteger.ONE));

        ArgumentCaptor<QueryWrapper> instanceQuery = ArgumentCaptor.forClass(QueryWrapper.class);
        ArgumentCaptor<QueryWrapper> taskQuery = ArgumentCaptor.forClass(QueryWrapper.class);
        verify(approvalInstanceMapper).selectOneByQuery(instanceQuery.capture());
        verify(approvalTaskMapper).selectOneByQuery(taskQuery.capture());
        assertTrue(instanceQuery.getValue().toSQL().toLowerCase(Locale.ROOT).contains("for update"));
        assertTrue(instanceQuery.getValue().toSQL().toLowerCase(Locale.ROOT).contains("tenant_id"));
        assertTrue(taskQuery.getValue().toSQL().toLowerCase(Locale.ROOT).contains("for update"));
    }
}
