package tech.easyflow.approval.service.impl;

import com.mybatisflex.core.query.QueryWrapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
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.entity.vo.ApprovalSubmitRequest;
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.approval.service.ApprovalMatchService;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.service.SysAccountService;

import java.math.BigInteger;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * {@link ApprovalInstanceServiceImpl} 审批提交身份授权测试。
 */
@RunWith(MockitoJUnitRunner.class)
public class ApprovalInstanceServiceImplAccessTest {

    @Mock
    private ApprovalMatchService approvalMatchService;

    @Mock
    private SysAccountService sysAccountService;

    @Mock
    private ApprovalInstanceMapper approvalInstanceMapper;

    @Mock
    private ApprovalTaskMapper approvalTaskMapper;

    @Mock
    private ApprovalLogMapper approvalLogMapper;

    @Mock
    private ApprovalActionFacade approvalActionFacade;

    @InjectMocks
    private ApprovalInstanceServiceImpl service;

    /**
     * 验证同租户账号也不能代替当前登录人发起审批。
     */
    @Test
    public void submitApprovalShouldRejectForgedApplicantBeforeMatchingFlow() {
        LoginAccount loginAccount = new LoginAccount();
        loginAccount.setId(BigInteger.ONE);
        loginAccount.setTenantId(BigInteger.valueOf(42));
        ApprovalSubmitRequest request = new ApprovalSubmitRequest();
        request.setApplicantId(BigInteger.TWO);

        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(loginAccount);
            BusinessException exception = assertThrows(BusinessException.class,
                    () -> service.submitApproval(request));
            assertEquals(403, exception.getHttpStatus());
        }

        verify(sysAccountService, never()).getById(BigInteger.TWO);
        verify(approvalMatchService, never()).matchFlow(request);
    }

    /**
     * 验证申请人可撤回进行中的审批，并同步结束当前任务与恢复资源状态。
     */
    @Test
    public void revokeShouldCompleteCurrentTaskForApplicant() {
        BigInteger applicantId = BigInteger.valueOf(7);
        BigInteger tenantId = BigInteger.valueOf(42);
        BigInteger instanceId = BigInteger.valueOf(101);
        SysAccount applicant = tenantAccount(applicantId, tenantId);
        ApprovalInstance instance = activeInstance(instanceId, applicantId, tenantId);
        ApprovalTask task = new ApprovalTask();
        task.setInstanceId(instanceId);
        task.setStepNo(1);
        task.setStatus(ApprovalTaskStatus.PENDING.getCode());

        when(sysAccountService.getById(applicantId)).thenReturn(applicant);
        when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(instance);
        when(approvalTaskMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(task);

        service.revoke(instanceId, "内容需要调整", applicantId);

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

    /**
     * 验证非申请人即使属于同一租户也不能撤回审批。
     */
    @Test
    public void revokeShouldRejectSameTenantNonApplicant() {
        BigInteger applicantId = BigInteger.valueOf(7);
        BigInteger operatorId = BigInteger.valueOf(8);
        BigInteger tenantId = BigInteger.valueOf(42);
        BigInteger instanceId = BigInteger.valueOf(101);

        when(sysAccountService.getById(operatorId)).thenReturn(tenantAccount(operatorId, tenantId));
        when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class)))
                .thenReturn(activeInstance(instanceId, applicantId, tenantId));

        BusinessException exception = assertThrows(
                BusinessException.class,
                () -> service.revoke(instanceId, "尝试撤回", 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));
    }

    private ApprovalInstance activeInstance(BigInteger instanceId, BigInteger applicantId, BigInteger tenantId) {
        ApprovalInstance instance = new ApprovalInstance();
        instance.setId(instanceId);
        instance.setApplicantId(applicantId);
        instance.setTenantId(tenantId);
        instance.setCurrentStepNo(1);
        instance.setStatus(ApprovalInstanceStatus.PENDING.getCode());
        return instance;
    }

    private SysAccount tenantAccount(BigInteger accountId, BigInteger tenantId) {
        SysAccount account = new SysAccount();
        account.setId(accountId);
        account.setTenantId(tenantId);
        return account;
    }
}
