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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.zt.plat.module.base.dal.dao.materialhasproperties.MaterialHasPropertiesMapper;
import com.zt.plat.module.base.dal.dao.materialproperties.MaterialPropertiesMapper;
import com.zt.plat.module.base.dal.dataobject.materialhasproperties.MaterialHasPropertiesDO;
import com.zt.plat.module.base.dal.dataobject.materialproperties.MaterialPropertiesDO;
import com.zt.plat.module.base.service.masterdatasync.support.MasterDataPropertyDefinition;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.util.*;
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.collection.CollectionUtils;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.base.controller.admin.departmentmaterial.vo.*;
import com.zt.plat.module.base.dal.dao.departmentmaterial.DepartmentMaterialMapper;
import com.zt.plat.module.base.dal.dao.materialclasses.MaterialClassesMapper;
import com.zt.plat.module.base.dal.dataobject.base.MaterialInfomationDO;
import com.zt.plat.module.base.dal.dataobject.departmentmaterial.DepartmentMaterialDO;
import com.zt.plat.module.base.dal.dataobject.materialclasses.MaterialClassesDO;
import com.zt.plat.module.base.dal.mysql.base.MaterialInfomationMapper;
import com.zt.plat.module.base.dal.dao.businessdictionarytype.BusinessDictionaryDataMapper;
import com.zt.plat.module.base.dal.dataobject.businessdictionarytype.BusinessDictionaryDataDO;
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
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.*;

/**
 * 组织架构物料 Service 实现类
 *
 * @author 后台管理-1
 */
@Service
@Validated
public class DepartmentMaterialServiceImpl implements DepartmentMaterialService {

    @Resource
    private DepartmentMaterialMapper departmentMaterialMapper;

    @Resource
    private MaterialInfomationMapper materialInfomationMapper;

    @Resource
    private MaterialClassesMapper materialClassesMapper;

    @Resource
    private BusinessDictionaryDataMapper businessDictionaryDataMapper;

    @Resource
    private MaterialHasPropertiesMapper materialHasPropertiesMapper;

    @Resource
    private MaterialPropertiesMapper materialPropertiesMapper;

    @Resource
    private DeptApi deptApi;

    @Override
    public DepartmentMaterialRespVO createDepartmentMaterial(DepartmentMaterialSaveReqVO createReqVO) {
        // 插入
        DepartmentMaterialDO departmentMaterial = BeanUtils.toBean(createReqVO, DepartmentMaterialDO.class);
        validateNotExists(departmentMaterial); // 简单验证不存在相同分类物料
        departmentMaterialMapper.insert(departmentMaterial);
        // 构造完整响应，方便前端直接刷新数据
        return CollUtil.getFirst(decorateDepartmentMaterials(Collections.singletonList(departmentMaterial)));
    }

    @Override
    public void updateDepartmentMaterial(DepartmentMaterialSaveReqVO updateReqVO) {
        // 校验存在
        validateDepartmentMaterialExists(updateReqVO.getId());
        // 更新
        DepartmentMaterialDO updateObj = BeanUtils.toBean(updateReqVO, DepartmentMaterialDO.class);
        validateNotExists(updateObj); // 简单验证不存在重复相同分类物料
        departmentMaterialMapper.updateById(updateObj);
    }

    @Override
    public void deleteDepartmentMaterial(Long id) {
        // 校验存在
        validateDepartmentMaterialExists(id);
        // 删除
        departmentMaterialMapper.deleteById(id);
    }

    @Override
    public void deleteDepartmentMaterialListByIds(List<Long> ids) {
        // 校验存在
        validateDepartmentMaterialExists(ids);
        // 删除
        departmentMaterialMapper.deleteByIds(ids);
    }

    private void validateDepartmentMaterialExists(List<Long> ids) {
        List<DepartmentMaterialDO> list = departmentMaterialMapper.selectByIds(ids);
        if (CollUtil.isEmpty(list) || list.size() != ids.size()) {
            throw exception(DEPARTMENT_MATERIAL_NOT_EXISTS);
        }
    }

