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

import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
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.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.bom.vo.BomDetailRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.bom.vo.BomPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.bom.vo.BomRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.bom.vo.BomSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.bom.BomDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.bom.BomDetailDO;
import cn.iocoder.yudao.module.mes.dal.mysql.bom.BomDetailMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.bom.BomMapper;
import cn.iocoder.yudao.module.mes.dal.redis.no.MesNoRedisDAO;
import org.apache.commons.lang3.StringUtils;
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.RoundingMode;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

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.BOM_EXISTS;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.BOM_NOT_EXISTS;

/**
 * 产品BOM Service 实现类
 *
 * @author 内蒙必硕
 */
@Service
@Validated
public class BomServiceImpl implements BomService {

    @Resource
    private BomMapper bomMapper;
    @Resource
    private BomDetailMapper bomDetailMapper;
    @Resource
    private ErpProductService productService;
    @Resource
    private ErpProductUnitService productUnitService;
    @Resource
    private MesNoRedisDAO noRedisDAO;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createBom(BomSaveReqVO createReqVO) {

        if (createReqVO.getIsEnable()) {
            BomDO temp = bomMapper.selectByProductId(createReqVO.getProductId());
            if (temp != null) {
                throw exception(BOM_EXISTS);
            }
        }
        // 插入
        BomDO bom = BeanUtils.toBean(createReqVO, BomDO.class);
        if (StringUtils.isEmpty(bom.getCode())) {
            // 1.4 生成订单号，并校验唯一性
            String no = noRedisDAO.generate2(MesNoRedisDAO.BOM_NO_PREFIX);
            if (bomMapper.selectByNo(no) != null) {
                throw exception(BOM_EXISTS);
            }
            bom.setCode(no);
        }
        bomMapper.insert(bom);

        // 插入子表
        createBomDetailList(bom.getId(), createReqVO.getBomDetails());
        // 返回
        return bom.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateBom(BomSaveReqVO updateReqVO) {
        if (updateReqVO.getIsEnable()) {
            BomDO temp = bomMapper.selectByProductId(updateReqVO.getProductId());
            if (temp != null && !Objects.equals(temp.getId(), updateReqVO.getId())) {
                throw exception(new ErrorCode(500, "每个产品启用的BOM只能有一个，请先禁用改产品其他BOM！"));
            }
        }
        // 校验存在
        validateBomExists(updateReqVO.getId());
        // 更新
        BomDO updateObj = BeanUtils.toBean(updateReqVO, BomDO.class);
        bomMapper.updateById(updateObj);

        // 更新子表
        updateBomDetailList(updateReqVO.getId(), updateReqVO.getBomDetails());
    }

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

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

    private void validateBomExists(Long id) {
        if (bomMapper.selectById(id) == null) {
            throw exception(BOM_NOT_EXISTS);
        }
    }

    @Override
    public BomDO getBom(Long id) {
        return bomMapper.selectById(id);
    }

    @Override
    public BomDO selectByProductId(Long productId) {
        return bomMapper.selectByProductId(productId);
    }

    @Override
    public PageResult<BomRespVO> getBomPage(BomPageReqVO pageReqVO) {
        PageResult<BomDO> pageResult = bomMapper.selectPage(pageReqVO);
        return new PageResult<>(buildVOList(pageResult.getList()), pageResult.getTotal());
    }

    private List<BomRespVO> buildVOList(List<BomDO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        Map<Long, ErpProductDO> map = productService.getProductMap(
                convertSet(list, BomDO::getProductId));
        Map<Long, ErpProductUnitDO> unitMap = productUnitService.getProductUnitMap(
                convertSet(list, BomDO::getUnitId));

        return BeanUtils.toBean(list, BomRespVO.class, item -> {
            MapUtils.findAndThen(map, item.getProductId(),
                    product -> item.setProductName(product.getName()));
            MapUtils.findAndThen(unitMap, item.getUnitId(),
                    unit -> item.setUnitName(unit.getName()));
        });
    }
    // ==================== 子表（产品BOM明细） ====================

    @Override
    public List<BomDetailRespVO> getBomDetailListByBomId(Long bomId) {
        List<BomDetailDO> list = bomDetailMapper.selectListByBomId(bomId);
        return buildDetailVOList(list);
    }

    @Override
    public List<BomDetailDO> getBomDetailListByProductId(Long productId) {
        BomDO bomDO = bomMapper.selectByProductId(productId);
        List<BomDetailDO> list = bomDetailMapper.selectListByBomId(bomDO.getId());
        return list;
    }

    /***根据产品id和产品数量，计算产品bom的物料需求，如果是重量单位，则统一到kg
     * 其他单位暂不处理
     */
    @Override
    public List<BomDetailDO> getBomDetailListByProductId(Long productId, Long number) {
        BomDO bomDO = bomMapper.selectByProductId(productId);
        List<BomDetailDO> list = bomDetailMapper.selectListByBomId(bomDO.getId());
        for (BomDetailDO detail : list) {
            BigDecimal result = detail.getUsageNumber().multiply(BigDecimal.valueOf(number));
            //如果是g则统一到kg
            if (detail.getUnitId().equals(ProductUnitEnum.Gram.getUnitId())) {
                detail.setUsageNumber(result.divide(BigDecimal.valueOf(1000), 3, RoundingMode.HALF_UP));
                detail.setUnitId(ProductUnitEnum.Kilogram.getUnitId());
            } else {
                detail.setUsageNumber(result);
            }
        }
        return list;
    }

    private List<BomDetailRespVO> buildDetailVOList(List<BomDetailDO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        Map<Long, ErpProductDO> map = productService.getProductMap(
                convertSet(list, BomDetailDO::getProductId));
        Map<Long, ErpProductUnitDO> unitMap = productUnitService.getProductUnitMap(
                convertSet(list, BomDetailDO::getUnitId));

        return BeanUtils.toBean(list, BomDetailRespVO.class, item -> {
            MapUtils.findAndThen(map, item.getProductId(),
                    product -> item.setProductName(product.getName()));
            MapUtils.findAndThen(unitMap, item.getUnitId(),
                    unit -> item.setUnitName(unit.getName()));
        });
    }

    private void createBomDetailList(Long bomId, List<BomDetailDO> list) {
        list.forEach(o -> o.setBomId(bomId));
        bomDetailMapper.insertBatch(list);
    }

    private void updateBomDetailList(Long bomId, List<BomDetailDO> list) {
        deleteBomDetailByBomId(bomId);
        list.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下：1）id 冲突；2）updateTime 不更新
        createBomDetailList(bomId, list);
    }

    private void deleteBomDetailByBomId(Long bomId) {
        bomDetailMapper.deleteByBomId(bomId);
    }

}