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

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.framework.security.core.LoginUser;
import com.zt.plat.framework.security.core.util.SecurityFrameworkUtils;
import com.zt.plat.module.qms.resource.material.controller.vo.*;
import com.zt.plat.module.qms.resource.material.controller.vo.query.MaterialUseRecordWithMakeInfoQueryVO;
import com.zt.plat.module.qms.resource.material.controller.vo.resp.MaterialUseRecordWithMakeInfoRespVO;
import com.zt.plat.module.qms.resource.material.dal.dataobject.*;
import com.zt.plat.module.qms.resource.material.dal.mapper.MaterialUseRecordDetailMapper;
import com.zt.plat.module.qms.resource.material.enums.MaterialInboundType;
import com.zt.plat.module.qms.resource.material.enums.MaterialOutboundType;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.math.BigDecimal;
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_USE_RECORD_DETAIL_NOT_EXISTS;

/**
 * 使用记录配置关系,用于处理危险品溶液配置关系 Service 实现类
 *
 * @author 后台管理
 */
@Service
@Validated
public class MaterialUseRecordDetailServiceImpl implements MaterialUseRecordDetailService {

    @Resource
    private MaterialUseRecordDetailMapper materialUseRecordDetailMapper;
    @Autowired
    private MaterialInfomationService materialInfomationService;
    @Autowired
    private MaterialUseRecordService materialUseRecordService;
    @Autowired
    private MaterialProductService materialProductService;
    @Autowired
    private MaterialInventoryOutboundService materialInventoryOutboundService;
    @Autowired
    private MaterialInventoryInboundService materialInventoryInboundService;

    @Override
    public MaterialUseRecordDetailRespVO createMaterialUseRecordDetail(MaterialUseRecordDetailSaveReqVO createReqVO) {
        // 插入
        MaterialUseRecordDetailDO materialUseRecordDetail = BeanUtils.toBean(createReqVO, MaterialUseRecordDetailDO.class);
        materialUseRecordDetailMapper.insert(materialUseRecordDetail);
        // 返回
        return BeanUtils.toBean(materialUseRecordDetail, MaterialUseRecordDetailRespVO.class);
    }

    @Override
    public void updateMaterialUseRecordDetail(MaterialUseRecordDetailSaveReqVO updateReqVO) {
        // 校验存在
        validateMaterialUseRecordDetailExists(updateReqVO.getId());
        // 更新
        MaterialUseRecordDetailDO updateObj = BeanUtils.toBean(updateReqVO, MaterialUseRecordDetailDO.class);
        materialUseRecordDetailMapper.updateById(updateObj);
    }

    @Override
    public void deleteMaterialUseRecordDetail(Long id) {
        // 校验存在
        validateMaterialUseRecordDetailExists(id);
        // 删除
        materialUseRecordDetailMapper.deleteById(id);
    }

    @Override
        public void deleteMaterialUseRecordDetailListByIds(List<Long> ids) {
        // 校验存在
        validateMaterialUseRecordDetailExists(ids);
        // 删除
        materialUseRecordDetailMapper.deleteByIds(ids);
        }

    private void validateMaterialUseRecordDetailExists(List<Long> ids) {
        List<MaterialUseRecordDetailDO> list = materialUseRecordDetailMapper.selectByIds(ids);
        if (CollUtil.isEmpty(list) || list.size() != ids.size()) {
            throw exception(MATERIAL_USE_RECORD_DETAIL_NOT_EXISTS);
        }
    }

    private void validateMaterialUseRecordDetailExists(Long id) {
        if (materialUseRecordDetailMapper.selectById(id) == null) {
            throw exception(MATERIAL_USE_RECORD_DETAIL_NOT_EXISTS);
        }
    }

    @Override
    public MaterialUseRecordDetailDO getMaterialUseRecordDetail(Long id) {
        return materialUseRecordDetailMapper.selectById(id);
    }

