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.entity.ApprovalLog;
import tech.easyflow.approval.entity.ApprovalTask;
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
import tech.easyflow.approval.enums.ApprovalTaskStatus;
import tech.easyflow.approval.mapper.ApprovalInstanceMapper;
import tech.easyflow.approval.mapper.ApprovalLogMapper;
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
import tech.easyflow.approval.service.ApprovalActionFacade;
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.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * 审批发起人撤回行为测试。
 */
@RunWith(MockitoJUnitRunner.class)
public class ApprovalRevokeServiceTest {

    private static final BigInteger APPLICANT_ID = BigInteger.valueOf(7);
    private static final BigInteger INSTANCE_ID = BigInteger.valueOf(101);
    private static final BigInteger TENANT_ID = BigInteger.valueOf(42);

    @Mock
    private ApprovalInstanceMapper approvalInstanceMapper;

    @Mock
    private ApprovalTaskMapper approvalTaskMapper;

    @Mock
    private ApprovalLogMapper approvalLogMapper;

    @Mock
    private ApprovalActionFacade approvalActionFacade;

    @Mock
    private SysAccountService sysAccountService;

    @InjectMocks
    private ApprovalInstanceServiceImpl service;

    /**
     * 验证发起人撤回后结束当前任务、记录日志并恢复资源状态。
     */
    @Test
    public void revokeShouldCompleteCurrentTaskForApplicant() {
        ApprovalInstance instance = activeInstance(APPLICANT_ID);
        ApprovalTask task = pendingTask();
        lenient().when(sysAccountService.getById(APPLICANT_ID)).thenReturn(tenantAccount(APPLICANT_ID));
        when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(instance);
        when(approvalTaskMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(task);

        service.revoke(INSTANCE_ID, "内容需要调整", APPLICANT_ID);

        assertEquals(ApprovalInstanceStatus.REVOKED.getCode(), instance.getStatus());
        assertNotNull(instance.getFinishedAt());
        assertEquals(ApprovalTaskStatus.REVOKED.getCode(), task.getStatus());
        assertEquals(APPLICANT_ID, task.getActedBy());
        assertEquals("内容需要调整", task.getComment());
        verify(approvalLogMapper).insert(any(ApprovalLog.class));
        verify(approvalActionFacade).handleRevoked(instance, APPLICANT_ID, "内容需要调整");

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

    /**
     * 验证当前审批人或其他同租户账号不能代替发起人撤回。
     */
    @Test
    public void revokeShouldRejectNonApplicant() {
        BigInteger operatorId = BigInteger.valueOf(8);
        lenient().when(sysAccountService.getById(operatorId)).thenReturn(tenantAccount(operatorId));
        when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class)))
                .thenReturn(activeInstance(APPLICANT_ID));

        BusinessException exception = assertThrows(
                BusinessException.class,
                () -> service.revoke(INSTANCE_ID, "尝试撤回", operatorId)
        );

        assertEquals(403, exception.getHttpStatus());
        verify(approvalTaskMapper, never()).selectOneByQuery(any(QueryWrapper.class));
        verify(approvalActionFacade, never())
                .handleRevoked(any(ApprovalInstance.class), any(BigInteger.class), any(String.class));
    }

    /**
     * 构造活动审批实例。
     *
     * @param applicantId 申请人 ID
     * @return 活动审批实例
     */
    private ApprovalInstance activeInstance(BigInteger applicantId) {
        ApprovalInstance instance = new ApprovalInstance();
        instance.setId(INSTANCE_ID);
        instance.setTenantId(TENANT_ID);
        instance.setApplicantId(applicantId);
        instance.setCurrentStepNo(1);
        instance.setStatus(ApprovalInstanceStatus.PENDING.getCode());
        return instance;
    }

    /**
     * 构造待处理任务。
     *
     * @return 待处理任务
     */
    private ApprovalTask pendingTask() {
        ApprovalTask task = new ApprovalTask();
        task.setInstanceId(INSTANCE_ID);
        task.setStepNo(1);
        task.setStatus(ApprovalTaskStatus.PENDING.getCode());
        return task;
    }

    /**
     * 构造具有租户归属的账号。
     *
     * @param accountId 账号 ID
     * @return 账号实体
     */
    private SysAccount tenantAccount(BigInteger accountId) {
        SysAccount account = new SysAccount();
        account.setId(accountId);
        account.setTenantId(TENANT_ID);
        return account;
    }
}
