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.junit.MockitoJUnitRunner;
import tech.easyflow.approval.entity.ApprovalTask;
import tech.easyflow.approval.entity.ApprovalTaskAssignee;
import tech.easyflow.approval.entity.vo.ApprovalAssigneeTargetVo;
import tech.easyflow.approval.entity.vo.ApprovalFlowStepVo;
import tech.easyflow.approval.enums.ApprovalAssigneeType;
import tech.easyflow.approval.enums.ApprovalTaskStatus;
import tech.easyflow.approval.mapper.ApprovalFlowStepAssigneeMapper;
import tech.easyflow.approval.mapper.ApprovalTaskAssigneeMapper;
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
import tech.easyflow.common.constant.enums.EnumDataStatus;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.entity.SysAccountRole;
import tech.easyflow.system.entity.SysDept;
import tech.easyflow.system.entity.SysRole;
import tech.easyflow.system.service.SysAccountRoleService;
import tech.easyflow.system.service.SysAccountService;
import tech.easyflow.system.service.SysDeptService;
import tech.easyflow.system.service.SysRoleService;

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

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

/**
 * {@link ApprovalAssigneeServiceImpl} 多审批对象语义测试。
 */
@RunWith(MockitoJUnitRunner.class)
public class ApprovalAssigneeServiceImplTest {

    @Mock
    private SysRoleService sysRoleService;

    @Mock
    private SysAccountService sysAccountService;

    @Mock
    private SysAccountRoleService sysAccountRoleService;

    @Mock
    private SysDeptService sysDeptService;

    @Mock
    private ApprovalTaskMapper approvalTaskMapper;

    @Mock
    private ApprovalFlowStepAssigneeMapper approvalFlowStepAssigneeMapper;

    @Mock
    private ApprovalTaskAssigneeMapper approvalTaskAssigneeMapper;

    @InjectMocks
    private ApprovalAssigneeServiceImpl service;

    /**
     * 验证同一步骤支持多个角色，并按 ID 去重、由服务端补齐名称。
     */
    @Test
    public void normalizeStepAssigneeShouldResolveAndDeduplicateMultipleRoles() {
        SysRole reviewer = role(11, "reviewer", "审核员");
        SysRole owner = role(12, "owner", "负责人");
        when(sysRoleService.getById(reviewer.getId())).thenReturn(reviewer);
        when(sysRoleService.getById(owner.getId())).thenReturn(owner);

        ApprovalFlowStepVo step = new ApprovalFlowStepVo();
        step.setAssigneeType(ApprovalAssigneeType.ROLE.getCode());
        step.setAssigneeTargets(List.of(
                target(reviewer.getId(), 0),
                target(owner.getId(), 0),
                target(reviewer.getId(), 0)));

        ApprovalFlowStepVo normalized = service.normalizeStepAssignee(step);

        assertEquals(2, normalized.getAssigneeTargets().size());
        assertEquals(reviewer.getId(), normalized.getAssigneeTargetId());
        assertEquals("审核员", normalized.getAssigneeTargetName());
        assertEquals("负责人", normalized.getAssigneeTargets().get(1).getTargetName());
    }

    /**
     * 验证部门审批可通过“包含子部门”命中下级部门成员。
     */
    @Test
    public void canHandleTaskShouldMatchDescendantDepartment() {
        BigInteger operatorId = BigInteger.valueOf(7);
        BigInteger parentDeptId = BigInteger.valueOf(21);
        BigInteger currentDeptId = BigInteger.valueOf(22);
        ApprovalTask task = new ApprovalTask();
        task.setId(BigInteger.valueOf(101));
        task.setAssigneeType(ApprovalAssigneeType.DEPT.getCode());

        ApprovalTaskAssignee relation = new ApprovalTaskAssignee();
        relation.setTaskId(task.getId());
        relation.setAssigneeType(ApprovalAssigneeType.DEPT.getCode());
        relation.setTargetId(parentDeptId);
        relation.setIncludeChildren(1);
        when(approvalTaskAssigneeMapper.selectListByQuery(any(QueryWrapper.class)))
                .thenReturn(List.of(relation));
        SysAccount operator = new SysAccount();
        operator.setId(operatorId);
        operator.setDeptId(currentDeptId);
        when(sysAccountService.getById(operatorId)).thenReturn(operator);
        when(sysDeptService.getSelfAndAncestorDeptIds(currentDeptId))
                .thenReturn(Set.of(parentDeptId, currentDeptId));

        assertTrue(service.canHandleTask(task, operatorId, Set.of()));
    }