    public void validateNotExists(DepartmentMaterialDO departmentMaterial) {
        Long cnt = departmentMaterialMapper.countExist(departmentMaterial);
        if (cnt != null && cnt > 0) {
            // 已存在未删除的同一条
            throw exception(DEPARTMENT_MATERIAL_EXISTS);
        }
    }

    private void validateDepartmentMaterialExists(Long id) {
        if (departmentMaterialMapper.selectById(id) == null) {
            throw exception(DEPARTMENT_MATERIAL_NOT_EXISTS);
        }
    }

    @Override
    public DepartmentMaterialRespVO getDepartmentMaterial(Long id) {
        DepartmentMaterialDO data = departmentMaterialMapper.selectById(id);
        if (data == null) {
            return null;
        }
        return CollUtil.getFirst(buildPropertyAggregates(decorateDepartmentMaterials(Collections.singletonList(data))));
    }

    @Override
    public List<DepartmentMaterialRespVO> getDepartmentMaterialByIds(List<Long> ids) {
        List<DepartmentMaterialDO> data = departmentMaterialMapper.selectByIds(ids);
        if (org.apache.commons.collections4.CollectionUtils.isEmpty(data)) {
            return null;
        }
        return decorateDepartmentMaterials(data);
    }

    @Override
    public PageResult<DepartmentMaterialRespVO> getDepartmentMaterialPage(DepartmentMaterialPageReqVO pageReqVO) {
        // 预处理物料名称/编码查询条件
        List<Long> matchedInfoIds = null;
        if (StrUtil.isNotBlank(pageReqVO.getMaterialName()) || StrUtil.isNotBlank(pageReqVO.getMaterialNumber())) {
            LambdaQueryWrapperX<MaterialInfomationDO> infoWrapper = new LambdaQueryWrapperX<>();
            infoWrapper.likeIfPresent(MaterialInfomationDO::getName, pageReqVO.getMaterialName())
                    .likeIfPresent(MaterialInfomationDO::getCode, pageReqVO.getMaterialNumber());
            List<MaterialInfomationDO> infoCandidates = materialInfomationMapper.selectList(infoWrapper);
            if (CollUtil.isEmpty(infoCandidates)) {
                return PageResult.empty();
            }
            matchedInfoIds = infoCandidates.stream().map(MaterialInfomationDO::getId).toList();
            pageReqVO.setInfomationIds(matchedInfoIds);
        }

        // 如果搜索物料编码，需要同时搜索中铜编码和中铝编码（作为物料属性存储）
        if (StrUtil.isNotBlank(pageReqVO.getMaterialNumber())) {
            // 优化：将分类筛选结果传入，在数据库层面就完成筛选，减少数据传输
            List<Long> codeMatchedIds = queryMaterialIdsByAttribute(pageReqVO.getMaterialNumber(), null);
            if (!CollUtil.isEmpty(codeMatchedIds)) {
                // 合并编码集合条件并去重
                List<Long> ids = Stream
                        .concat(pageReqVO.getInfomationIds().stream(), codeMatchedIds.stream())
                        .distinct()
                        .toList();
                pageReqVO.setInfomationIds(ids);
            }
        }

        // 判断部门是否是公司，非公司的部门需要显示所属公司下的所有物料信息
        if (pageReqVO.getIsCompany() != null && !pageReqVO.getIsCompany()) {
            List<DeptRespDTO> deptList = (deptApi.upFindCompanyNode(pageReqVO.getDeptId())).getData();
            if (!CollUtil.isEmpty(deptList)) {
                pageReqVO.setDeptIds(deptList.stream().map(DeptRespDTO::getId).toList()); // 设置部门查询范围
                pageReqVO.setDeptId(null); // 置空部门编号
            }
        }

        PageResult<DepartmentMaterialDO> pageResult = departmentMaterialMapper.selectPage(pageReqVO);
        // 重置，避免后续使用该入参出现意外的 in 条件
        pageReqVO.setInfomationIds(null);

        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty(pageResult.getTotal());
        }

