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

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSONObject;
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.PageResult;
import com.zt.plat.framework.common.util.object.BeanUtils;
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.MaterialInfomationPageReqVO;
import com.zt.plat.module.qms.resource.material.controller.vo.MaterialInfomationRespVO;
import com.zt.plat.module.qms.resource.material.controller.vo.MaterialInfomationSaveReqVO;
import com.zt.plat.module.qms.resource.material.controller.vo.query.MaterialInfomationQueryVO;
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.MaterialInfomationOrigin;
import groovy.util.logging.Slf4j;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
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;

    @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 Map<Long, Long> getStockQuantityByPdtIds(List<Long> pdtIds) {
        QueryWrapper<MaterialInfomationDO> queryWrapper = Wrappers.query(MaterialInfomationDO.class)
                .select("PDT_ID ,count(*) as quantity")
                .in("PDT_ID", pdtIds)
                .eq("USG_STS", 0)
                .groupBy("PDT_ID");
        List<Map<String, Object>> maps = materialInfomationMapper.selectMaps(queryWrapper);
        if (CollUtil.isEmpty(maps)) return new HashMap<>();

        return maps.stream().collect(Collectors.toMap(
                map -> (Long) map.get("PDT_ID"),
                map -> (Long) map.get("QUANTITY")
        ));
    }

    @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) {

        return materialInfomationMapper.selectOneWithPdtInfoByInfCode(queryVO);
    }

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

    @Override
    public void save(MaterialInfomationDO infomationDO) {
        materialInfomationMapper.insert(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> getMaterialInfomationsWithPdtInfoByIds(List<Long> ids) {
        return materialInfomationMapper.selectListWithPdtInfoByInfIds(ids);
    }

    @Override
    public MaterialInfomationDO saveMaterialInfomationByHzrdMtrlMake(MaterialProductDO product, BigDecimal totalOperationQuantity, Long productId) {
        MaterialInfomationDO infomationDO = BeanUtils.toBean(product, MaterialInfomationDO.class);
        infomationDO
                .setId(null)
                .setInitialVolume(totalOperationQuantity)
                .setRemainingVolume(totalOperationQuantity)
                .setProductId(productId)
                .setCode(sequenceUtil.genCode(MaterialConstants.SEQUENCE_INF_KEY))
                .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();
        infomationDO
                .setProductId(productId)
                .setCode(sequenceUtil.genCode(MaterialConstants.SEQUENCE_INF_KEY))
                .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, Long gongduanId, 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(gongduanId)
                    .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())
                    .setOrigin(MaterialInfomationOrigin.batch_inbound.name());
            // 生成编号
            String code = sequenceUtil.genCode(MaterialConstants.SEQUENCE_INF_KEY);
            infomationDO.setCode(code);
            infomationDOS.add(infomationDO);
        }
        this.saveBatch(infomationDOS);
        return infomationDOS;
    }

    @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 = getMaterialInfomationsByIds(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 = getMaterialInfomationsByIds(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.getMaterialInfomationsByIds(infomationIds);
        if (CollUtil.isEmpty(infomations)) return;
        List<Long> productIds = infomations.stream().map(MaterialInfomationDO::getProductId).toList();
        List<MaterialProductDO> products = materialProductService.getMaterialProductListByPdtIds(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);
    }
}