    /**
     * 验证未启用“包含子部门”时，下级部门成员不能处理父部门任务。
     */
    @Test
    public void canHandleTaskShouldRejectDescendantDepartmentWhenFlagDisabled() {
        BigInteger operatorId = BigInteger.valueOf(7);
        BigInteger parentDeptId = BigInteger.valueOf(21);
        BigInteger currentDeptId = BigInteger.valueOf(22);
        ApprovalTask task = new ApprovalTask();
        task.setId(BigInteger.valueOf(101));
        task.setAssigneeType(ApprovalAssigneeType.DEPT.getCode());

        ApprovalTaskAssignee relation = new ApprovalTaskAssignee();
        relation.setTaskId(task.getId());
        relation.setAssigneeType(ApprovalAssigneeType.DEPT.getCode());
        relation.setTargetId(parentDeptId);
        relation.setIncludeChildren(0);
        when(approvalTaskAssigneeMapper.selectListByQuery(any(QueryWrapper.class)))
                .thenReturn(List.of(relation));
        SysAccount operator = new SysAccount();
        operator.setId(operatorId);
        operator.setDeptId(currentDeptId);
        when(sysAccountService.getById(operatorId)).thenReturn(operator);
        when(sysDeptService.getSelfAndAncestorDeptIds(currentDeptId))
                .thenReturn(Set.of(parentDeptId, currentDeptId));

        assertFalse(service.canHandleTask(task, operatorId, Set.of()));
    }

    /**
     * 验证包含子部门的部门任务会出现在下级部门成员的待审批列表。
     */
    @Test
    public void listPendingInstanceIdsShouldMatchDescendantDepartment() {
        BigInteger operatorId = BigInteger.valueOf(7);
        BigInteger parentDeptId = BigInteger.valueOf(21);
        BigInteger currentDeptId = BigInteger.valueOf(22);
        BigInteger taskId = BigInteger.valueOf(101);
        BigInteger instanceId = BigInteger.valueOf(201);

        SysAccount operator = new SysAccount();
        operator.setId(operatorId);
        operator.setDeptId(currentDeptId);
        when(sysAccountService.getById(operatorId)).thenReturn(operator);
        when(sysDeptService.getSelfAndAncestorDeptIds(currentDeptId))
                .thenReturn(Set.of(parentDeptId, currentDeptId));

        ApprovalTaskAssignee relation = new ApprovalTaskAssignee();
        relation.setTaskId(taskId);
        relation.setAssigneeType(ApprovalAssigneeType.DEPT.getCode());
        relation.setTargetId(parentDeptId);
        relation.setIncludeChildren(1);
        when(approvalTaskAssigneeMapper.selectListByQuery(any(QueryWrapper.class)))
                .thenReturn(List.of(), List.of(relation));

        ApprovalTask task = new ApprovalTask();
        task.setId(taskId);
        task.setInstanceId(instanceId);
        task.setStatus(ApprovalTaskStatus.PENDING.getCode());
        when(approvalTaskMapper.selectListByQuery(any(QueryWrapper.class)))
                .thenReturn(List.of(task), List.of(), List.of());

        assertEquals(
                Set.of(instanceId),
                service.listPendingInstanceIds(operatorId, Set.of(), null));
    }

    /**
     * 验证多个角色审批对象采用 OR 语义，命中任一角色即可处理。
     */
    @Test
    public void canHandleTaskShouldMatchAnyRoleTarget() {
        BigInteger operatorId = BigInteger.valueOf(7);
        ApprovalTask task = new ApprovalTask();
        task.setId(BigInteger.valueOf(101));
        task.setAssigneeType(ApprovalAssigneeType.ROLE.getCode());

        ApprovalTaskAssignee first = taskAssignee(task.getId(), 31);
        ApprovalTaskAssignee second = taskAssignee(task.getId(), 32);
        when(approvalTaskAssigneeMapper.selectListByQuery(any(QueryWrapper.class)))
                .thenReturn(List.of(first, second));

        assertTrue(service.canHandleTask(task, operatorId, Set.of(BigInteger.valueOf(32))));
    }

    /**
     * 验证开启发起人部门限制后不能配置部门审批对象。
     */
    @Test
    public void normalizeStepAssigneeShouldRejectDepartmentWhenRestricted() {
        ApprovalFlowStepVo step = new ApprovalFlowStepVo();
        step.setAssigneeType(ApprovalAssigneeType.DEPT.getCode());
        step.setRestrictToApplicantDept(1);

        assertThrows(BusinessException.class, () -> service.normalizeStepAssignee(step));
    }