    @Override
    public PageResult<MaterialUseRecordDetailDO> getMaterialUseRecordDetailPage(MaterialUseRecordDetailPageReqVO pageReqVO) {
        return materialUseRecordDetailMapper.selectPage(pageReqVO);
    }

    @Transactional
    @Override
    public void saveBatch(List<MaterialUseRecordDetailDO> recordDetailDOS) {
        materialUseRecordDetailMapper.insertBatch(recordDetailDOS);
    }

    @Override
    public List<MaterialUseRecordWithMakeInfoRespVO> getMaterialMakeInfoUseRecordDetail(MaterialUseRecordWithMakeInfoQueryVO queryVO) {
        Long infomationId = queryVO.getInfomationId();
        Long recordId = queryVO.getRecordId();
        LambdaQueryWrapper<MaterialUseRecordDetailDO> wrapper = new LambdaQueryWrapper<>();
        if (infomationId != null) {
            // 根据物料id查询
            List<MaterialUseRecordDetailDO> detailDOS = materialUseRecordDetailMapper.selectList(Wrappers.lambdaQuery(MaterialUseRecordDetailDO.class)
                    .eq(MaterialUseRecordDetailDO::getInfomationId, infomationId)
                    .or()
                    .eq(MaterialUseRecordDetailDO::getTargetInfomationId, infomationId));
            if (CollUtil.isEmpty(detailDOS)) return List.of();
            Set<Long> targetInfomationIds = detailDOS.stream().map(MaterialUseRecordDetailDO::getTargetInfomationId).collect(Collectors.toSet());
            wrapper = Wrappers.lambdaQuery(MaterialUseRecordDetailDO.class)
                    .in(MaterialUseRecordDetailDO::getTargetInfomationId, targetInfomationIds);

        } else if (recordId != null) {
            // 根据使用记录id查询
            wrapper = Wrappers.lambdaQuery(MaterialUseRecordDetailDO.class)
                    .eq(MaterialUseRecordDetailDO::getRecordId, recordId);
        }
        // 获取物料存在的全部配置
        List<MaterialUseRecordDetailDO> recordDetailDOS = materialUseRecordDetailMapper.selectList(
                wrapper.orderByDesc(MaterialUseRecordDetailDO::getId));
        if (CollUtil.isEmpty(recordDetailDOS)) return List.of();
        // 获取配置生成的物料
        Set<Long> targetInfomationIds = recordDetailDOS.stream().map(MaterialUseRecordDetailDO::getTargetInfomationId)
                .collect(Collectors.toSet());
        List<MaterialInfomationRespVO> targetInfomationRespVOS = materialInfomationService
                .getRespListWithPdtInfoByIds(new ArrayList<>(targetInfomationIds));
        if (CollUtil.isEmpty(targetInfomationRespVOS)) return List.of();
        // 获取使用记录
        Set<Long> recordIds = recordDetailDOS.stream().map(MaterialUseRecordDetailDO::getRecordId).collect(Collectors.toSet());
        List<MaterialUseRecordRespVO> recordRespVOS = materialUseRecordService.getUseRecordsWithMaterialInfoByIds(new ArrayList<>(recordIds));

        List<MaterialUseRecordWithMakeInfoRespVO> result = new ArrayList<>();
        Map<Long, List<MaterialUseRecordDetailDO>> detailMapByTargetInfId = recordDetailDOS.stream().collect(Collectors.groupingBy(MaterialUseRecordDetailDO::getTargetInfomationId));
        Map<Long, MaterialInfomationRespVO> targetInfoRespVOMapById = targetInfomationRespVOS.stream().collect(Collectors.toMap(MaterialInfomationRespVO::getId, Function.identity()));
        Map<Long, MaterialUseRecordRespVO> recordRespVOMap = recordRespVOS.stream()
                .collect(Collectors.toMap(MaterialUseRecordRespVO::getId, Function.identity()));
        detailMapByTargetInfId.forEach((targetInfId, detailDOS) -> {
            MaterialUseRecordWithMakeInfoRespVO respVO = new MaterialUseRecordWithMakeInfoRespVO();
            respVO.setTargetInfo(targetInfoRespVOMapById.get(targetInfId));
            List<MaterialUseRecordRespVO> makeRecordVOS = detailDOS.stream()
                    .map(MaterialUseRecordDetailDO::getRecordId)
                    .map(recordRespVOMap::get)
                    .filter(Objects::nonNull)
                    .toList();
            respVO.setMakeInfoList(makeRecordVOS);
            result.add(respVO);
        });

        return result;
    }

