package com.zt.plat.module.base.service.processinginfomationoperation;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.base.controller.admin.processinginfomationoperation.vo.ProcessingInfomationOperationDetailRespVO;
import com.zt.plat.module.base.controller.admin.processinginfomationoperation.vo.ProcessingInfomationOperationMaterialRespVO;
import com.zt.plat.module.base.controller.admin.processinginfomationoperation.vo.ProcessingInfomationOperationMaterialRespVO.ProcessingInfomationOperationMaterialDeptRespVO;
import com.zt.plat.module.base.controller.admin.processinginfomationoperation.vo.ProcessingInfomationOperationMaterialSaveReqVO;
import com.zt.plat.module.base.controller.admin.processinginfomationoperation.vo.ProcessingInfomationOperationOrganizationPageReqVO;
import com.zt.plat.module.base.controller.admin.processinginfomationoperation.vo.ProcessingInfomationOperationOrganizationRespVO;
import com.zt.plat.module.base.controller.admin.processinginfomationoperation.vo.ProcessingInfomationOperationPageReqVO;
import com.zt.plat.module.base.controller.admin.processinginfomationoperation.vo.ProcessingInfomationOperationRespVO;
import com.zt.plat.module.base.controller.admin.processinginfomationoperation.vo.ProcessingInfomationOperationSaveReqVO;
import com.zt.plat.module.base.dal.dao.processinginfomation.ProcessingInfomationMapper;
import com.zt.plat.module.base.dal.dao.processinginfomationoperation.ProcessingInfomationOperationMapper;
import com.zt.plat.module.base.dal.dao.processingoperation.ProcessingOperationMapper;
import com.zt.plat.module.base.dal.dao.processingoperationmaterial.ProcessingOperationMaterialMapper;
import com.zt.plat.module.base.dal.dataobject.base.MaterialInfomationDO;
import com.zt.plat.module.base.dal.dataobject.processinginfomation.ProcessingInfomationDO;
import com.zt.plat.module.base.dal.dataobject.processinginfomationoperation.ProcessingInfomationOperationDO;
import com.zt.plat.module.base.dal.dataobject.processingoperation.ProcessingOperationDO;
import com.zt.plat.module.base.dal.dataobject.processingoperationmaterial.ProcessingOperationMaterialDO;
import com.zt.plat.module.base.dal.mysql.base.MaterialInfomationMapper;
import com.zt.plat.module.system.api.dept.DeptApi;
import com.zt.plat.module.system.api.dept.dto.DeptRespDTO;

import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.zt.plat.module.base.enums.ErrorCodeConstants.PROCESSING_INFOMATION_OPERATION_NOT_EXISTS;

/**
 * Processing operation service implementation.
 */
@Service
@Validated
public class ProcessingInfomationOperationServiceImpl implements ProcessingInfomationOperationService {