    /**
     * 验证受限用户没有有效部门时，流程配置校验失败。
     */
    @Test
    public void validateRestrictedStepCandidatesShouldRejectUserWithoutDepartment() {
        SysAccount account = account(11, null);
        when(sysAccountService.list(any(QueryWrapper.class))).thenReturn(List.of(account));

        ApprovalFlowStepVo step = restrictedStep(
                ApprovalAssigneeType.USER, List.of(target(account.getId(), 0)));

        assertThrows(BusinessException.class,
                () -> service.validateRestrictedStepCandidates(List.of(step), null));
    }

    /**
     * 验证角色中存在发起人直属部门的有效账号时校验通过。
     */
    @Test
    public void validateRestrictedStepCandidatesShouldMatchRoleMemberInRequiredDepartment() {
        BigInteger roleId = BigInteger.valueOf(31);
        BigInteger accountId = BigInteger.valueOf(11);
        BigInteger deptId = BigInteger.valueOf(21);
        SysRole role = role(roleId.longValue(), "reviewer", "审核员");
        when(sysRoleService.list(any(QueryWrapper.class))).thenReturn(List.of(role));
        SysAccountRole relation = new SysAccountRole();
        relation.setRoleId(roleId);
        relation.setAccountId(accountId);
        when(sysAccountRoleService.list(any(QueryWrapper.class))).thenReturn(List.of(relation));
        when(sysAccountService.list(any(QueryWrapper.class))).thenReturn(List.of(account(11, deptId)));
        when(sysDeptService.getById(deptId)).thenReturn(dept(deptId));

        ApprovalFlowStepVo step = restrictedStep(
                ApprovalAssigneeType.ROLE, List.of(target(roleId, 0)));

        service.validateRestrictedStepCandidates(List.of(step), deptId);
    }

    /**
     * 验证角色成员与发起人直属部门没有交集时校验失败。
     */
    @Test
    public void validateRestrictedStepCandidatesShouldRejectEmptyRoleDepartmentIntersection() {
        BigInteger roleId = BigInteger.valueOf(31);
        BigInteger deptId = BigInteger.valueOf(21);
        SysRole role = role(roleId.longValue(), "reviewer", "审核员");
        when(sysRoleService.list(any(QueryWrapper.class))).thenReturn(List.of(role));
        SysAccountRole relation = new SysAccountRole();
        relation.setRoleId(roleId);
        relation.setAccountId(BigInteger.valueOf(11));
        when(sysAccountRoleService.list(any(QueryWrapper.class))).thenReturn(List.of(relation));
        when(sysAccountService.list(any(QueryWrapper.class))).thenReturn(List.of());
        when(sysDeptService.getById(deptId)).thenReturn(dept(deptId));

        ApprovalFlowStepVo step = restrictedStep(
                ApprovalAssigneeType.ROLE, List.of(target(roleId, 0)));

        assertThrows(BusinessException.class,
                () -> service.validateRestrictedStepCandidates(List.of(step), deptId));
    }

    /**
     * 验证任务同时命中角色和冻结直属部门时才允许处理。
     */
    @Test
    public void canHandleTaskShouldRequireFrozenDirectDepartment() {
        BigInteger operatorId = BigInteger.valueOf(7);
        BigInteger deptId = BigInteger.valueOf(21);
        BigInteger roleId = BigInteger.valueOf(31);
        ApprovalTask task = new ApprovalTask();
        task.setId(BigInteger.valueOf(101));
        task.setAssigneeType(ApprovalAssigneeType.ROLE.getCode());
        task.setRequiredDeptId(deptId);
        when(approvalTaskAssigneeMapper.selectListByQuery(any(QueryWrapper.class)))
                .thenReturn(List.of(taskAssignee(task.getId(), roleId.longValue())));
        when(sysAccountService.getById(operatorId)).thenReturn(account(operatorId.longValue(), deptId));
        when(sysDeptService.getById(deptId)).thenReturn(dept(deptId));

        assertTrue(service.canHandleTask(task, operatorId, Set.of(roleId)));

        task.setRequiredDeptId(BigInteger.valueOf(22));
        assertFalse(service.canHandleTask(task, operatorId, Set.of(roleId)));
    }

