package com.zt.plat.module.qms.resource.material.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zt.plat.framework.common.exception.ServiceException;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.common.util.object.BeanUtils;
import com.zt.plat.module.qms.business.config.dal.dataobject.ConfigWarehouseLocationDO;
import com.zt.plat.module.qms.business.config.service.ConfigWarehouseLocationService;
import com.zt.plat.module.qms.core.code.SequenceUtil;
import com.zt.plat.module.qms.enums.AdjustType;
import com.zt.plat.module.qms.enums.LockType;
import com.zt.plat.module.qms.resource.material.constant.MaterialConstants;
import com.zt.plat.module.qms.resource.material.controller.vo.*;
import com.zt.plat.module.qms.resource.material.controller.vo.query.MaterialInfomationQueryVO;
import com.zt.plat.module.qms.resource.material.controller.vo.resp.MaterialInfomationLedgerRespVO;
import com.zt.plat.module.qms.resource.material.dal.dataobject.*;
import com.zt.plat.module.qms.resource.material.dal.mapper.MaterialInfomationMapper;
import com.zt.plat.module.qms.resource.material.enums.MaterialInfomationOneBusinessType;
import com.zt.plat.module.qms.resource.material.enums.MaterialInfomationOrigin;
import com.zt.plat.module.system.api.dept.DeptApi;
import com.zt.plat.module.system.api.dept.dto.DeptRespDTO;
import groovy.util.logging.Slf4j;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.zt.plat.module.qms.enums.ErrorCodeConstants.MATERIAL_INFOMATION_NOT_EXISTS;

/**
 * 物料实例 Service 实现类
 *
 * @author 后台管理
 */
@lombok.extern.slf4j.Slf4j
@Slf4j
@Service
@Validated
public class MaterialInfomationServiceImpl implements MaterialInfomationService {

    @Resource
    private MaterialInfomationMapper materialInfomationMapper;
    @Autowired
    private SequenceUtil sequenceUtil;
    @Autowired
    private MaterialProductService materialProductService;
    @Autowired
    private DeptApi deptApi;
    @Autowired
    private ConfigWarehouseLocationService configWarehouseLocationService;

    @Override
    public MaterialInfomationRespVO createMaterialInfomation(MaterialInfomationSaveReqVO createReqVO) {
        // 插入
        MaterialInfomationDO materialInfomation = BeanUtils.toBean(createReqVO, MaterialInfomationDO.class);
        materialInfomationMapper.insert(materialInfomation);
        // 返回
        return BeanUtils.toBean(materialInfomation, MaterialInfomationRespVO.class);
    }

    @Override
    public void updateMaterialInfomation(MaterialInfomationSaveReqVO updateReqVO) {
        // 校验存在
        validateMaterialInfomationExists(updateReqVO.getId());
        // 更新
        MaterialInfomationDO updateObj = BeanUtils.toBean(updateReqVO, MaterialInfomationDO.class);
        materialInfomationMapper.updateById(updateObj);
    }

    @Override
    public void deleteMaterialInfomation(Long id) {
        // 校验存在
        validateMaterialInfomationExists(id);
        // 删除
        materialInfomationMapper.deleteById(id);
    }

    @Override
        public void deleteMaterialInfomationListByIds(List<Long> ids) {
        // 校验存在
        validateMaterialInfomationExists(ids);
        // 删除
        materialInfomationMapper.deleteByIds(ids);
        }

    private void validateMaterialInfomationExists(List<Long> ids) {
        List<MaterialInfomationDO> list = materialInfomationMapper.selectByIds(ids);
        if (CollUtil.isEmpty(list) || list.size() != ids.size()) {
            throw exception(MATERIAL_INFOMATION_NOT_EXISTS);
        }
    }

    private void validateMaterialInfomationExists(Long id) {
        if (materialInfomationMapper.selectById(id) == null) {
            throw exception(MATERIAL_INFOMATION_NOT_EXISTS);
        }
    }

    @Override
    public MaterialInfomationDO getMaterialInfomation(Long id) {
        return materialInfomationMapper.selectById(id);
    }