        List<DepartmentMaterialRespVO> respList = decorateDepartmentMaterials(pageResult.getList());
        return new PageResult<>(respList, pageResult.getTotal());
    }

    private List<DepartmentMaterialRespVO> decorateDepartmentMaterials(List<DepartmentMaterialDO> sourceList) {
        if (CollUtil.isEmpty(sourceList)) {
            return Collections.emptyList();
        }

        // 关联数据准备
        Set<Long> infoIds = sourceList.stream()
                .map(DepartmentMaterialDO::getInfomationId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, MaterialInfomationDO> infoMap = infoIds.isEmpty()
                ? Collections.emptyMap()
                : materialInfomationMapper.selectBatchIds(infoIds).stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(MaterialInfomationDO::getId, Function.identity()));

        Set<Long> classIds = new HashSet<>();
        sourceList.forEach(item -> {
            if (item.getClassesId() != null) {
                classIds.add(item.getClassesId());
            }
            MaterialInfomationDO info = infoMap.get(item.getInfomationId());
            if (info != null && info.getClassesId() != null) {
                classIds.add(info.getClassesId());
            }
        });

        Map<Long, MaterialClassesDO> classCache = loadClassHierarchy(classIds);

        Set<Long> deptIds = sourceList.stream()
                .map(DepartmentMaterialDO::getDeptId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());

        Map<Long, DeptRespDTO> deptMap = Collections.emptyMap();
        if (CollUtil.isNotEmpty(deptIds)) {
            try {
                deptMap = CollectionUtils.convertMap(deptApi.getDeptList(deptIds).getCheckedData(), DeptRespDTO::getId);
            } catch (Exception ignore) {
                deptMap = Collections.emptyMap();
            }
        }

        Set<String> dictionaryValues = sourceList.stream()
                .map(DepartmentMaterialDO::getDictionaryDataValue)
                .filter(StrUtil::isNotBlank)
                .collect(Collectors.toSet());
        Map<String, BusinessDictionaryDataDO> dictionaryMap = dictionaryValues.isEmpty()
                ? Collections.emptyMap()
                : businessDictionaryDataMapper.selectListByValues(dictionaryValues).stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(BusinessDictionaryDataDO::getValue, Function.identity(), (exist, replace) -> exist));

        List<DepartmentMaterialRespVO> respList = new ArrayList<>(sourceList.size());
        for (DepartmentMaterialDO item : sourceList) {
            DepartmentMaterialRespVO respVO = BeanUtils.toBean(item, DepartmentMaterialRespVO.class);

            MaterialInfomationDO info = infoMap.get(item.getInfomationId());
            if (info != null) {
                respVO.setMaterialNumber(info.getCode());
                respVO.setMaterialName(info.getName());
            }

            Long targetClassId = item.getClassesId();
            if (targetClassId == null && info != null) {
                targetClassId = info.getClassesId();
            }
            if (targetClassId != null) {
                List<String> classPath = buildClassPath(targetClassId, classCache);
                if (!classPath.isEmpty()) {
                    respVO.setCategoryLargeName(classPath.get(0));
                    if (classPath.size() > 1) {
                        respVO.setCategoryMediumName(classPath.get(1));
                    }
                    if (classPath.size() > 2) {
                        respVO.setCategorySmallName(classPath.get(classPath.size() - 1));
                    }
                    respVO.setCategoryPath(String.join("/", classPath));
                }
            }

            DeptRespDTO deptRespDTO = deptMap.get(item.getDeptId());
            if (deptRespDTO != null) {
                respVO.setDeptName(deptRespDTO.getName());
            }

            BusinessDictionaryDataDO dictionaryData = dictionaryMap.get(respVO.getDictionaryDataValue());
            if (dictionaryData != null) {
                respVO.setDictionaryDataLabel(dictionaryData.getLabel());
            } else {
                respVO.setDictionaryDataLabel(respVO.getDictionaryDataValue());
            }

            respList.add(respVO);
        }
        return respList;
    }

    private Map<Long, MaterialClassesDO> loadClassHierarchy(Set<Long> initialIds) {
        if (CollUtil.isEmpty(initialIds)) {
            return Collections.emptyMap();
        }
        Map<Long, MaterialClassesDO> cache = new HashMap<>();
        Set<Long> pending = new HashSet<>(initialIds);
        while (!pending.isEmpty()) {
            List<MaterialClassesDO> fetched = materialClassesMapper.selectBatchIds(pending);
            pending = new HashSet<>();
            for (MaterialClassesDO cls : fetched) {
                if (cls == null || cache.containsKey(cls.getId())) {
                    continue;
                }
                cache.put(cls.getId(), cls);
                Long parentId = cls.getParentId();
                if (parentId != null && parentId > 0 && !cache.containsKey(parentId)) {
                    pending.add(parentId);
                }
            }
        }
        return cache;
    }

    private List<String> buildClassPath(Long classId, Map<Long, MaterialClassesDO> cache) {
        List<String> path = new ArrayList<>();
        MaterialClassesDO current = cache.get(classId);
        int guard = 0;
        while (current != null && guard++ < 10) {
            path.add(current.getName());
            Long parentId = current.getParentId();
            if (parentId == null || parentId == 0) {
                break;
            }
            current = cache.get(parentId);
        }
        Collections.reverse(path);
        return path;
    }


    private List<DepartmentMaterialRespVO> buildPropertyAggregates(List<DepartmentMaterialRespVO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }

        List<MaterialHasPropertiesDO> hasPropertiesList = materialHasPropertiesMapper.selectList(
                new LambdaQueryWrapperX<MaterialHasPropertiesDO>().in(MaterialHasPropertiesDO::getInfomationId,
                        list.stream().map(DepartmentMaterialRespVO::getInfomationId).toList()));
        if (CollUtil.isEmpty(hasPropertiesList)) {
            return list;
        }

        Set<Long> propertiesIds = hasPropertiesList.stream()
                .map(MaterialHasPropertiesDO::getPropertiesId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, MaterialPropertiesDO> propertiesMap = propertiesIds.isEmpty()
                ? Collections.emptyMap()
                : materialPropertiesMapper.selectBatchIds(propertiesIds).stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(MaterialPropertiesDO::getId, Function.identity()));

        Map<Long, List<MaterialHasPropertiesDO>> infoPropsMap = hasPropertiesList.stream()
                .collect(Collectors.groupingBy(MaterialHasPropertiesDO::getInfomationId));

        Map<Long, MaterialPropertiesAggregate> result = new HashMap<>();
        for (Map.Entry<Long, List<MaterialHasPropertiesDO>> entry : infoPropsMap.entrySet()) {
            Long infoId = entry.getKey();
            List<MaterialHasPropertiesDO> props = entry.getValue();
            // 按 sort 升序，保持 deterministic，重复 code 时保留首条
            props.sort(Comparator.comparing((MaterialHasPropertiesDO o) -> o.getSort() == null ? Long.MAX_VALUE : o.getSort())
                    .thenComparing(MaterialHasPropertiesDO::getPropertiesId, Comparator.nullsLast(Long::compareTo))
                    .thenComparing(MaterialHasPropertiesDO::getId, Comparator.nullsLast(Long::compareTo)));

            Map<String, Object> flatMap = new LinkedHashMap<>();
            for (MaterialHasPropertiesDO item : props) {
                MaterialPropertiesDO property = propertiesMap.get(item.getPropertiesId());
                String code = property != null ? property.getCode() : null;
                if (StrUtil.isBlank(code)) {
                    continue;
                }
                if (flatMap.containsKey(code)) {
                    continue;
                }
                flatMap.put(code, item.getValue());
            }

            result.put(infoId, new MaterialPropertiesAggregate(flatMap));
        }

        return list.stream().peek(item -> {
            Map<String, Object> flatMap = new LinkedHashMap<>();
            MaterialPropertiesAggregate aggregate = result.get(item.getInfomationId());
            if (aggregate != null && CollUtil.isNotEmpty(aggregate.getAttributes())) {
                aggregate.getAttributes().forEach((k, v) -> {
                    // 基础字段优先，存在同名则跳过属性值
                    if (!flatMap.containsKey(k)) {
                        flatMap.put(k, v);
                    }
                });
                item.setFlatAttributes(flatMap);
            }
        }).collect(Collectors.toList());
    }

    private record MaterialPropertiesAggregate(Map<String, Object> attributes) {
        public Map<String, Object> getAttributes() {
            return attributes;
        }
    }

    /**
     * 获取中铜编码和中铝编码的属性ID（使用本地缓存避免重复查询）
     */
    private static volatile List<Long> cachedCodePropertyIds = null;

    private List<Long> getCachedCodePropertyIds() {
        if (cachedCodePropertyIds == null) {
                synchronized (DepartmentMaterialServiceImpl.class) {
                if (cachedCodePropertyIds == null) {
                    List<MaterialPropertiesDO> properties = materialPropertiesMapper.selectList(
                            new LambdaQueryWrapperX<MaterialPropertiesDO>()
                                    .in(MaterialPropertiesDO::getCode, Arrays.asList(
                                            MasterDataPropertyDefinition.CHALCO_CODE.getCode(),
                                            MasterDataPropertyDefinition.ZHONGTONG_CODE.getCode()))
                    );
                    if (CollUtil.isNotEmpty(properties)) {
                        cachedCodePropertyIds = properties.stream()
                                .map(MaterialPropertiesDO::getId)
                                .filter(Objects::nonNull)
                                .collect(Collectors.toList());
                    } else {
                        cachedCodePropertyIds = Collections.emptyList();
                    }
                }
            }
        }
        return cachedCodePropertyIds;
    }

    /**
     * 根据物料编码或属性编码（中铜编码、中铝编码）查询物料ID列表
     * 优化版本：减少数据库查询次数，提升性能
     *
     * @param code          编码关键字（支持模糊查询）
     * @param classIdFilter 分类筛选的物料ID列表（可为null）
     * @return 匹配的物料ID列表
     */
    private List<Long> queryMaterialIdsByAttribute(String code, List<Long> classIdFilter) {
        Set<Long> materialIds = new HashSet<>();

        // 2. 查询中铜编码和中铝编码属性（使用缓存的属性ID）
        List<Long> propertyIds = getCachedCodePropertyIds();
        if (CollUtil.isNotEmpty(propertyIds)) {
            // 查询属性值表中匹配的物料
            List<MaterialHasPropertiesDO> hasProperties = materialHasPropertiesMapper.selectList(
                    new LambdaQueryWrapperX<MaterialHasPropertiesDO>()
                            .in(MaterialHasPropertiesDO::getPropertiesId, propertyIds)
                            .like(MaterialHasPropertiesDO::getValue, code)
                            .in(Objects.nonNull(classIdFilter), MaterialHasPropertiesDO::getInfomationId, classIdFilter)
            );

            if (CollUtil.isNotEmpty(hasProperties)) {
                materialIds.addAll(hasProperties.stream()
                        .map(MaterialHasPropertiesDO::getInfomationId)
                        .filter(Objects::nonNull)
                        .collect(Collectors.toSet()));
            }
        }

        return new ArrayList<>(materialIds);
    }
}