    @Resource
    private ProcessingInfomationOperationMapper processingInfomationOperationMapper;
    @Resource
    private ProcessingOperationMapper processingOperationMapper;
    @Resource
    private ProcessingOperationMaterialMapper processingOperationMaterialMapper;
    @Resource
    private ProcessingInfomationMapper processingInfomationMapper;
    @Resource
    private MaterialInfomationMapper materialInfomationMapper;
    @Resource
    private DeptApi deptApi;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ProcessingInfomationOperationDetailRespVO createProcessingInfomationOperation(ProcessingInfomationOperationSaveReqVO createReqVO) {
        ProcessingInfomationOperationDO relation = BeanUtils.toBean(createReqVO, ProcessingInfomationOperationDO.class);
        processingInfomationOperationMapper.insert(relation);
        saveOperationMaterials(relation.getId(), createReqVO.getMaterials());
        return getProcessingInfomationOperation(relation.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProcessingInfomationOperation(ProcessingInfomationOperationSaveReqVO updateReqVO) {
        validateProcessingInfomationOperationExists(updateReqVO.getId());
        ProcessingInfomationOperationDO updateObj = BeanUtils.toBean(updateReqVO, ProcessingInfomationOperationDO.class);
        processingInfomationOperationMapper.updateById(updateObj);
        saveOperationMaterials(updateReqVO.getId(), updateReqVO.getMaterials());
    }

    @Override
    public void deleteProcessingInfomationOperation(Long id) {
        validateProcessingInfomationOperationExists(id);
        processingInfomationOperationMapper.deleteById(id);
        processingOperationMaterialMapper.deleteByProcessingOperationId(id);
    }

    @Override
    public void deleteProcessingInfomationOperationListByIds(List<Long> ids) {
        validateProcessingInfomationOperationExists(ids);
        processingInfomationOperationMapper.deleteBatchIds(ids);
        processingOperationMaterialMapper.deleteByProcessingOperationIds(ids);
    }

    private void validateProcessingInfomationOperationExists(List<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            throw exception(PROCESSING_INFOMATION_OPERATION_NOT_EXISTS);
        }
        List<ProcessingInfomationOperationDO> list = processingInfomationOperationMapper.selectBatchIds(ids);
        if (CollUtil.isEmpty(list) || list.size() != ids.size()) {
            throw exception(PROCESSING_INFOMATION_OPERATION_NOT_EXISTS);
        }
    }

    private void validateProcessingInfomationOperationExists(Long id) {
        if (processingInfomationOperationMapper.selectById(id) == null) {
            throw exception(PROCESSING_INFOMATION_OPERATION_NOT_EXISTS);
        }
    }

    @Override
    public ProcessingInfomationOperationDetailRespVO getProcessingInfomationOperation(Long id) {
        ProcessingInfomationOperationDO relation = processingInfomationOperationMapper.selectById(id);
        if (relation == null) {
            return null;
        }
        Map<Long, ProcessingOperationDO> operationMap = loadOperationMap(Collections.singletonList(relation));
        Map<Long, List<ProcessingInfomationOperationMaterialRespVO>> materialMap = buildMaterialDetailMap(Collections.singletonList(relation.getId()));
        ProcessingInfomationOperationDetailRespVO detail = BeanUtils.toBean(relation, ProcessingInfomationOperationDetailRespVO.class);
        fillOperationInfo(relation, detail, operationMap);
        detail.setMaterials(materialMap.getOrDefault(relation.getId(), Collections.emptyList()));
        return detail;
    }

    @Override
    public PageResult<ProcessingInfomationOperationDO> getProcessingInfomationOperationPage(ProcessingInfomationOperationPageReqVO pageReqVO) {
        return processingInfomationOperationMapper.selectPage(pageReqVO);
    }

    @Override
    public PageResult<ProcessingInfomationOperationRespVO> getProcessingInfomationOperationDetailPage(ProcessingInfomationOperationPageReqVO pageReqVO) {
        List<ProcessingInfomationOperationRespVO> detailList = buildDetailList(pageReqVO);
        return paginate(detailList, pageReqVO.getPageNo(), pageReqVO.getPageSize());
    }

    @Override
    public List<ProcessingInfomationOperationRespVO> getProcessingInfomationOperationDetailList(ProcessingInfomationOperationPageReqVO pageReqVO) {
        return buildDetailList(pageReqVO);
    }

    @Override
    public PageResult<ProcessingInfomationOperationOrganizationRespVO> getOrganizationOperationPage(ProcessingInfomationOperationOrganizationPageReqVO pageReqVO) {
        List<ProcessingOperationMaterialDO> materialRecords = processingOperationMaterialMapper.selectOrganizationList(pageReqVO);
        if (CollUtil.isEmpty(materialRecords)) {
            return new PageResult<>(Collections.emptyList(), 0L);
        }

        Long deptFilter = pageReqVO.getDeptId();
        List<ProcessingOperationMaterialDO> filteredMaterials = materialRecords.stream()
                .filter(Objects::nonNull)
                .filter(item -> {
                    if (deptFilter == null) {
                        return true;
                    }
                    List<Long> deptIds = parseDepartmentIds(item.getDepartmentIds());
                    return CollUtil.isNotEmpty(deptIds) && deptIds.contains(deptFilter);
                })
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(filteredMaterials)) {
            return new PageResult<>(Collections.emptyList(), 0L);
        }

        Set<Long> relationIds = filteredMaterials.stream()
                .map(ProcessingOperationMaterialDO::getProcessingOperationId)
                .filter(Objects::nonNull)
                .collect(Collectors.toCollection(LinkedHashSet::new));
        if (CollUtil.isEmpty(relationIds)) {
            return new PageResult<>(Collections.emptyList(), 0L);
        }

        List<ProcessingInfomationOperationDO> relations = processingInfomationOperationMapper.selectBatchIds(relationIds);
        if (CollUtil.isEmpty(relations)) {
            return new PageResult<>(Collections.emptyList(), 0L);
        }

        Map<Long, ProcessingInfomationOperationDO> relationMap = relations.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(ProcessingInfomationOperationDO::getId, Function.identity(), (v1, v2) -> v1, LinkedHashMap::new));
        Map<Long, ProcessingOperationDO> operationMap = loadOperationMap(relations);
        Map<Long, ProcessingInfomationDO> processingMap = loadProcessingMap(relations);
        Map<Long, List<ProcessingInfomationOperationMaterialRespVO>> materialDetailMap = buildMaterialDetailMap(relationIds);