    @Override
    public PageResult<MaterialInfomationDO> getMaterialInfomationPage(MaterialInfomationPageReqVO pageReqVO) {
        return materialInfomationMapper.selectPage(pageReqVO);
    }

    @Override
    public void saveBatch(List<MaterialInfomationDO> infomationDOS) {
        materialInfomationMapper.insertBatch(infomationDOS);
    }

    @Override
    public PageResult<MaterialInfomationRespVO> getMaterialInfomationPageWithPdtInfo(MaterialInfomationPageReqVO pageReqVO) {
        PageResult<MaterialInfomationRespVO> pageResult = materialInfomationMapper.selectPageWithPdtInfo(pageReqVO);
        if (pageReqVO.getNeedDetectExistHazardousMake()) {
            List<MaterialInfomationRespVO> infomationRespVOS = pageResult.getList();
            if (CollUtil.isEmpty(infomationRespVOS)) return pageResult;
            List<Long> infIds = infomationRespVOS.stream().map(MaterialInfomationRespVO::getId).toList();
            Map<Long, Boolean> isExistHzrdMakeInfo = materialInfomationMapper.checkIsExistHazardousMakeInfo(infIds);

            for (MaterialInfomationRespVO infomationRespVO : infomationRespVOS) {
                infomationRespVO.setExistHazardousMake(isExistHzrdMakeInfo.get(infomationRespVO.getId()));
            }
            pageResult.setList(infomationRespVOS);
        }
        return pageResult;
    }

    @Override
    public MaterialInfomationRespVO getMaterialInfomationWithPdtInfoByInfCode(MaterialInfomationQueryVO queryVO) {
        MaterialInfomationRespVO infomation = materialInfomationMapper.selectOneWithPdtInfoByInfCode(queryVO);
        if (infomation == null) throw exception(MATERIAL_INFOMATION_NOT_EXISTS);
        MaterialInfomationOneBusinessType businessType = queryVO.getBusinessType();
        switch (businessType) {
            // 出库时校验是否已出库
            case outbound -> {
                if (infomation.getUsageStatus() == 1) throw new ServiceException(1_032_160_000, "该物料已出库");
            }
            // 使用记录时校验是否已用完
            case use_record -> {
                // 需要校验是否危化品
                Long productId = infomation.getProductId();
                MaterialProductDO product = materialProductService.getMaterialProduct(productId);
                if (product.getHazardous() != 1) throw new ServiceException(1_032_160_000, "该物料是危化品");
                if (infomation.getUseEndFlag() == 1) throw new ServiceException(1_032_160_000, "该物料已用完");
            }
            // 用完标记时校验是否已用完并标记
            case used_mark -> {
                if (infomation.getUseEndFlag() == 1) throw new ServiceException(1_032_160_000, "该物料已用完并标记");
            }
            // 开封时校验是否已开封
            case open_seal -> {
                // 需要校验是否要开封
                Long productId = infomation.getProductId();
                MaterialProductDO product = materialProductService.getMaterialProduct(productId);
                if (product.getOpenDueFlag() != 1) throw new ServiceException(1_032_160_000, "该物料不需要开封");
                if (infomation.getOpenStatus() == 1) throw new ServiceException(1_032_160_000, "该物料已开封");
            }
            case inventory_check -> {
                // 检验物料是否已经盘点过
            }
        }
        return infomation;
    }

    @Override
    public List<MaterialInfomationDO> getListByIds(List<Long> infomationIds) {
        return materialInfomationMapper.selectByIds(infomationIds);
    }

    @Override
    public void save(MaterialInfomationDO infomationDO) {
        materialInfomationMapper.insert(infomationDO);
        if (infomationDO.getBelongDepartmentId() == null || StrUtil.isEmpty(infomationDO.getBelongDepartmentName())) {
            infomationDO.setBelongDepartmentId(infomationDO.getDeptId()).setBelongDepartmentName(infomationDO.getDeptName());
            this.updateById(infomationDO);
        }
    }

    @Override
    public void updateById(MaterialInfomationDO infomationDO) {
        materialInfomationMapper.updateById(infomationDO);
    }

