package cn.iocoder.yudao.module.mes.service.feedingrecord;

import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO;
import cn.iocoder.yudao.module.erp.framework.bean.ProductUnitEnum;
import cn.iocoder.yudao.module.erp.service.product.ErpProductService;
import cn.iocoder.yudao.module.erp.service.product.ErpProductUnitService;
import cn.iocoder.yudao.module.mes.controller.admin.feedingrecord.vo.*;
import cn.iocoder.yudao.module.mes.controller.admin.itemrequisition.vo.ItemRequisitionDetailRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.stockworkshop.vo.StockWorkShopTypeEnum;
import cn.iocoder.yudao.module.mes.dal.dataobject.feedingplan.FeedingRecordPlanDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.feedingrecord.FeedingRecordDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.feedingrecord.FeedingRecordDetailDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.itemrequisition.ItemRequisitionDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.plan.PlanDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.stockworkshop.StockWorkshopDO;
import cn.iocoder.yudao.module.mes.dal.mysql.feedingplan.FeedingRecordPlanMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.feedingrecord.FeedingRecordDetailMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.feedingrecord.FeedingRecordMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.itemrequisition.ItemRequisitionMapper;
import cn.iocoder.yudao.module.mes.dal.redis.no.MesNoRedisDAO;
import cn.iocoder.yudao.module.mes.service.itemrequisition.ItemRequisitionService;
import cn.iocoder.yudao.module.mes.service.stockworkshop.StockWorkshopService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.*;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.FEEDING_RECORD_NOT_EXISTS;

/**
 * 投料记录 Service 实现类
 *
 * @author 内蒙必硕
 */
@Service
@Validated
public class FeedingRecordServiceImpl implements FeedingRecordService {

    @Resource
    private FeedingRecordMapper feedingRecordMapper;
    @Resource
    private FeedingRecordDetailMapper feedingRecordDetailMapper;
    @Resource
    private ErpProductService productService;
    @Resource
    private ErpProductUnitService productUnitService;
    @Resource
    private MesNoRedisDAO noRedisDAO;
    @Resource
    private AdminUserApi adminUserApi;

