package tech.easyflow.admin.controller.system;

import com.mybatisflex.core.query.QueryWrapper;
import org.mockito.ArgumentCaptor;
import org.mockito.MockedStatic;
import org.testng.annotations.Test;
import tech.easyflow.approval.mapper.ApprovalFlowScopeMapper;
import tech.easyflow.approval.mapper.ApprovalFlowStepAssigneeMapper;
import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.constant.enums.EnumDataStatus;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.tree.TreeNode;
import tech.easyflow.system.entity.SysDept;
import tech.easyflow.system.mapper.SysDeptMapper;
import tech.easyflow.system.service.SysAccountService;
import tech.easyflow.system.service.SysDeptService;

import java.io.Serializable;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyCollection;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;

/**
 * {@link SysDeptController} 部门状态保存测试。
 */
public class SysDeptControllerTest {

    /**
     * 验证批量状态修改使用单次更新并写入审计字段。
     */
    @Test
    public void changeStatusBatchShouldUpdateAllDepartmentsOnce() {
        SysDeptService service = mock(SysDeptService.class);
        SysDeptMapper mapper = mock(SysDeptMapper.class);
        SysDeptController controller = createController(service);
        List<BigInteger> ids = List.of(BigInteger.ONE, BigInteger.TWO);
        when(service.listByIds(anyCollection()))
                .thenReturn(List.of(buildDept(BigInteger.ONE, "dept_1"), buildDept(BigInteger.TWO, "dept_2")));
        when(service.getMapper()).thenReturn(mapper);
        when(mapper.updateByQuery(any(SysDept.class), any(QueryWrapper.class))).thenReturn(2);

        LoginAccount loginAccount = mock(LoginAccount.class);
        when(loginAccount.getId()).thenReturn(BigInteger.TEN);
        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(loginAccount);

            Result<Integer> result = controller.changeStatusBatch(ids, EnumDataStatus.UNAVAILABLE.getCode());

            assertEquals(result.getErrorCode(), 0);
            assertEquals(result.getData(), Integer.valueOf(2));
        }