    @Override
    public MaterialInfomationRespVO getMaterialInfomationWithPdtInfoByInfId(Long id) {
        return materialInfomationMapper.selectOneWithPdtInfoByInfId(id);
    }

    @Override
    public void updateBatch(List<MaterialInfomationDO> infomationDOS) {
        materialInfomationMapper.updateBatch(infomationDOS);
    }

    @Override
    public List<MaterialInfomationRespVO> getRespListWithPdtInfoByIds(List<Long> ids) {
        return materialInfomationMapper.selectListWithPdtInfoByInfIds(ids);
    }

    @Override
    public MaterialInfomationDO saveMaterialInfomationByHzrdMtrlMake(MaterialProductDO product, BigDecimal totalOperationQuantity) {
        MaterialInfomationDO infomationDO = BeanUtils.toBean(product, MaterialInfomationDO.class);
        infomationDO
                .setId(null)
                .setInitialVolume(totalOperationQuantity)
                .setRemainingVolume(totalOperationQuantity)
                .setProductId(product.getId())
                .setCode(this.getInfomationCode(product.getCustomConfig()))
                .setManufacturerDate(LocalDate.now())
                .setExpirationDate(LocalDate.now().plusDays(product.getDue()))
                .setUsageStatus(0)
                .setUseEndFlag(0)
                .setOrigin(MaterialInfomationOrigin.hazardous_material_make.name());
        this.save(infomationDO);
        return infomationDO;
    }

    @Override
    public MaterialInfomationDO saveMaterialInfomationByStdSolMake(Long productId, MaterialStandardSolutionDO mtrlStandSol) {
        MaterialInfomationDO infomationDO = new MaterialInfomationDO();
        MaterialProductDO product = materialProductService.getMaterialProduct(productId);
        infomationDO
                .setProductId(productId)
                .setCode(this.getInfomationCode(product.getCustomConfig()))
                .setManufacturerDate(LocalDate.from(mtrlStandSol.getMakeDate()))
                .setExpirationDate(LocalDate.from(mtrlStandSol.getDueDate()))
                .setUsageStatus(0)
                .setUseEndFlag(0)
                .setOrigin(MaterialInfomationOrigin.standard_solution_make.name());
        this.save(infomationDO);
        return infomationDO;
    }

    @Override
    public List<MaterialInfomationDO> saveMaterialInfomationsByBatInb(Long locationId, BigDecimal reqQuantity, MaterialBatchDO gong, MaterialProductDO product, MaterialBatchDO batch) {
        List<MaterialInfomationDO> infomationDOS = new ArrayList<>();
        for (int i = 0; i < reqQuantity.intValue(); i++) {
            MaterialInfomationDO infomationDO = new MaterialInfomationDO();
            infomationDO
                    .setProductId(product.getId()).setBatchId(batch.getId())
                    .setLocationId(locationId)
                    .setGongduanId(gong.getId())
                    .setName(product.getName())
                    .setSpecification(product.getSpecification()).setModelNo(product.getModelNo())
                    .setParameter(product.getParameter())
                    .setManufacturer(product.getManufacturer()).setUnit(product.getUnit())
                    .setDue(product.getDue()).setOpenDueAfter(product.getOpenDueAfter())
                    .setPublishStatus(0)
                    .setUsageStatus(0)
                    .setOpenStatus(0)
                    .setManufacturerDate(batch.getManufacturerDate()).setExpirationDate(batch.getDueDate())
                    .setInitialVolume(product.getStandardCapacity())
                    .setRemainingVolume(product.getStandardCapacity())
                    .setBelongDepartmentId(gong.getAssignDepartmentId())
                    .setBelongDepartmentName(gong.getAssignDepartmentName())
                    .setOrigin(MaterialInfomationOrigin.batch_inbound.name());
            // 生成编号 - 需要检查是否有自定义编号
            String code = getInfomationCode(product.getCustomConfig());
            infomationDO.setCode(code);
            infomationDOS.add(infomationDO);
        }
        this.saveBatch(infomationDOS);
        return infomationDOS;
    }