    /**
     * 验证待审批列表会排除不属于当前账号直属部门的受限任务。
     */
    @Test
    public void listPendingInstanceIdsShouldFilterFrozenDepartment() {
        BigInteger operatorId = BigInteger.valueOf(7);
        BigInteger deptId = BigInteger.valueOf(21);
        BigInteger roleId = BigInteger.valueOf(31);
        BigInteger matchedTaskId = BigInteger.valueOf(101);
        BigInteger otherTaskId = BigInteger.valueOf(102);
        BigInteger matchedInstanceId = BigInteger.valueOf(201);

        when(sysAccountService.getById(operatorId)).thenReturn(account(operatorId.longValue(), deptId));
        when(sysDeptService.getById(deptId)).thenReturn(dept(deptId));
        when(sysDeptService.getSelfAndAncestorDeptIds(deptId)).thenReturn(Set.of());
        when(approvalTaskAssigneeMapper.selectListByQuery(any(QueryWrapper.class)))
                .thenReturn(
                        List.of(),
                        List.of(
                                taskAssignee(matchedTaskId, roleId.longValue()),
                                taskAssignee(otherTaskId, roleId.longValue())));

        ApprovalTask matchedTask = pendingTask(matchedTaskId, matchedInstanceId, deptId);
        ApprovalTask otherTask = pendingTask(
                otherTaskId, BigInteger.valueOf(202), BigInteger.valueOf(22));
        when(approvalTaskMapper.selectListByQuery(any(QueryWrapper.class)))
                .thenReturn(List.of(matchedTask, otherTask), List.of(), List.of());

        assertEquals(
                Set.of(matchedInstanceId),
                service.listPendingInstanceIds(operatorId, Set.of(roleId), null));
    }

    /**
     * 构造有效角色。
     *
     * @param id 角色 ID
     * @param code 角色编码
     * @param name 角色名称
     * @return 角色
     */
    private SysRole role(long id, String code, String name) {
        SysRole role = new SysRole();
        role.setId(BigInteger.valueOf(id));
        role.setRoleKey(code);
        role.setRoleName(name);
        role.setStatus(EnumDataStatus.AVAILABLE.getCode());
        return role;
    }

    /**
     * 构造有效账号。
     *
     * @param id 账号 ID
     * @param deptId 部门 ID
     * @return 账号
     */
    private SysAccount account(long id, BigInteger deptId) {
        SysAccount account = new SysAccount();
        account.setId(BigInteger.valueOf(id));
        account.setDeptId(deptId);
        account.setStatus(EnumDataStatus.AVAILABLE.getCode());
        return account;
    }

    /**
     * 构造有效部门。
     *
     * @param deptId 部门 ID
     * @return 部门
     */
    private SysDept dept(BigInteger deptId) {
        SysDept dept = new SysDept();
        dept.setId(deptId);
        dept.setStatus(EnumDataStatus.AVAILABLE.getCode());
        return dept;
    }

    /**
     * 构造受发起人部门限制的步骤。
     *
     * @param type 审批对象类型
     * @param targets 审批对象
     * @return 步骤
     */
    private ApprovalFlowStepVo restrictedStep(ApprovalAssigneeType type,
                                              List<ApprovalAssigneeTargetVo> targets) {
        ApprovalFlowStepVo step = new ApprovalFlowStepVo();
        step.setStepNo(1);
        step.setStepName("审核");
        step.setAssigneeType(type.getCode());
        step.setAssigneeTargets(targets);
        step.setRestrictToApplicantDept(1);
        return step;
    }

    /**
     * 构造待审批任务。
     *
     * @param taskId 任务 ID
     * @param instanceId 实例 ID
     * @param requiredDeptId 限定部门 ID
     * @return 待审批任务
     */
    private ApprovalTask pendingTask(BigInteger taskId, BigInteger instanceId, BigInteger requiredDeptId) {
        ApprovalTask task = new ApprovalTask();
        task.setId(taskId);
        task.setInstanceId(instanceId);
        task.setStatus(ApprovalTaskStatus.PENDING.getCode());
        task.setAssigneeType(ApprovalAssigneeType.ROLE.getCode());
        task.setRequiredDeptId(requiredDeptId);
        return task;
    }

    /**
     * 构造审批对象。
     *
     * @param targetId 对象 ID
     * @param includeChildren 是否包含子部门
     * @return 审批对象
     */
    private ApprovalAssigneeTargetVo target(BigInteger targetId, int includeChildren) {
        ApprovalAssigneeTargetVo target = new ApprovalAssigneeTargetVo();
        target.setTargetId(targetId);
        target.setIncludeChildren(includeChildren);
        return target;
    }

    /**
     * 构造角色任务关联。
     *
     * @param taskId 任务 ID
     * @param targetId 角色 ID
     * @return 任务关联
     */
    private ApprovalTaskAssignee taskAssignee(BigInteger taskId, long targetId) {
        ApprovalTaskAssignee relation = new ApprovalTaskAssignee();
        relation.setTaskId(taskId);
        relation.setAssigneeType(ApprovalAssigneeType.ROLE.getCode());
        relation.setTargetId(BigInteger.valueOf(targetId));
        relation.setIncludeChildren(0);
        return relation;
    }
}