    @Override
    public Boolean checkIsExistMakeInfoByRecordIds(List<Long> recordIds) {
        return materialUseRecordDetailMapper.exists(Wrappers.lambdaQuery(MaterialUseRecordDetailDO.class)
                .in(MaterialUseRecordDetailDO::getRecordId, recordIds));
    }

    @Override
    public List<MaterialUseRecordDetailDO> getMaterialUseRecordDetailsByInfIds(List<Long> infIds) {

        return materialUseRecordDetailMapper.selectList(Wrappers.lambdaQuery(MaterialUseRecordDetailDO.class)
                .in(MaterialUseRecordDetailDO::getInfomationId, infIds)
                .or()
                .in(MaterialUseRecordDetailDO::getTargetInfomationId, infIds));
    }

    @Transactional
    @Override
    public MaterialInfomationRespVO makeHazardousMaterial(MaterialHazardousMakeSaveReqVO makeReqVO) {

        List<MaterialUseRecordSaveReqVO> records = makeReqVO.getRecords();
        BigDecimal totalOperationQuantity = records.stream()
                .map(MaterialUseRecordSaveReqVO::getOperationQuantity)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        Long productId = makeReqVO.getProductId();
        MaterialProductDO product = materialProductService.getMaterialProduct(productId);
        if (product == null) throw new ServiceException(1_032_160_000, "配置大类不存在");
        if (totalOperationQuantity.compareTo(product.getStandardCapacity()) > 0) throw new ServiceException(1_032_160_000, "配置的总容量超过了额定容量");
        Set<Long> infIds = records.stream().map(MaterialUseRecordSaveReqVO::getInfomationId).collect(Collectors.toSet());
        List<MaterialInfomationDO> infomations = materialInfomationService.getListByIds(new ArrayList<>(infIds));
        if (CollUtil.isEmpty(infomations)) throw new ServiceException(1_032_160_000, "物料实例不存在");

        Map<Long, List<MaterialUseRecordSaveReqVO>> recordMapByInfId = records.stream().collect(Collectors.groupingBy(MaterialUseRecordSaveReqVO::getInfomationId));
        for (MaterialInfomationDO infomation : infomations) {
            if (infomation.getRemainingVolume() == null) throw new ServiceException(1_032_160_000, String.format("物料【%s】不存在剩余量", infomation.getCode()));
            BigDecimal reduce = recordMapByInfId.get(infomation.getId()).stream().map(MaterialUseRecordSaveReqVO::getOperationQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
            if (reduce.compareTo(infomation.getRemainingVolume()) > 0) throw new ServiceException(1_032_160_000, "使用量不能大于剩余量");
        }
        // 保存使用记录
        List<MaterialUseRecordDO> recordDOS = saveMaterialUseRecordDOS(infomations, records);

        // 更新物料剩余量
        updateInfomationsRemainingVolume(recordDOS, infomations);

        // 生成物料实例
        // 入库
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        assert loginUser != null;
        String userNickname = SecurityFrameworkUtils.getLoginUserNickname();
        MaterialInventoryInboundDO inbound = new MaterialInventoryInboundDO()
                .setTitle("危化品配置后入库")
                .setBusinessType(MaterialInboundType.hzrd_make_inbound.getName())
                .setApplyUser(userNickname).setApplyUserId(loginUser.getId())
                .setApplyDepartment(loginUser.getVisitDeptName()).setApplyDepartmentId(loginUser.getVisitDeptId())
                .setApplyTime(LocalDateTime.now());
        MaterialInfomationDO infomationDO = materialInventoryInboundService.inboundInfomation(inbound, product, totalOperationQuantity, null).get(0);
        // 保存配置明细
        List<MaterialUseRecordDetailDO> recordDetailDOS = recordDOS.stream().map(record -> new MaterialUseRecordDetailDO()
                .setRecordId(record.getId()).setInfomationId(record.getInfomationId())
                .setTargetInfomationId(infomationDO.getId())).toList();
        this.saveBatch(recordDetailDOS);

        // 保存领用记录 - 出库
        MaterialInventoryOutboundSaveReqVO outboundSaveReqVO = new MaterialInventoryOutboundSaveReqVO()
                .setBusinessType(MaterialOutboundType.receive_outbound.getName())
                .setInfomationIds(Collections.singletonList(infomationDO.getId()))
                .setApplyUser(recordDOS.get(0).getUserName()).setApplyUserId(recordDOS.get(0).getUserId())
                .setSuperviseUser(recordDOS.get(0).getSupervisorName())
                .setSuperviseUserId(recordDOS.get(0).getSupervisorId());
        materialInventoryOutboundService.createMaterialInventoryOutbound(outboundSaveReqVO);
        return materialInfomationService.getMaterialInfomationWithPdtInfoByInfId(infomationDO.getId());
    }

    private void updateInfomationsRemainingVolume(List<MaterialUseRecordDO> recordDOS, List<MaterialInfomationDO> infomations) {
        Map<Long, List<MaterialUseRecordDO>> groupRecordDOSByInfId = recordDOS.stream().collect(Collectors.groupingBy(MaterialUseRecordDO::getInfomationId));
        for (MaterialInfomationDO infomation : infomations) {
            List<MaterialUseRecordDO> recordDOS1 = groupRecordDOSByInfId.get(infomation.getId());
            BigDecimal totalOperationQuantity1 = recordDOS1.stream().map(MaterialUseRecordDO::getOperationQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
            infomation.setRemainingVolume(infomation.getRemainingVolume().subtract(totalOperationQuantity1));
        }
        materialInfomationService.updateBatch(infomations);
    }

    private List<MaterialUseRecordDO> saveMaterialUseRecordDOS(List<MaterialInfomationDO> infomations, List<MaterialUseRecordSaveReqVO> records) {
        Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
        String loginUserNickname = SecurityFrameworkUtils.getLoginUserNickname();
        Map<Long, MaterialInfomationDO> infomationDOMapById = infomations.stream().collect(Collectors.toMap(MaterialInfomationDO::getId, Function.identity()));
        List<MaterialUseRecordDO> recordDOS = records.stream().map(record -> {
            MaterialUseRecordDO recordDO = BeanUtils.toBean(record, MaterialUseRecordDO.class);
            recordDO.setHazardousMakeFlag(1);

            recordDO.setUseDate(LocalDateTime.now()).setUserId(loginUserId).setUserName(loginUserNickname);
            Long infomationId = record.getInfomationId();
            MaterialInfomationDO infomationDO = infomationDOMapById.get(infomationId);
            BigDecimal remainingVolume = infomationDO.getRemainingVolume();
            recordDO.setBeforeQuantity(remainingVolume)
                    .setAfterQuantity(remainingVolume.subtract(record.getOperationQuantity()));
            // 扣除物料剩余量
            infomationDO.setRemainingVolume(remainingVolume.subtract(record.getOperationQuantity()));
            infomationDOMapById.put(infomationId, infomationDO);
            return recordDO;
        }).toList();
        materialUseRecordService.saveBatch(recordDOS);
        return recordDOS;
    }

}