    private String getInfomationCode(String customConfig) {
        cn.hutool.json.JSONObject customConfigObj = JSONUtil.parseObj(customConfig);
        String sequenceRule = customConfigObj.getStr("sequenceRule");
        String sequenceKey = StrUtil.isEmpty(sequenceRule) ? MaterialConstants.SEQUENCE_INF_KEY : sequenceRule;
        return sequenceUtil.genCode(sequenceKey);
    }

    @Override
    public void updateByIds(List<Long> infomationIds, MaterialInfomationDO materialInfomationDO) {
        materialInfomationMapper.update(materialInfomationDO, Wrappers.lambdaQuery(MaterialInfomationDO.class)
                .in(MaterialInfomationDO::getId, infomationIds));
    }

    @Override
    public void updateInfomationInbOrOutbStatus(List<MaterialLifecycleDetailDO> infomations, AdjustType adjustType) {
        List<Long> infIds = infomations.stream().map(MaterialLifecycleDetailDO::getInfomationId).toList();
        List<MaterialInfomationDO> infomationDOS = getListByIds(infIds);
        for (MaterialInfomationDO infomationDO : infomationDOS) {
            switch (adjustType) {
                case add:
                    infomationDO.setUsageStatus(1);
                    break;
                case subtract:
                    infomationDO.setUsageStatus(0);
                    break;
            }
        }
        this.updateBatch(infomationDOS);

    }

    @Override
    public void lockInfomationInbOrOutbStatus(List<MaterialLifecycleDetailDO> infomations, LockType lockType) {
        List<Long> infIds = infomations.stream().map(MaterialLifecycleDetailDO::getInfomationId).toList();
        List<MaterialInfomationDO> infomationDOS = getListByIds(infIds);
        for (MaterialInfomationDO infomationDO : infomationDOS) {
            switch (lockType) {
                case lock:
                    if (infomationDO.getUsageStatus() == 2)
                        throw new ServiceException(1_032_160_000, String.format("物料【%s】已被锁定", infomationDO.getCode()));
                    infomationDO.setUsageStatus(2);
                    break;
                case unlock:
                    infomationDO.setUsageStatus(0);
                    break;
            }
        }
        this.updateBatch(infomationDOS);
    }

    @Override
    public void openSealTreatment(List<Long> infomationIds, MaterialUseEndReuseDO openInfo) {
        List<MaterialInfomationDO> infomations = this.getListByIds(infomationIds);
        if (CollUtil.isEmpty(infomations)) return;
        List<Long> productIds = infomations.stream().map(MaterialInfomationDO::getProductId).toList();
        List<MaterialProductDO> products = materialProductService.getListByIds(productIds);
        if (CollUtil.isEmpty(products)) return;
        Map<Long, MaterialProductDO> productByIdMap = products.stream().collect(Collectors.toMap(MaterialProductDO::getId, Function.identity()));
        for (MaterialInfomationDO infomation : infomations) {
            // 批次入库的且开封后保质期变化的才有开封操作
            MaterialProductDO product = productByIdMap.get(infomation.getProductId());
            if (!MaterialInfomationOrigin.batch_inbound.name().equals(infomation.getOrigin())
                    || product.getOpenDueFlag() == 0) throw new ServiceException(1_032_160_000, String.format("物料【%s】无开封操作", infomation.getCode()));
            infomation.setOpenDate(openInfo.getOperatorDate()).setOpenStatus(1)
                    .setOpenUserId(openInfo.getOperatorId()).setOpenUserName(openInfo.getOperator());
            Integer openDueAfter = product.getOpenDueAfter();
            infomation.setExpirationDate(infomation.getManufacturerDate().plusDays(openDueAfter));
        }
        this.updateBatch(infomations);
    }