        List<ProcessingInfomationOperationOrganizationRespVO> dataList = new ArrayList<>();
        LocalDateTime[] createTimeRange = pageReqVO.getCreateTime();
        for (ProcessingInfomationOperationDO relation : relationMap.values()) {
            if (pageReqVO.getIsKey() != null && !Objects.equals(relation.getIsKey(), pageReqVO.getIsKey())) {
                continue;
            }
            if (pageReqVO.getProcessingId() != null && !Objects.equals(relation.getProcessingId(), pageReqVO.getProcessingId())) {
                continue;
            }
            if (createTimeRange != null && createTimeRange.length == 2 && createTimeRange[0] != null && createTimeRange[1] != null && relation.getCreateTime() != null) {
                if (relation.getCreateTime().isBefore(createTimeRange[0]) || relation.getCreateTime().isAfter(createTimeRange[1])) {
                    continue;
                }
            }

            Long displayOperationId = resolveDisplayOperationId(relation);
            if (pageReqVO.getOperationId() != null && !Objects.equals(displayOperationId, pageReqVO.getOperationId())) {
                continue;
            }

            List<ProcessingInfomationOperationMaterialRespVO> materials = materialDetailMap.getOrDefault(relation.getId(), Collections.emptyList());
            if (CollUtil.isEmpty(materials)) {
                continue;
            }

            Map<Long, String> deptNameMap = new LinkedHashMap<>();
            for (ProcessingInfomationOperationMaterialRespVO material : materials) {
                List<Long> deptIds = material.getDepartmentIds();
                if (CollUtil.isEmpty(deptIds)) {
                    continue;
                }
                for (Long deptId : deptIds) {
                    if (deptId == null) {
                        continue;
                    }
                    deptNameMap.putIfAbsent(deptId, resolveDeptName(material, deptId));
                }
            }
            if (deptNameMap.isEmpty()) {
                continue;
            }
            if (deptFilter != null && !deptNameMap.containsKey(deptFilter)) {
                continue;
            }

            Stream<Long> deptStream = deptFilter != null ? Stream.of(deptFilter) : deptNameMap.keySet().stream();
            deptStream.forEach(deptId -> {
                ProcessingInfomationOperationOrganizationRespVO resp = buildOrganizationResp(
                        deptId,
                        deptNameMap.get(deptId),
                        relation,
                        processingMap,
                        operationMap,
                        displayOperationId
                );
                dataList.add(resp);
            });
        }
        return paginate(dataList, pageReqVO.getPageNo(), pageReqVO.getPageSize());
    }

    private String resolveDeptName(ProcessingInfomationOperationMaterialRespVO material, Long deptId) {
        if (deptId == null) {
            return null;
        }
        List<ProcessingInfomationOperationMaterialDeptRespVO> departments = material.getDepartments();
        if (CollUtil.isEmpty(departments)) {
            return null;
        }
        return departments.stream()
                .filter(item -> Objects.equals(item.getId(), deptId))
                .map(ProcessingInfomationOperationMaterialDeptRespVO::getName)
                .findFirst()
                .orElse(null);
    }

    private ProcessingInfomationOperationOrganizationRespVO buildOrganizationResp(Long deptId,
                                                                                  String deptName,
                                                                                  ProcessingInfomationOperationDO relation,
                                                                                  Map<Long, ProcessingInfomationDO> processingMap,
                                                                                  Map<Long, ProcessingOperationDO> operationMap,
                                                                                  Long displayOperationId) {
        ProcessingInfomationOperationOrganizationRespVO resp = new ProcessingInfomationOperationOrganizationRespVO();
        resp.setId(relation.getId());
        resp.setDeptId(deptId);
        resp.setDeptName(deptName);
        resp.setRemark(relation.getRemark());
        resp.setCreateTime(relation.getCreateTime());
        resp.setIsKey(relation.getIsKey());

        ProcessingInfomationDO processing = processingMap.get(relation.getProcessingId());
        if (processing != null) {
            resp.setProcessingId(processing.getId());
            resp.setProcessingCode(processing.getCode());
            resp.setProcessingName(processing.getName());
            resp.setProcessingStatus(processing.getStatusValue());
        }

        resp.setOperationId(displayOperationId);
        ProcessingOperationDO operation = operationMap.get(displayOperationId);
        if (operation != null) {
            resp.setOperationCode(operation.getCode());
            resp.setOperationName(operation.getName());
        }
        return resp;
    }

    private Long resolveDisplayOperationId(ProcessingInfomationOperationDO relation) {
        if (relation.getOperationId() != null) {
            return relation.getOperationId();
        }
        if (relation.getNextOperationId() != null) {
            return relation.getNextOperationId();
        }
        return relation.getPreviousOperationId();
    }

    private List<ProcessingInfomationOperationRespVO> buildDetailList(ProcessingInfomationOperationPageReqVO pageReqVO) {
        List<ProcessingInfomationOperationDO> relations = processingInfomationOperationMapper.selectList(pageReqVO);
        if (CollUtil.isEmpty(relations)) {
            return Collections.emptyList();
        }
        Map<Long, ProcessingOperationDO> operationMap = loadOperationMap(relations);
        List<ProcessingInfomationOperationRespVO> detailList = relations.stream()
                .map(item -> {
                    ProcessingInfomationOperationRespVO resp = BeanUtils.toBean(item, ProcessingInfomationOperationRespVO.class);
                    fillOperationInfo(item, resp, operationMap);
                    return resp;
                })
                .collect(Collectors.toList());
        applyOperationFilters(detailList, pageReqVO);
        return detailList;
    }

    private void applyOperationFilters(List<ProcessingInfomationOperationRespVO> list, ProcessingInfomationOperationPageReqVO pageReqVO) {
        if (StrUtil.isNotBlank(pageReqVO.getNextOperationCode())) {
            list.removeIf(item -> !StrUtil.containsIgnoreCase(StrUtil.blankToDefault(item.getNextOperationCode(), ""), pageReqVO.getNextOperationCode()));
        }
        if (StrUtil.isNotBlank(pageReqVO.getNextOperationName())) {
            list.removeIf(item -> !StrUtil.containsIgnoreCase(StrUtil.blankToDefault(item.getNextOperationName(), ""), pageReqVO.getNextOperationName()));
        }
    }

    private Map<Long, ProcessingOperationDO> loadOperationMap(Collection<ProcessingInfomationOperationDO> relations) {
        if (CollUtil.isEmpty(relations)) {
            return Collections.emptyMap();
        }
        Set<Long> operationIds = relations.stream()
                .flatMap(item -> Stream.of(item.getOperationId(), item.getPreviousOperationId(), item.getNextOperationId()))
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (CollUtil.isEmpty(operationIds)) {
            return Collections.emptyMap();
        }
        List<ProcessingOperationDO> operations = processingOperationMapper.selectBatchIds(operationIds);
        if (CollUtil.isEmpty(operations)) {
            return Collections.emptyMap();
        }
        return operations.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(ProcessingOperationDO::getId, Function.identity(), (v1, v2) -> v1));
    }

    private Map<Long, ProcessingInfomationDO> loadProcessingMap(Collection<ProcessingInfomationOperationDO> relations) {
        Set<Long> processingIds = relations.stream()
                .map(ProcessingInfomationOperationDO::getProcessingId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (CollUtil.isEmpty(processingIds)) {
            return Collections.emptyMap();
        }
        List<ProcessingInfomationDO> list = processingInfomationMapper.selectBatchIds(processingIds);
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyMap();
        }
        return list.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(ProcessingInfomationDO::getId, Function.identity(), (v1, v2) -> v1));
    }

    private void fillOperationInfo(ProcessingInfomationOperationDO relation,
                                   ProcessingInfomationOperationRespVO target,
                                   Map<Long, ProcessingOperationDO> operationMap) {
        ProcessingOperationDO current = operationMap.get(relation.getOperationId());
        String operationType = null;
        if (current != null) {
            target.setOperationCode(current.getCode());
            target.setOperationName(current.getName());
            operationType = normalizeOperationTypeValue(current.getTypeValue());
        }
        ProcessingOperationDO previous = operationMap.get(relation.getPreviousOperationId());
        if (previous != null) {
            target.setPreviousOperationCode(previous.getCode());
            target.setPreviousOperationName(previous.getName());
            if (operationType == null) {
                operationType = normalizeOperationTypeValue(previous.getTypeValue());
            }
        }
        ProcessingOperationDO next = operationMap.get(relation.getNextOperationId());
        if (next != null) {
            target.setNextOperationCode(next.getCode());
            target.setNextOperationName(next.getName());
            if (operationType == null) {
                operationType = normalizeOperationTypeValue(next.getTypeValue());
            }
        }
        target.setOperationTypeValue(operationType);
    }

    private String normalizeOperationTypeValue(String typeValue) {
        if (StrUtil.isBlank(typeValue)) {
            return null;
        }
        return StrUtil.trim(typeValue);
    }

    private void saveOperationMaterials(Long relationId, List<ProcessingInfomationOperationMaterialSaveReqVO> materials) {
        processingOperationMaterialMapper.deleteByProcessingOperationId(relationId);
        if (CollUtil.isEmpty(materials)) {
            return;
        }
        List<ProcessingOperationMaterialDO> entities = new ArrayList<>();
        for (ProcessingInfomationOperationMaterialSaveReqVO item : materials) {
            if (item == null || item.getMaterialId() == null) {
                continue;
            }
            ProcessingOperationMaterialDO entity = new ProcessingOperationMaterialDO();
            entity.setProcessingOperationId(relationId);
            entity.setMaterialId(item.getMaterialId());
            entity.setTypeValue(item.getTypeValue());
            entity.setRemark(item.getRemark());
            entity.setDepartmentIds(formatDepartmentIds(item.getDepartmentIds()));
            entities.add(entity);
        }
        if (CollUtil.isNotEmpty(entities)) {
            processingOperationMaterialMapper.insertBatch(entities);
        }
    }

    private Map<Long, List<ProcessingInfomationOperationMaterialRespVO>> buildMaterialDetailMap(Collection<Long> relationIds) {
        if (CollUtil.isEmpty(relationIds)) {
            return Collections.emptyMap();
        }
        List<ProcessingOperationMaterialDO> materialList = processingOperationMaterialMapper.selectListByProcessingOperationIds(relationIds);
        if (CollUtil.isEmpty(materialList)) {
            return Collections.emptyMap();
        }
        Set<Long> materialIds = materialList.stream()
                .map(ProcessingOperationMaterialDO::getMaterialId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, MaterialInfomationDO> materialMap = Collections.emptyMap();
        if (CollUtil.isNotEmpty(materialIds)) {
            List<MaterialInfomationDO> materials = materialInfomationMapper.selectBatchIds(materialIds);
            if (CollUtil.isNotEmpty(materials)) {
                materialMap = materials.stream()
                        .filter(Objects::nonNull)
                        .collect(Collectors.toMap(MaterialInfomationDO::getId, Function.identity(), (v1, v2) -> v1));
            }
        }
        Set<Long> deptIds = materialList.stream()
                .flatMap(item -> parseDepartmentIds(item.getDepartmentIds()).stream())
                .collect(Collectors.toSet());
        Map<Long, DeptRespDTO> deptMap = loadDeptMap(deptIds);

        Map<Long, List<ProcessingInfomationOperationMaterialRespVO>> result = new LinkedHashMap<>();
        for (ProcessingOperationMaterialDO item : materialList) {
            ProcessingInfomationOperationMaterialRespVO resp = buildMaterialResp(item, materialMap, deptMap);
            result.computeIfAbsent(item.getProcessingOperationId(), key -> new ArrayList<>()).add(resp);
        }
        return result;
    }

    private Map<Long, DeptRespDTO> loadDeptMap(Set<Long> deptIds) {
        if (CollUtil.isEmpty(deptIds)) {
            return Collections.emptyMap();
        }
        try {
            List<DeptRespDTO> deptList = deptApi.getDeptList(deptIds).getCheckedData();
            if (CollUtil.isEmpty(deptList)) {
                return Collections.emptyMap();
            }
            return deptList.stream()
                    .filter(Objects::nonNull)
                    .collect(Collectors.toMap(DeptRespDTO::getId, Function.identity(), (v1, v2) -> v1));
        } catch (Exception ex) {
            return Collections.emptyMap();
        }
    }

    private ProcessingInfomationOperationMaterialRespVO buildMaterialResp(ProcessingOperationMaterialDO materialDO,
                                                                          Map<Long, MaterialInfomationDO> materialMap,
                                                                          Map<Long, DeptRespDTO> deptMap) {
        ProcessingInfomationOperationMaterialRespVO resp = new ProcessingInfomationOperationMaterialRespVO();
        resp.setId(materialDO.getId());
        resp.setProcessingOperationId(materialDO.getProcessingOperationId());
        resp.setMaterialId(materialDO.getMaterialId());
        resp.setTypeValue(materialDO.getTypeValue());
        resp.setRemark(materialDO.getRemark());
        resp.setCreateTime(materialDO.getCreateTime());

        MaterialInfomationDO material = materialMap.get(materialDO.getMaterialId());
        if (material != null) {
            resp.setMaterialCode(material.getCode());
            resp.setMaterialName(material.getName());
        }
        List<Long> deptIds = parseDepartmentIds(materialDO.getDepartmentIds());
        resp.setDepartmentIds(new ArrayList<>(deptIds));
        if (CollUtil.isNotEmpty(deptIds)) {
            List<ProcessingInfomationOperationMaterialDeptRespVO> deptInfos = deptIds.stream()
                    .map(id -> {
                        ProcessingInfomationOperationMaterialDeptRespVO deptResp = new ProcessingInfomationOperationMaterialDeptRespVO();
                        deptResp.setId(id);
                        DeptRespDTO dept = deptMap.get(id);
                        if (dept != null) {
                            deptResp.setName(dept.getName());
                        }
                        return deptResp;
                    })
                    .collect(Collectors.toList());
            resp.setDepartments(deptInfos);
        } else {
            resp.setDepartments(Collections.emptyList());
        }
        return resp;
    }

    private List<Long> parseDepartmentIds(String raw) {
        if (StrUtil.isBlank(raw)) {
            return Collections.emptyList();
        }
        return Arrays.stream(raw.split(","))
                .map(String::trim)
                .filter(StrUtil::isNotEmpty)
                .map(id -> {
                    try {
                        return Long.parseLong(id);
                    } catch (NumberFormatException ex) {
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
    }

    private String formatDepartmentIds(List<Long> deptIds) {
        if (CollUtil.isEmpty(deptIds)) {
            return null;
        }
        List<Long> normalized = deptIds.stream()
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(normalized)) {
            return null;
        }
        return CollUtil.join(normalized, ",");
    }

    private <T> PageResult<T> paginate(List<T> list, Integer pageNo, Integer pageSize) {
        if (CollUtil.isEmpty(list)) {
            return new PageResult<>(Collections.emptyList(), 0L);
        }
        int current = pageNo == null || pageNo <= 0 ? 1 : pageNo;
        int size = pageSize == null || pageSize <= 0 ? list.size() : pageSize;
        int fromIndex = Math.min((current - 1) * size, list.size());
        int toIndex = Math.min(fromIndex + size, list.size());
        List<T> pageList = list.subList(fromIndex, toIndex);
        return new PageResult<>(pageList, (long) list.size());
    }
}
