package tech.easyflow.approval.service.impl;

import com.mybatisflex.core.paginate.Page;
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.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.ApprovalInstanceDetailVo;
import tech.easyflow.approval.entity.vo.ApprovalInstancePageVo;
import tech.easyflow.approval.enums.ApprovalAssigneeType;
import tech.easyflow.approval.enums.ApprovalEventType;
import tech.easyflow.approval.enums.ApprovalInstanceStatus;
import tech.easyflow.approval.enums.ApprovalTaskStatus;
import tech.easyflow.approval.mapper.ApprovalFlowStepMapper;
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.ApprovalAssigneeService;
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.CategoryPermissionService;
import tech.easyflow.system.service.SysAccountService;

import java.math.BigInteger;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.mockStatic;

/**
 * {@link ApprovalQueryServiceImpl} 审批详情租户和主体授权回归测试。
 */
@RunWith(MockitoJUnitRunner.class)
public class ApprovalQueryServiceImplAccessTest {

    private static final BigInteger INSTANCE_ID = BigInteger.valueOf(101);
    private static final BigInteger RESOURCE_ID = BigInteger.valueOf(501);
    private static final BigInteger TENANT_ID = BigInteger.valueOf(42);

    @Mock
    private ApprovalInstanceMapper approvalInstanceMapper;
    @Mock
    private ApprovalTaskMapper approvalTaskMapper;
    @Mock
    private ApprovalLogMapper approvalLogMapper;
    @Mock
    private ApprovalFlowStepMapper approvalFlowStepMapper;
    @Mock
    private ApprovalAssigneeService approvalAssigneeService;
    @Mock
    private ApprovalActionFacade approvalActionFacade;
    @Mock
    private CategoryPermissionService categoryPermissionService;
    @Mock
    private SysAccountService sysAccountService;
    @InjectMocks
    private ApprovalQueryServiceImpl service;

    /**
     * 验证详情查询显式带租户条件，并拒绝 Mapper 异常返回的跨租户实例。
     */
    @Test
    public void detailShouldRejectCrossTenantInstanceBeforeReadingSnapshot() {
        LoginAccount account = account(7, 42);
        ApprovalInstance instance = instance(99, 99);
        when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(instance);

        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
            BusinessException exception = assertThrows(BusinessException.class,
                    () -> service.detail(INSTANCE_ID));
            assertEquals(404, exception.getHttpStatus());
        }