    @Resource
    private FeedingRecordPlanMapper recordPlanMapper;
    @Resource
    private StockWorkshopService workshopService;
    @Resource
    private ItemRequisitionService itemRequisitionService;
    @Resource
    private ItemRequisitionMapper itemRequisitionMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createFeedingRecord(FeedingRecordSaveReqVO createReqVO) {
        createReqVO.setFeedingRecordCode(noRedisDAO.generate3(MesNoRedisDAO.FEEDING_RECORD_CODE_PREFIX));
        // 插入
        FeedingRecordDO feedingRecord = BeanUtils.toBean(createReqVO, FeedingRecordDO.class);
        feedingRecord.setRecordStatus(FeedingStatusEnum.未投料.getValue());
        feedingRecordMapper.insert(feedingRecord);
        //检查投料原料项，如果不是kg，则转为kg
        if (createReqVO.getFeedingType().equals(FeedingTypeEnum.领料.getValue())) {
            transfer(createReqVO.getFeedingRecordDetails());
            // 插入子表
            createFeedingRecordDetailList(feedingRecord, createReqVO.getFeedingRecordDetails());
        }

        // 返回
        return feedingRecord.getId();
    }

//    @Override
//    @Transactional(rollbackFor = Exception.class)
//    public void createFeedingRecordDetail(FeedingRecordDO recordDO) {
//
//        transfer(createReqVO.getFeedingRecordDetails());
//        // 插入子表
//        list.forEach(o -> o.setId(null).setRecordId(recordDO.getId())
//                .setUserId(recordDO.getUserId())
//                .setFeedingTime(recordDO.getFeedingTime())
//        );
//        feedingRecordDetailMapper.insertBatch(list);
//    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateFeedingRecord(FeedingRecordSaveReqVO updateReqVO) {
        // 校验存在
        validateFeedingRecordExists(updateReqVO.getId());
        // 更新
        FeedingRecordDO updateObj = BeanUtils.toBean(updateReqVO, FeedingRecordDO.class);
        feedingRecordMapper.updateById(updateObj);
        if (updateReqVO.getFeedingType().equals(FeedingTypeEnum.领料.getValue())) {
            transfer(updateReqVO.getFeedingRecordDetails());
            // 更新子表
            updateFeedingRecordDetailList(updateObj, updateReqVO.getFeedingRecordDetails());
        }
    }

    //将单位转到kg
    private List<FeedingRecordDetailDO> transfer(List<FeedingRecordDetailDO> list) {
        if (list != null) {
            MathContext mc = new MathContext(3, RoundingMode.HALF_UP);
            for (int i = 0; i < list.size(); i++) {
                FeedingRecordDetailDO detailDO = list.get(i);
                if (detailDO.getUnitId().equals(ProductUnitEnum.Gram.getUnitId())) {
                    detailDO.setWeight(detailDO.getWeight().divide(BigDecimal.valueOf(1000), mc));
                    detailDO.setUnitId(ProductUnitEnum.Kilogram.getUnitId());
                } else if (detailDO.getUnitId().equals(ProductUnitEnum.Ton.getUnitId())) {
                    detailDO.setWeight(detailDO.getWeight().multiply(BigDecimal.valueOf(1000), mc));
                    detailDO.setUnitId(ProductUnitEnum.Kilogram.getUnitId());
                }
            }
        }
        return list;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteFeedingRecord(Long id) {
        // 校验存在
        validateFeedingRecordExists(id);
        // 删除
        feedingRecordMapper.deleteById(id);

        // 删除子表
        deleteFeedingRecordDetailByRecordId(id);
    }

    private void validateFeedingRecordExists(Long id) {
        if (feedingRecordMapper.selectById(id) == null) {
            throw exception(FEEDING_RECORD_NOT_EXISTS);
        }
    }

    @Override
    public FeedingRecordDO getFeedingRecord(Long id) {
        return feedingRecordMapper.selectById(id);
    }

    @Override
    public PageResult<FeedingRecordRespVO> getFeedingRecordPage(FeedingRecordPageReqVO pageReqVO) {
        PageResult<FeedingRecordDO> pageResult = feedingRecordMapper.selectPage(pageReqVO);
        return new PageResult<>(buildList(pageResult.getList()),pageResult.getTotal());
    }
    @Override
    public List<FeedingRecordRespVO> buildList(List<FeedingRecordDO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        Map<Long, ErpProductDO> map = productService.getProductMap(
                convertSet(list, FeedingRecordDO::getItemId));
        // 1.4 管理员信息
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(list, FeedingRecordDO::getUserId));

        return BeanUtils.toBean(list, FeedingRecordRespVO.class, item -> {
            MapUtils.findAndThen(map, item.getProductId(),
                    product -> item.setProductName(product.getName()));
            MapUtils.findAndThen(userMap, item.getUserId(),
                    user -> item.setUserName(user.getNickname()));
        });
    }

    // ==================== 子表（投料记录明细） ====================

    @Override
    public List<FeedingRecordDetailDO> getFeedingRecordDetailListByRecordId(Long recordId) {
        return feedingRecordDetailMapper.selectListByRecordId(recordId);
    }

    private void createFeedingRecordDetailList( FeedingRecordDO feedingRecord, List<FeedingRecordDetailDO> list) {
        if (list != null && list.size() > 0) {
            list.forEach(o -> o.setId(null).setRecordId(feedingRecord.getId())
                    .setUserId(feedingRecord.getUserId())
                    .setFeedingTime(feedingRecord.getFeedingTime())
            );
            feedingRecordDetailMapper.insertBatch(list);
        }
    }

    private void updateFeedingRecordDetailList(FeedingRecordDO updateObj, List<FeedingRecordDetailDO> list) {
        deleteFeedingRecordDetailByRecordId(updateObj.getId());
        list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下：1）id 冲突；2）updateTime 不更新
        createFeedingRecordDetailList(updateObj, list);
    }

    private void deleteFeedingRecordDetailByRecordId(Long recordId) {
        feedingRecordDetailMapper.deleteByRecordId(recordId);
    }

    @Override
    public List<FeedingRecordDetailRespVO> buildVOList(List<FeedingRecordDetailDO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        Map<Long, ErpProductDO> map = productService.getProductMap(
                convertSet(list, FeedingRecordDetailDO::getProductId));
        Map<Long, ErpProductUnitDO> unitMap = productUnitService.getProductUnitMap(
                convertSet(list, FeedingRecordDetailDO::getUnitId));
        // 1.4 管理员信息
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(list, FeedingRecordDetailDO::getUserId));

        return BeanUtils.toBean(list, FeedingRecordDetailRespVO.class, item -> {
            MapUtils.findAndThen(map, item.getProductId(),
                    product -> item.setProductName(product.getName()));
            MapUtils.findAndThen(unitMap, item.getUnitId(),
                    unit -> item.setUnitName(unit.getName()));
            MapUtils.findAndThen(userMap, item.getUserId(),
                    user -> item.setUserName(user.getNickname()));
        });
    }

    @Override
    public List<FeedingRecordDO> getFeedingRecordList(Collection<Long> ids) {
        return feedingRecordMapper.selectBatchIds(ids);
    }

    /**
     * 提交，反提交投料记录
     * 按照投料明细扣减车间仓库存
     * //投料明细按照当前正在开工的计划需求比例进行分配，生成投料分配计划记录FeedingRecordPlanDO
     * (投料明细按照当前正在开工的计划进行分配，如果计划都满足了，再按需求比例分配剩余投料)
     **/
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Map<String, FeedingRecordPlanDO> updateStatus(FeedingRecordDO recordDO, List<FeedingRecordDetailDO> detailDOList, String status, Long userId
            , List<PlanDO> planDOList, Map<Long, List<ItemRequisitionDetailRespVO>> planItemListMap,
                                                         Map<String, ItemRequisitionDetailRespVO> planItemMap) {

        if (status.equals(FeedingStatusEnum.投料中.getValue())) {
            //detailDOMap 汇总投料原料
            Map<Long, FeedingRecordDetailDO> detailDOMap = new HashMap<>();
            for (FeedingRecordDetailDO recordDetailDO : detailDOList) {
                //车间仓投料出库
                StockWorkshopDO workshopDO = new StockWorkshopDO()
                        .setItemId(recordDetailDO.getItemId()).setUnitId(recordDetailDO.getUnitId())
                        .setCount(recordDetailDO.getWeight().multiply(BigDecimal.valueOf(-1)))
                        .setCategoryId(1L);
                workshopService.updateStock(workshopDO, StockWorkShopTypeEnum.投料出库.getValue(),
                        recordDO.getId(), recordDO.getFeedingRecordCode());
                //合并同原料
                if (detailDOMap.containsKey(recordDetailDO.getItemId())) {
                    BigDecimal totalWeight = detailDOMap.get(recordDetailDO.getItemId()).getWeight()
                            .add(recordDetailDO.getWeight());
                    detailDOMap.get(recordDetailDO.getItemId()).setWeight(totalWeight);
                } else detailDOMap.put(recordDetailDO.getItemId(), recordDetailDO);
            }
            Map<String, FeedingRecordPlanDO> recordPlanDOMap =
                    dealPlan(detailDOMap, recordDO, userId, planDOList, planItemListMap, planItemMap);

            recordDO.setRecordStatus(FeedingStatusEnum.投料中.getValue());
            feedingRecordMapper.updateById(recordDO);
            return recordPlanDOMap;
        }
        //todo 需要回退吗？
        else if (status.equals(FeedingStatusEnum.未投料.getValue())) {

        }
        return null;
    }

    private Map<String, FeedingRecordPlanDO> dealPlan(Map<Long, FeedingRecordDetailDO> detailDOMap, FeedingRecordDO recordDO, Long userId,
                                                      List<PlanDO> planDOList, Map<Long, List<ItemRequisitionDetailRespVO>> planItemListMap,
                                                      Map<String, ItemRequisitionDetailRespVO> planItemMap) {
        MathContext mc = new MathContext(3, RoundingMode.HALF_UP);
        //分配计划
        //totalPlanNeedItem记录所有计划需要的该原料总数，用于最后按比例分配原料
        Map<Long, BigDecimal> totalPlanNeedItem = new HashMap<>();
        //recordPlanDOMap 记录已分配计划id原料id对应的分配量，同计划id原料id的合并为一个记录，最终插入数据库
        Map<String, FeedingRecordPlanDO> recordPlanDOMap = new HashMap<>();
        for (int i = 0; i < planDOList.size(); i++) {
            List<ItemRequisitionDetailRespVO> list = planItemListMap.get(planDOList.get(i).getId());

            for (ItemRequisitionDetailRespVO respVO : list) {
                //原料需要分配
                int cp = respVO.getNumber().compareTo(respVO.getFinishNumber());
                if (detailDOMap.containsKey(respVO.getProductId())
                        && detailDOMap.get(respVO.getProductId()).getWeight().compareTo(BigDecimal.ZERO) > 0
                        && cp > 0) {
                    FeedingRecordDetailDO detailDO = detailDOMap.get(respVO.getProductId());
                    BigDecimal weight = respVO.getNumber().subtract(respVO.getFinishNumber(), mc);
                    //投料不能满足计划需求，则对该计划投料为实际投料数量, 否则满足，为需求数量
                    if (weight.compareTo(detailDO.getWeight()) > 0) {
                        weight = detailDO.getWeight();
                    }
                    //未分配投料数量减少
                    detailDO.setWeight(detailDO.getWeight().subtract(weight));

                    String key = getMapKey(planDOList.get(i).getId(), respVO.getProductId());
                    if (recordPlanDOMap.containsKey(key)) {
                        //正常来说单个计划再做物料分析的时候只会产生一条ItemRequisitionDetailRespVO
                        recordPlanDOMap.get(key).setWeight(recordPlanDOMap.get(key).getWeight().add(weight));
                    } else {
                        FeedingRecordPlanDO recordPlanDO = new FeedingRecordPlanDO()
                                .setPlanId(planDOList.get(i).getId())
                                .setTaskDetailId(planDOList.get(i).getTaskDetailId())
                                .setItemId(respVO.getProductId())
                                .setRecordId(recordDO.getId()).setUserId(userId).setUnitId(respVO.getUnitId())
                                .setRecordDetailId(respVO.getId()).setFeedingTime(detailDO.getFeedingTime())
                                .setWeight(weight);
                        recordPlanDOMap.put(key, recordPlanDO);
                    }
                }
                if (totalPlanNeedItem.containsKey(respVO.getProductId())) {
                    BigDecimal totalNumber = totalPlanNeedItem.get(respVO.getProductId()).add(respVO.getNumber());
                    totalPlanNeedItem.put(respVO.getProductId(), totalNumber);
                } else totalPlanNeedItem.put(respVO.getProductId(), respVO.getNumber());
            }
        }

        dealLast(detailDOMap, recordDO.getId(), userId, planDOList, totalPlanNeedItem, recordPlanDOMap, planItemMap);
        //插入
        recordPlanMapper.insertBatch(recordPlanDOMap.values());
        return recordPlanDOMap;
    }

    //处理剩余未分配原料，按照totalPlanNeedItem 比例分配到每个计划
    private Map<String, FeedingRecordPlanDO> dealLast(Map<Long, FeedingRecordDetailDO> detailDOMap, Long recordId, Long userId
            , List<PlanDO> planDOList, Map<Long, BigDecimal> totalPlanNeedItem, Map<String, FeedingRecordPlanDO> recordPlanDOMap,
                                                      Map<String, ItemRequisitionDetailRespVO> planItemMap) {
        MathContext mc = new MathContext(5, RoundingMode.HALF_UP);

        for (Long itemId : detailDOMap.keySet()) {
            //原料有未分配数量
            if (detailDOMap.get(itemId).getWeight().compareTo(BigDecimal.ZERO) <= 0) continue;
            if(!totalPlanNeedItem.containsKey(itemId)){
                System.out.println("~~~~~~~计算出错！~~~~~~");
                continue;
            }
            BigDecimal factor = detailDOMap.get(itemId).getWeight().divide(totalPlanNeedItem.get(itemId), mc);
            for (PlanDO plan : planDOList) {
                ItemRequisitionDetailRespVO detailRespVO = planItemMap.get(getMapKey(plan.getId(), itemId));
                //该计划不需要这种物料
                if (detailRespVO == null) {
                    System.out.println("~~~~~~~计算出错！~~~~~~");
                    continue;
                }

                BigDecimal weight = factor.multiply(detailRespVO.getNumber());
                String key = getMapKey(plan.getId(), itemId);
                if (recordPlanDOMap.containsKey(key)) {
                    recordPlanDOMap.get(key).setWeight(recordPlanDOMap.get(key).getWeight().add(weight));
                } else {
                    FeedingRecordPlanDO recordPlanDO = new FeedingRecordPlanDO()
                            .setPlanId(plan.getId()).setItemId(itemId)
                            .setTaskDetailId(plan.getTaskDetailId())
                            .setRecordId(recordId).setUserId(userId).setUnitId(detailRespVO.getUnitId())
                            .setRecordDetailId(detailDOMap.get(itemId).getId())
                            .setFeedingTime(detailDOMap.get(itemId).getFeedingTime())
                            .setWeight(weight);
                    recordPlanDOMap.put(key, recordPlanDO);
                }
            }
        }
        return recordPlanDOMap;
    }

    private String getMapKey(Long planId, Long itemId) {
        return planId + "-" + itemId;
    }


    //干废品和湿废品投料分配计划
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void dealOtherRecord(FeedingRecordDO recordDO, String status, Long userId, List<PlanDO> planDOList) {
        FeedingTypeEnum typeEnum = FeedingTypeEnum.fromValue(recordDO.getFeedingType());
        MathContext mc = new MathContext(3, RoundingMode.HALF_UP);
        if (status.equals(FeedingStatusEnum.投料中.getValue())) {
            Long totalPlanNumber = 0L;
            for (PlanDO plan : planDOList) {
                totalPlanNumber += plan.getPlanNumber();
            }
            List<FeedingRecordPlanDO> recordPlanDOList = new ArrayList<>();
            for (PlanDO plan : planDOList) {
                BigDecimal weight = recordDO.getWeight().multiply(BigDecimal.valueOf(plan.getPlanNumber() / totalPlanNumber), mc);
                FeedingRecordPlanDO recordPlanDO = new FeedingRecordPlanDO()
                        .setPlanId(plan.getId()).setItemId(typeEnum.getTable_id())
                        .setTaskDetailId(plan.getTaskDetailId())
                        .setRecordId(recordDO.getId()).setUserId(userId).setUnitId(ProductUnitEnum.Kilogram.getUnitId())
                        .setRecordDetailId(0L)
                        .setFeedingTime(recordDO.getFeedingTime())
                        .setWeight(weight);
                recordPlanDOList.add(recordPlanDO);
            }
            if (recordPlanDOList.size() > 0)
                recordPlanMapper.insertBatch(recordPlanDOList);

        }
        //todo 需要回退吗？
        else if (status.equals(FeedingStatusEnum.未投料.getValue())) {

        }

        //车间仓也进行出入库记录
        StockWorkshopDO workshopDO = new StockWorkshopDO()
                .setItemId(typeEnum.getTable_id()).setUnitId(ProductUnitEnum.Kilogram.getUnitId())
                .setCount(recordDO.getWeight())
                .setCategoryId(1L);
        workshopService.updateStock(workshopDO, StockWorkShopTypeEnum.领料入库.getValue(),
                0L, "");

        workshopService.updateStock(workshopDO, StockWorkShopTypeEnum.投料出库.getValue(),
                recordDO.getId(), recordDO.getFeedingRecordCode());
        return;
    }



    @Override
    @Transactional(rollbackFor = Exception.class)
    public void dealWithOnPlan(PlanDO plan, List<FeedingRecordDetailDO> detailDOList,
                               Map<String, ItemRequisitionDetailRespVO> planItemMap, LoginUser user,FeedingRecordDO recordDO){

        for (FeedingRecordDetailDO detail : detailDOList) {
            //车间仓投料出库
            StockWorkshopDO workshopDO = new StockWorkshopDO()
                    .setItemId(detail.getItemId()).setUnitId(detail.getUnitId())
                    .setCount(detail.getWeight().multiply(BigDecimal.valueOf(-1)))
                    .setCategoryId(1L);
            workshopService.updateStock(workshopDO, StockWorkShopTypeEnum.投料出库.getValue(),
                    recordDO.getId(), recordDO.getFeedingRecordCode());

            //分配给计划
            FeedingRecordPlanDO recordPlanDO = new FeedingRecordPlanDO()
                    .setPlanId(plan.getId())
                    .setTaskDetailId(plan.getTaskDetailId())
                    .setItemId(detail.getItemId())
                    .setRecordId(detail.getRecordId()).setUserId(user.getId()).setUnitId(detail.getUnitId())
                    .setRecordDetailId(detail.getId()).setFeedingTime(detail.getFeedingTime())
                    .setWeight(detail.getWeight());

            recordPlanMapper.insert(recordPlanDO);
        }
        //更新对应投料单投料时间
        ItemRequisitionDO itemRequisitionDO = itemRequisitionService.getItemRequisitionByPlanId(plan.getId());
        if (itemRequisitionDO != null && itemRequisitionDO.getDeliveryDate()==null) {
            itemRequisitionDO.setDeliveryDate(LocalDate.now());
            itemRequisitionMapper.updateById(itemRequisitionDO);
        }
        return;
    }
}