    @Override
    public Integer updateExpiredMaterialStatus() {
        LocalDate today = LocalDate.now();

        // 查询所有已过期的物料
        List<MaterialInfomationDO> expiredMaterials = materialInfomationMapper.selectList(
                Wrappers.lambdaQuery(MaterialInfomationDO.class)
                        .eq(MaterialInfomationDO::getExpirationFlag, "0")
                        .lt(MaterialInfomationDO::getExpirationDate, today)
        );

        if (CollUtil.isEmpty(expiredMaterials)) {
            log.info("[updateExpiredMaterialStatus] 今日 ({}) 没有发现过期的物料", today);
            return 0;
        }

        // 批量更新过期状态
        List<MaterialInfomationDO> updateList = expiredMaterials.stream().peek(
                material -> material.setExpirationFlag("1")).toList();

        this.updateBatch(updateList);

        log.info("[updateExpiredMaterialStatus] 今日 ({}) 更新了 {} 个物料的过期状态", today, updateList.size());
        return updateList.size();
    }

    @Override
    public List<MaterialInfoWithBizConfigVO> getListNeedByRule(JSONObject params) {
        return materialInfomationMapper.getListNeedByRule(params);
    }

    @Override
    public PageResult<MaterialInfomationLedgerRespVO> getMaterialInventoryLedgerPage(MaterialInfomationPageReqVO pageReqVO) {
        // 获取主信息
        PageResult<MaterialInfomationLedgerRespVO> result = materialInfomationMapper.selectInventoryLedgerPage(pageReqVO);
        List<MaterialInfomationLedgerRespVO> list = result.getList();
        if (CollUtil.isEmpty(list)) return result;
        // 大类信息
        enrichProductInfo(list);

        // 部门信息（包括上级部门）
        enrichDepartmentInfo(list);

        // 存放位置信息
        enrichLocationInfo(list);

        // 累加上级部门库存数量
        calculateSuperiorDepartmentInventory(list);

        return result.setList(list);
    }

    @Override
    public List<MaterialInfomationDO> getCheckInfsByCheckLfc(MaterialLifecycleRespVO lfcWithDtl) {
        LocalDateTime createTime = lfcWithDtl.getCreateTime();
        cn.hutool.json.JSONObject customConfig = JSONUtil.parseObj(lfcWithDtl.getCustomConfig());
        cn.hutool.json.JSONArray jsonDepartmentIds = customConfig.getJSONArray("departmentIds");
        cn.hutool.json.JSONArray jsonWarehouseIds = customConfig.getJSONArray("warehouseIds");
        List<Long> departmentIds = CollUtil.isEmpty(jsonWarehouseIds) ? List.of() : jsonDepartmentIds.toList(Long.class);
        List<Long> warehouseIds = CollUtil.isEmpty(jsonWarehouseIds) ? List.of() : jsonWarehouseIds.toList(Long.class);
        List<MaterialInventoryCheckItemRespVO> checkItems = lfcWithDtl.getCheckItems();
        Set<Long> pdtIds = checkItems.stream().map(MaterialInventoryCheckItemRespVO::getProductId).collect(Collectors.toSet());
        List<ConfigWarehouseLocationDO> locations = configWarehouseLocationService.getLocationsByWarehouseIds(warehouseIds);
        LambdaQueryWrapper<MaterialInfomationDO> wrapper = Wrappers.lambdaQuery(MaterialInfomationDO.class)
                .ne(MaterialInfomationDO::getUsageStatus, 1)
                .le(MaterialInfomationDO::getCreateTime, createTime);
        // 部门不参与数据过滤
        /*if (CollUtil.isNotEmpty(departmentIds))
            wrapper.in(MaterialInfomationDO::getDeptId, departmentIds);*/
        if (CollUtil.isNotEmpty(pdtIds))
            wrapper.in(MaterialInfomationDO::getProductId, pdtIds);
        if (CollUtil.isNotEmpty(locations))
            wrapper.in(MaterialInfomationDO::getLocationId, locations.stream().map(ConfigWarehouseLocationDO::getId).toList());
        return materialInfomationMapper.selectList(wrapper);
    }