        ArgumentCaptor<QueryWrapper> query = ArgumentCaptor.forClass(QueryWrapper.class);
        verify(approvalInstanceMapper).selectOneByQuery(query.capture());
        assertTrue(query.getValue().toSQL().toLowerCase(Locale.ROOT).contains("tenant_id"));
        verify(approvalTaskMapper, never()).selectListByQuery(any(QueryWrapper.class));
        verify(approvalLogMapper, never()).selectListByQuery(any(QueryWrapper.class));
    }

    /**
     * 验证同租户普通用户不能仅凭审批查询操作权限读取完整资源快照。
     */
    @Test
    public void detailShouldRejectSameTenantNonParticipant() {
        LoginAccount account = account(7, 42);
        ApprovalInstance instance = instance(8, 42);
        when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(instance);
        when(approvalTaskMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of());
        when(approvalAssigneeService.getAvailableRoleIds(account.getId())).thenReturn(Set.of());
        when(approvalActionFacade.canAccessApprovalDetail("SKILL", RESOURCE_ID)).thenReturn(false);

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

        verify(approvalLogMapper, never()).selectListByQuery(any(QueryWrapper.class));
    }

    /**
     * 验证申请人仍可读取自己发起的审批快照。
     */
    @Test
    public void detailShouldAllowApplicant() {
        LoginAccount account = account(7, 42);
        ApprovalInstance instance = instance(7, 42);
        Map<String, Object> snapshot = instance.getSnapshotJson();
        stubAuthorizedDetail(instance, account, List.of());

        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
            ApprovalInstanceDetailVo detail = service.detail(INSTANCE_ID);
            assertNotSame(snapshot, detail.getSnapshotJson());
            assertEquals(Map.of(), detail.getSnapshotJson().get("resourceSnapshot"));
            assertTrue(detail.getSnapshotJson().containsKey("steps"));
            assertFalse(detail.isCanApprove());
            assertFalse(detail.isCanReject());
            assertTrue(detail.isCanRevoke());
        }
    }

    /**
     * 验证当前待办处理人可查看审批详情。
     */
    @Test
    public void detailShouldAllowCurrentTaskHandler() {
        LoginAccount account = account(7, 42);
        ApprovalInstance instance = instance(8, 42);
        ApprovalTask task = task(ApprovalTaskStatus.PENDING.getCode(), null);
        task.setAssigneeType(ApprovalAssigneeType.USER.getCode());
        task.setAssigneeTargetId(account.getId());
        stubAuthorizedDetail(instance, account, List.of(task));
        when(approvalAssigneeService.canHandleTask(task, account.getId(), Set.of())).thenReturn(true);

        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
            ApprovalInstanceDetailVo detail = service.detail(INSTANCE_ID);
            assertEquals(INSTANCE_ID, detail.getId());
            assertTrue(detail.isCanApprove());
            assertTrue(detail.isCanReject());
            assertFalse(detail.isCanRevoke());
        }
    }

    /**
     * 验证实际处理过历史步骤的用户仍可查看审批详情。
     */
    @Test
    public void detailShouldAllowHistoricalActor() {
        LoginAccount account = account(7, 42);
        ApprovalInstance instance = instance(8, 42);
        ApprovalTask task = task(ApprovalTaskStatus.APPROVED.getCode(), account.getId());
        stubAuthorizedDetail(instance, account, List.of(task));

        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
            assertEquals(INSTANCE_ID, service.detail(INSTANCE_ID).getId());
        }
    }

    /**
     * 验证审批说明在详情、审批任务和提交日志中完整透传。
     */
    @Test
    public void detailShouldExposeApplicationReasonAcrossRelatedViews() {
        LoginAccount account = account(7, 42);
        ApprovalInstance instance = instance(7, 42);
        instance.setApplicationReason("发布新的问答流程");
        ApprovalTask task = task(ApprovalTaskStatus.PENDING.getCode(), null);
        ApprovalLog log = new ApprovalLog();
        log.setEventType(ApprovalEventType.SUBMITTED.getCode());
        stubAuthorizedDetail(instance, account, List.of(task));
        when(approvalLogMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of(log));

        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
            ApprovalInstanceDetailVo detail = service.detail(INSTANCE_ID);
            assertEquals("发布新的问答流程", detail.getApplicationReason());
            assertEquals("发布新的问答流程", detail.getTasks().get(0).getApplicationReason());
            assertEquals("发布新的问答流程", detail.getLogs().get(0).getApplicationReason());
        }
    }

    /**
     * 验证审批分页列表返回申请人填写的审批说明和账号信息。
     */
    @Test
    public void initiatedPageShouldExposeApplicationReasonAndApplicant() {
        LoginAccount account = account(7, 42);
        ApprovalInstance instance = instance(7, 42);
        instance.setApplicationReason("发布新的问答流程");
        SysAccount applicant = new SysAccount();
        applicant.setId(account.getId());
        applicant.setNickname("陈子默");
        applicant.setLoginName("czm");
        Page<ApprovalInstance> page = new Page<>(List.of(instance), 1L, 10L, 1L);
        when(approvalInstanceMapper.paginate(anyLong(), anyLong(), any(QueryWrapper.class))).thenReturn(page);
        when(sysAccountService.list(any(QueryWrapper.class))).thenReturn(List.of(applicant));

        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
            ApprovalInstancePageVo item = service.initiatedPage(null, null, null, 1L, 10L)
                    .getRecords()
                    .get(0);
            assertEquals("发布新的问答流程", item.getApplicationReason());
            assertEquals("陈子默", item.getApplicantName());
            assertEquals("czm", item.getApplicantAccount());
            assertTrue(item.isCanRevoke());
            assertFalse(item.isCanApprove());
            assertFalse(item.isCanReject());
        }
    }

    private void stubAuthorizedDetail(ApprovalInstance instance, LoginAccount account, List<ApprovalTask> tasks) {
        when(approvalInstanceMapper.selectOneByQuery(any(QueryWrapper.class))).thenReturn(instance);
        when(approvalTaskMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(tasks);
        when(approvalAssigneeService.getAvailableRoleIds(account.getId())).thenReturn(Set.of());
        when(approvalLogMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of());
        when(sysAccountService.list(any(QueryWrapper.class))).thenReturn(List.of());
    }

    private ApprovalInstance instance(long applicantId, long tenantId) {
        ApprovalInstance instance = new ApprovalInstance();
        instance.setId(INSTANCE_ID);
        instance.setTenantId(BigInteger.valueOf(tenantId));
        instance.setFlowId(BigInteger.valueOf(301));
        instance.setFlowVersion(1);
        instance.setResourceType("SKILL");
        instance.setResourceId(RESOURCE_ID);
        instance.setActionType("PUBLISH");
        instance.setStatus(ApprovalInstanceStatus.PENDING.getCode());
        instance.setCurrentStepNo(1);
        instance.setApplicantId(BigInteger.valueOf(applicantId));
        instance.setSnapshotJson(Map.of(
                "resourceSnapshot", Map.of("skillContent", "private prompt"),
                "steps", List.of(Map.of(
                        "stepNo", 1,
                        "stepName", "审核",
                        "assigneeType", ApprovalAssigneeType.USER.getCode(),
                        "assigneeTargetId", 7))));
        return instance;
    }

    private ApprovalTask task(String status, BigInteger actedBy) {
        ApprovalTask task = new ApprovalTask();
        task.setInstanceId(INSTANCE_ID);
        task.setStepNo(1);
        task.setStatus(status);
        task.setActedBy(actedBy);
        return task;
    }

    private LoginAccount account(long accountId, long tenantId) {
        LoginAccount account = new LoginAccount();
        account.setId(BigInteger.valueOf(accountId));
        account.setTenantId(BigInteger.valueOf(tenantId));
        return account;
    }
}