        ArgumentCaptor<SysDept> updateCaptor = ArgumentCaptor.forClass(SysDept.class);
        verify(mapper).updateByQuery(updateCaptor.capture(), any(QueryWrapper.class));
        assertEquals(updateCaptor.getValue().getStatus(), EnumDataStatus.UNAVAILABLE.getCode());
        assertEquals(updateCaptor.getValue().getModifiedBy(), BigInteger.TEN);
    }

    /**
     * 验证根部门不能通过批量接口禁用。
     */
    @Test
    public void changeStatusBatchShouldRejectRootDepartmentDisable() {
        SysDeptService service = mock(SysDeptService.class);
        SysDeptMapper mapper = mock(SysDeptMapper.class);
        SysDeptController controller = createController(service);
        SysDept root = buildDept(BigInteger.ONE, Constants.ROOT_DEPT);
        when(service.listByIds(anyCollection())).thenReturn(List.of(root));
        when(service.getMapper()).thenReturn(mapper);

        Result<Integer> result = controller.changeStatusBatch(
                List.of(BigInteger.ONE),
                EnumDataStatus.UNAVAILABLE.getCode());

        assertEquals(result.getErrorCode(), 1);
        assertEquals(result.getMessage(), "根部门不能禁用");
        verify(mapper, never()).updateByQuery(any(SysDept.class), any(QueryWrapper.class));
    }

    /**
     * 验证批量状态修改拒绝非法状态。
     */
    @Test
    public void changeStatusBatchShouldRejectInvalidStatus() {
        SysDeptService service = mock(SysDeptService.class);
        SysDeptController controller = createController(service);

        Result<Integer> result = controller.changeStatusBatch(List.of(BigInteger.ONE), 2);

        assertEquals(result.getErrorCode(), 1);
        assertEquals(result.getMessage(), "部门状态不合法");
        verify(service, never()).listByIds(anyCollection());
    }

    /**
     * 验证存在未选中下级部门时禁止批量删除。
     */
    @Test
    public void removeBatchShouldRejectUnselectedChildDepartment() {
        SysDeptService service = mock(SysDeptService.class);
        SysDeptController controller = createController(service);
        Collection<Serializable> ids = List.of(BigInteger.ONE);
        when(service.listByIds(ids)).thenReturn(List.of(buildDept(BigInteger.ONE, "dept_1")));
        when(service.count(any(QueryWrapper.class))).thenReturn(1L);

        Result<?> result = controller.onRemoveBefore(ids);

        assertEquals(result.getErrorCode(), 1);
        assertEquals(result.getMessage(), "所选部门包含未选中的下级部门，不能删除");
    }

    /**
     * 验证审批步骤引用的部门不能禁用。
     */
    @Test
    public void changeStatusBatchShouldRejectApprovalStepReference() {
        SysDeptService service = mock(SysDeptService.class);
        ApprovalFlowStepAssigneeMapper assigneeMapper = mock(ApprovalFlowStepAssigneeMapper.class);
        SysDeptController controller = new SysDeptController(
                service,
                mock(SysAccountService.class),
                assigneeMapper,
                mock(ApprovalFlowScopeMapper.class));
        when(service.listByIds(anyCollection()))
                .thenReturn(List.of(buildDept(BigInteger.ONE, "dept_1")));
        when(assigneeMapper.selectCountByQuery(any(QueryWrapper.class))).thenReturn(1L);

        Result<Integer> result = controller.changeStatusBatch(
                List.of(BigInteger.ONE),
                EnumDataStatus.UNAVAILABLE.getCode());

        assertEquals(result.getErrorCode(), 1);
        assertEquals(result.getMessage(), "所选部门已被审批流程使用，不能禁用");
    }

    /**
     * 验证审批范围引用的部门不能删除。
     */
    @Test
    public void removeBatchShouldRejectApprovalScopeReference() {
        SysDeptService service = mock(SysDeptService.class);
        ApprovalFlowScopeMapper scopeMapper = mock(ApprovalFlowScopeMapper.class);
        SysDeptController controller = new SysDeptController(
                service,
                mock(SysAccountService.class),
                mock(ApprovalFlowStepAssigneeMapper.class),
                scopeMapper);
        Collection<Serializable> ids = List.of(BigInteger.ONE);
        when(service.listByIds(ids)).thenReturn(List.of(buildDept(BigInteger.ONE, "dept_1")));
        when(service.count(any(QueryWrapper.class))).thenReturn(0L);
        when(scopeMapper.selectCountByQuery(any(QueryWrapper.class))).thenReturn(1L);

        Result<?> result = controller.onRemoveBefore(ids);

        assertEquals(result.getErrorCode(), 1);
        assertEquals(result.getMessage(), "所选部门已被审批流程使用，请先调整审批配置");
    }

    /**
     * 验证新增部门未传状态时默认启用。
     */
    @Test
    public void saveShouldDefaultMissingStatusToAvailable() {
        SysDept entity = buildDept(null);

        invokeSaveBefore(entity);

        assertEquals(entity.getStatus(), EnumDataStatus.AVAILABLE.getCode());
    }

    /**
     * 验证新增部门显式传入未启用状态时保持原值。
     */
    @Test
    public void saveShouldPreserveExplicitUnavailableStatus() {
        SysDept entity = buildDept(EnumDataStatus.UNAVAILABLE.getCode());

        invokeSaveBefore(entity);

        assertEquals(entity.getStatus(), EnumDataStatus.UNAVAILABLE.getCode());
    }

    /**
     * 验证名称或编码模糊搜索会保留所有重名部门及其祖先路径。
     */
    @Test
    public void listShouldKeepAllDuplicateNameMatchesAndAncestors() {
        SysDeptService service = mock(SysDeptService.class);
        SysDeptController controller = createController(service);
        SysDept firstMatch = buildTreeDept(BigInteger.valueOf(3), BigInteger.valueOf(2),
                "1,2", "交易银行部", "A-01");
        SysDept secondMatch = buildTreeDept(BigInteger.valueOf(5), BigInteger.valueOf(4),
                "1,4", "交易银行部", "B-01");
        List<SysDept> allDepartments = List.of(
                buildTreeDept(BigInteger.ONE, BigInteger.ZERO, "0", "总行", "ROOT"),
                buildTreeDept(BigInteger.valueOf(2), BigInteger.ONE, "1", "分行一", "A"),
                firstMatch,
                buildTreeDept(BigInteger.valueOf(4), BigInteger.ONE, "1", "分行二", "B"),
                secondMatch);
        when(service.list(any(QueryWrapper.class)))
                .thenReturn(List.of(firstMatch, secondMatch), allDepartments);

        SysDept query = new SysDept();
        query.setKeyword(" 银行 ");
        Result<List<SysDept>> result = controller.list(query, true, null, null);

        List<SysDept> flattened = new ArrayList<>();
        flattenDepartments(result.getData(), flattened);
        assertEquals(flattened.stream()
                .filter(department -> "交易银行部".equals(department.getDeptName()))
                .count(), 2L);
        assertEquals(flattened.size(), 5);

        ArgumentCaptor<QueryWrapper> queryCaptor = ArgumentCaptor.forClass(QueryWrapper.class);
        verify(service, times(2)).list(queryCaptor.capture());
        String ancestorQuerySql = queryCaptor.getAllValues().get(1).toSQL().toUpperCase(Locale.ROOT);
        assertTrue(ancestorQuerySql.contains("ID") && ancestorQuerySql.contains(" IN "),
                "祖先节点查询应使用可见部门 ID 集合限制范围: " + ancestorQuerySql);
    }

    /**
     * 构造待保存的部门。
     *
     * @param status 部门状态
     * @return 部门实体
     */
    private SysDept buildDept(Integer status) {
        SysDept entity = new SysDept();
        entity.setParentId(BigInteger.ZERO);
        entity.setStatus(status);
        return entity;
    }

    /**
     * 构造指定主键和编码的部门。
     *
     * @param id 部门主键
     * @param deptCode 部门编码
     * @return 部门实体
     */
    private SysDept buildDept(BigInteger id, String deptCode) {
        SysDept entity = new SysDept();
        entity.setId(id);
        entity.setDeptCode(deptCode);
        return entity;
    }

    /**
     * 构造部门树节点。
     *
     * @param id 部门主键
     * @param parentId 父部门主键
     * @param ancestors 祖先路径
     * @param deptName 部门名称
     * @param deptCode 部门编码
     * @return 部门树节点
     */
    private SysDept buildTreeDept(BigInteger id, BigInteger parentId, String ancestors,
                                  String deptName, String deptCode) {
        SysDept entity = buildDept(id, deptCode);
        entity.setParentId(parentId);
        entity.setAncestors(ancestors);
        entity.setDeptName(deptName);
        return entity;
    }

    /**
     * 将部门树展开为列表。
     *
     * @param departments 当前层部门
     * @param result 展开结果
     */
    private void flattenDepartments(List<? extends TreeNode> departments, List<SysDept> result) {
        for (TreeNode node : departments) {
            SysDept department = (SysDept) node;
            result.add(department);
            if (department.getChildren() != null) {
                flattenDepartments(department.getChildren(), result);
            }
        }
    }

    /**
     * 调用新增前置处理。
     *
     * @param entity 部门实体
     */
    private void invokeSaveBefore(SysDept entity) {
        LoginAccount loginAccount = mock(LoginAccount.class);
        SysDeptController controller = createController(mock(SysDeptService.class));
        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(loginAccount);
            controller.onSaveOrUpdateBefore(entity, true);
        }
    }

    /**
     * 创建带有隔离依赖的部门控制器。
     *
     * @param service 部门服务
     * @return 部门控制器
     */
    private SysDeptController createController(SysDeptService service) {
        return new SysDeptController(
                service,
                mock(SysAccountService.class),
                mock(ApprovalFlowStepAssigneeMapper.class),
                mock(ApprovalFlowScopeMapper.class));
    }
}