    private void calculateSuperiorDepartmentInventory(List<MaterialInfomationLedgerRespVO> list) {
        // 按上级部门 ID 分组，统计每个上级部门的总库存
        Map<Long, BigDecimal> superiorDeptInventoryMap = list.stream()
                .filter(item -> item.getCompanyId() != null)
                .collect(Collectors.groupingBy(
                        MaterialInfomationLedgerRespVO::getCompanyId,
                        Collectors.reducing(
                                BigDecimal.ZERO,
                                MaterialInfomationLedgerRespVO::getDepartmentInventoryQuantity,
                                BigDecimal::add
                        )
                ));

        // 将累加结果设置到每条记录中
        list.forEach(item -> {
            BigDecimal totalInventory = superiorDeptInventoryMap.get(item.getCompanyId());
            item.setInventoryQuantity(totalInventory != null ? totalInventory : BigDecimal.ZERO);
        });
    }

    private void enrichLocationInfo(List<MaterialInfomationLedgerRespVO> list) {
        // 存放位置信息
        List<Long> locationIds = list.stream().map(MaterialInfomationLedgerRespVO::getLocationId).toList();
        if (CollUtil.isEmpty(locationIds)) return;
        List<ConfigWarehouseLocationDO> locationDOS = configWarehouseLocationService.getListByIds(locationIds);
        if (CollUtil.isEmpty(locationDOS)) return;
        Map<Long, ConfigWarehouseLocationDO> locationMap = locationDOS.stream().collect(Collectors.toMap(ConfigWarehouseLocationDO::getId, Function.identity()));
        list.forEach(item -> {
            ConfigWarehouseLocationDO locationDO = locationMap.get(item.getLocationId());
            item.setLocationName(locationDO.getName())
                    .setLocationCode(locationDO.getCode());
        });
    }

    private void enrichDepartmentInfo(List<MaterialInfomationLedgerRespVO> list) {
        // 部门信息
        List<Long> deptIds = list.stream().map(MaterialInfomationLedgerRespVO::getDepartmentId).toList();
        CommonResult<List<DeptRespDTO>> deptListResult = deptApi.getDeptList(deptIds);
        List<DeptRespDTO> deptList = deptListResult.getData();
        if (CollUtil.isEmpty(deptList)) return;
        Map<Long, DeptRespDTO> deptMap = deptList.stream().collect(Collectors.toMap(DeptRespDTO::getId, Function.identity()));
        list.forEach(item -> {
            DeptRespDTO dept = deptMap.get(item.getDepartmentId());
            item.setDepartmentName(dept.getName());
        });
        List<Long> deptPrnIds = deptList.stream().map(DeptRespDTO::getParentId).toList();
        CommonResult<List<DeptRespDTO>> deptPrnListResult = deptApi.getDeptList(deptPrnIds);
        List<DeptRespDTO> deptPrnList = deptPrnListResult.getData();
        if (CollUtil.isEmpty(deptPrnList)) return;
        Map<Long, DeptRespDTO> deptPrnMap = deptPrnList.stream().collect(Collectors.toMap(DeptRespDTO::getId, Function.identity()));
        list.forEach(item -> {
            DeptRespDTO dept = deptMap.get(item.getDepartmentId());
            DeptRespDTO deptPrn = deptPrnMap.get(dept.getParentId());
            item.setCompanyId(deptPrn.getId()).setCompanyName(deptPrn.getName());
        });
    }

    private void enrichProductInfo(List<MaterialInfomationLedgerRespVO> list) {
        List<Long> productIds = list.stream().map(MaterialInfomationLedgerRespVO::getProductId).toList();
        List<MaterialProductDO> productList = materialProductService.getListByIds(productIds);
        if (CollUtil.isEmpty(productList)) return;
        Map<Long, MaterialProductDO> productMap = productList.stream().collect(Collectors.toMap(MaterialProductDO::getId, Function.identity()));
        list.forEach(item -> {
            MaterialProductDO productDO = productMap.get(item.getProductId());
            item.setProductName(productDO.getName())
                    .setProductCode(productDO.getCode())
                    .setSpecification(productDO.getSpecification()).setModelNo(productDO.getModelNo())
                    .setProductCustomConfig(JSONUtil.parseObj(productDO.getCustomConfig()));
        });
    }
}