package cn.iocoder.yudao.module.flow.service.outbound;

import cn.iocoder.yudao.framework.common.enums.BlockStateEnum;
import cn.iocoder.yudao.framework.common.enums.CodeEnum;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.basic.api.dealerinfo.DealerInfoApi;
import cn.iocoder.yudao.module.basic.api.dealerinfo.dto.DealerInfoRespDTO;
import cn.iocoder.yudao.module.chain.api.blockcertificate.BlockCertificateApi;
import cn.iocoder.yudao.module.chain.api.blockcertificate.dto.BlockCertificateRespDTO;
import cn.iocoder.yudao.module.chain.api.blockcertificate.dto.BlockCertificateUpChainReqDTO;
import cn.iocoder.yudao.module.chain.api.blockcertificate.dto.UpChainObjReqDTO;
import cn.iocoder.yudao.module.chain.api.blockcertificate.dto.UpdateStatusReqDTO;
import cn.iocoder.yudao.module.chain.enums.ChainStatusEnum;
import cn.iocoder.yudao.module.chain.enums.OperateTypeEnum;
import cn.iocoder.yudao.module.flow.controller.admin.outbound.vo.*;
import cn.iocoder.yudao.module.flow.controller.admin.outboundgoods.vo.OutboundGoodsRespVO;
import cn.iocoder.yudao.module.flow.controller.admin.outboundgoods.vo.OutboundGoodsSaveReqVO;
import cn.iocoder.yudao.module.flow.convert.outbound.OutboundConvert;
import cn.iocoder.yudao.module.flow.convert.outboundgoods.OutboundGoodsConvert;
import cn.iocoder.yudao.module.flow.dal.dataobject.outbound.OutboundDO;
import cn.iocoder.yudao.module.flow.dal.dataobject.outboundgoods.OutboundGoodsDO;
import cn.iocoder.yudao.module.flow.dal.mysql.outbound.OutboundMapper;
import cn.iocoder.yudao.module.flow.dal.mysql.outboundgoods.OutboundGoodsMapper;
import cn.iocoder.yudao.module.flow.enums.ErrorCodeConstants;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.BeanUtils;
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.util.*;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.basic.enums.ErrorCodeConstants.GOODS_ENCODING_INFO_NOT_EXISTS;

/**
 * 出库单 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
public class OutboundServiceImpl implements OutboundService {

    @Resource
    private OutboundMapper outboundMapper;

    @Resource
    private OutboundGoodsMapper outboundGoodsMapper;

    @Resource
    private DealerInfoApi dealerInfoApi;

    @Resource
    private BlockCertificateApi blockCertificateApi;

    /**
     * 创建出库单
     *
     * @param createReqVO 创建信息
     * @return
     */
    @Override
    @Transactional
    public Long createOutbound(OutboundCreateReqVO createReqVO) {
        //检验经销商id是否存在，如果不存在则抛出异常。
        CommonResult<DealerInfoRespDTO> result = dealerInfoApi.getDealerGoodsInfoByDealerGoodsId(createReqVO.getVendorId());
        DealerInfoRespDTO dto = result.getData();
        if (result.isError() || dto == null) {
            throw exception(cn.iocoder.yudao.module.basic.enums.ErrorCodeConstants.VENDOR_INFO_NOT_EXISTS);
        }
        //将输批次信息转换成对应的 DO 对象，并进行存在性校验。  第一种
        List<OutboundGoodsDO> outboundGoods = OutboundConvert.INSTANCE.convertListCreate(createReqVO.getOutboundGoods());
        Map<String, String> idAndNames = new HashMap<>();
        //判断出库单商品是否存在
        this.verifiHarvestBatchs(outboundGoods, idAndNames, null, null,null);
        outboundGoods.forEach(outboundGood -> {
            outboundGood.setBindId(CodeEnum.OUTBOUND_BIND_CODE.getCode());
            outboundGood.setId(null);
            outboundGood.setOutBoundId(createReqVO.getOutBoundId());
            outboundGood.setBelongBusinessId(dto.getBelongBusinessId());
            outboundGood.setBelongBusinessName(dto.getBelongBusinessName());
            if (outboundGood.getGoodsUnitPrice() != null){
                //重新计算总额
                outboundGood.setTotalPrice(outboundGood.getGoodsUnitPrice().multiply(new BigDecimal(outboundGood.getGoodsNum())));
            }else {
                outboundGood.setTotalPrice(new BigDecimal(0));
            }

        });
        // 插入数据
        OutboundDO outbound = OutboundConvert.INSTANCE.convert(createReqVO);
        //下面的数据参照数据库为准
        outbound.setOutBoundTime(createReqVO.getOutBoundTime());
        outbound.setBelongBusinessId(dto.getBelongBusinessId());
        outbound.setBelongBusinessName(dto.getBelongBusinessName());
        outbound.setBlockState(BlockStateEnum.ACTIVATION.getBlockState());
        outboundMapper.insert(outbound);
        outboundGoodsMapper.insertBatch(outboundGoods);
        // 返回
        return outbound.getId();

    }


    /**
     *
     * @param outboundGoods 页面传入的OutboundGoodsDO集合
     * @param idAndNames id和名字的键值对
     * @param harvestInfoMaps 商品编码
     * @param willUpdateIds 将要更新的数据的批次id
     */
    private void verifiHarvestBatchs(List<OutboundGoodsDO> outboundGoods, Map<String, String> idAndNames, Map<String, OutboundGoodsDO> harvestInfoMaps,
            List<Long> willUpdateIds, OutboundDO outboundDO) {
        Set<String> outboundIdsSet = new HashSet<>();
        Set<Long> willUpdateIdsSet = new HashSet<>();
        outboundGoods.forEach(outboundsBatch -> {
            outboundIdsSet.add(outboundsBatch.getGoodsId());
            if (outboundsBatch.getId() != null ){
                willUpdateIdsSet.add(outboundsBatch.getId());
                if (willUpdateIds != null){
                    willUpdateIds.add(outboundsBatch.getId());
                }
            }
        });
        //存在相同的商品编码
        if (outboundIdsSet.size() != outboundGoods.size()){
            throw exception(ErrorCodeConstants.GOODS_INFO_NOT_EXISTS_BE_REPEATED);
        }
        //存在相同的批次id
        if (willUpdateIds != null && willUpdateIdsSet.size() != willUpdateIds.size()){
            throw exception(ErrorCodeConstants.HARVEST_BATCH_NOT_BE_REPEATED);
        }
        //这边开始校验商品编码是否存在
        if(outboundDO != null){
            List<String> outBoundGoodsIds = new ArrayList<>(outboundIdsSet);
            List<OutboundGoodsDO> outboundGoodsRespDTO = outboundGoodsMapper.selectList(new LambdaQueryWrapperX<OutboundGoodsDO>()
                    .in(OutboundGoodsDO::getGoodsId, outBoundGoodsIds).eq(OutboundGoodsDO::getOutBoundId, outboundDO.getOutBoundId()));
            if (!outboundGoodsRespDTO.isEmpty() && outboundGoodsRespDTO.size() != outboundGoodsRespDTO.size()){
                throw exception(GOODS_ENCODING_INFO_NOT_EXISTS);
            }
            outboundGoodsRespDTO.forEach(item ->{
                idAndNames.put(item.getGoodsId(),item.getGoodsName());
                if (harvestInfoMaps != null){
                    harvestInfoMaps.put(item.getGoodsId(),item);
                }
            });
        }


    }



    /**
     * 更新出库单
     *
     * @param updateReqVO 更新信息
     */
    @Override
    @Transactional
    public void updateOutbound(OutboundUpdateReqVO updateReqVO) {
        //查询出库单信息
        OutboundDO outboundBy = outboundMapper.selectById(updateReqVO.getId());
        if (outboundBy == null){
            throw exception(ErrorCodeConstants.OUTBOUND_NOT_EXISTS);
        }
        List<OutboundGoodsDO> outboundGoods = OutboundGoodsConvert.INSTANCE.convertListCreate(updateReqVO.getOutboundGoods());
        //构建出库商品批次 id 和名称的映射
        HashMap<String,String> idAndNames = new HashMap<>();
        //页面传入的outboundGoods集合
        Map<String, OutboundGoodsDO> outboundGoodsMaps = new HashMap<>();
        //将要更新的出库商品批次主键id
        List<Long> willUpdateId = new ArrayList<>();

        //判断出库商品是否存在
        this.verifiHarvestBatchs(outboundGoods, idAndNames, outboundGoodsMaps, willUpdateId, outboundBy);
        //出库商品批次键值对
        Map<Long,OutboundGoodsDO> outboundGoodsDOMaps = new HashMap<>();
        if(willUpdateId.size() > 0){
            //查询需要更新的出库商品批次
            List<OutboundGoodsDO> outboundGoodsDOS = outboundGoodsMapper.selectList(new LambdaQueryWrapperX<OutboundGoodsDO>()
                    .in(OutboundGoodsDO::getId, willUpdateId)
                    .eq(OutboundGoodsDO::getOutBoundId, outboundBy.getOutBoundId()));
//            if (outboundGoodsDOS.size() != willUpdateId.size()){ //存在已删除或者不是该出库商品出库单号的批次信息
//                throw exception(ErrorCodeConstants.EXPORT_HAS_BEEN_DELETED_OUTBOUND);//存在已删除的出库商品批次
//            }
            outboundGoodsDOS.forEach(item -> outboundGoodsDOMaps.put(item.getId(),item));
        }
        //需要新增的的集合
        List<OutboundGoodsDO> addList = new ArrayList<>();
        //需要新增修改的集合
        List<OutboundGoodsDO> updateList = new ArrayList<>();
        //遍历出库商品列表
        for (OutboundGoodsSaveReqVO item: updateReqVO.getOutboundGoods()){
            //加了Id字段
            if (item.getId() != null){
                //更新流程
                OutboundGoodsDO outboundGoodsDO = outboundGoodsDOMaps.get(item.getId());
                //防止非法修改数据
                if(outboundGoodsDO == null){
                    continue;
                }
                //只更新可更新的字段
                outboundGoodsDO.setHarvestBatchId(item.getHarvestBatchId());
                outboundGoodsDO.setGoodsWeight(item.getGoodsWeight());
                outboundGoodsDO.setGoodsNum(item.getGoodsNum());
                outboundGoodsDO.setGoodsUnitPrice(item.getGoodsUnitPrice());
                //计算总额
                if (outboundGoodsDO.getGoodsUnitPrice() != null){

                    outboundGoodsDO.setTotalPrice(outboundGoodsDO.getGoodsUnitPrice().multiply(new BigDecimal(outboundGoodsDO.getGoodsNum())));
                }else {

                    outboundGoodsDO.setTotalPrice(new BigDecimal(0));
                }
                //更新所有
                updateList.add(outboundGoodsDO);
            }else {

                OutboundGoodsDO outboundGood = OutboundGoodsConvert.INSTANCE.convert(item);
                //新增 这里都以数据库的数据为准
                outboundGood.setId(null);
                outboundGood.setBindId(CodeEnum.OUTBOUND_BIND_CODE.getCode());
                outboundGood.setBelongBusinessId(outboundBy.getBelongBusinessId());
                outboundGood.setBelongBusinessName(outboundBy.getBelongBusinessName());
                outboundGood.setOutBoundId(outboundBy.getOutBoundId());
                // 使用相同的单位
                outboundGood.setGoodsUnit(item.getGoodsUnit());
                // 使用相同的单位
                if (outboundGood.getGoodsUnitPrice() != null){

                    outboundGood.setTotalPrice(outboundGood.getGoodsUnitPrice().multiply(new BigDecimal(outboundGood.getGoodsNum())));
                }else {
                    outboundGood.setTotalPrice(new BigDecimal(0));
                }
                //将新增成功的数据返回给调用者
                addList.add(outboundGood);
            }
        }
        //更新主库
        OutboundDO updateOutboundDO = OutboundConvert.INSTANCE.convert(updateReqVO);
        if (!addList.isEmpty()){
            outboundGoodsMapper.insertBatch(addList);
        }
        if (!updateList.isEmpty()){
            outboundGoodsMapper.updateBatch(updateList,Integer.MAX_VALUE);
        }
        outboundMapper.updateById(updateOutboundDO);
    }



    /**
     * 删除出库单
     *
     * @param id 编号
     */
    @Override
    public void deleteOutbound(Long id) {
        // 校验存在
        validateOutboundExists(id);
        // 删除
        outboundMapper.deleteById(id);
    }


    private void validateOutboundExists(Long id) {
        //判断id为空
        if (outboundMapper.selectById(id) == null) {
            throw exception(ErrorCodeConstants.OUTBOUND_NOT_EXISTS);
        }
    }

    /**
     * 获得出库单
     *
     * @param id 编号
     * @return
     */
    @Override
    public OutboundRespVO getOutbound(Long id) {
        OutboundDO outboundDO = outboundMapper.selectById(id);
        if (outboundDO == null){
            throw exception(ErrorCodeConstants.OUTBOUND_NOT_EXISTS);
        }
        OutboundRespVO outboundRespVO = OutboundConvert.INSTANCE.convert(outboundDO);
        List<OutboundGoodsDO> outboundGoodsDOList = outboundGoodsMapper.selectList(OutboundGoodsDO::getOutBoundId, outboundRespVO.getOutBoundId());
        List<OutboundGoodsRespVO> outboundGoodsRespVOS = OutboundGoodsConvert.INSTANCE.convertList(outboundGoodsDOList);
        outboundRespVO.setOutboundGoods(outboundGoodsRespVOS);
        return outboundRespVO;
    }

    /**
     * 获得出库单列表实现
     *
     * @param ids 编号
     * @return
     */
    @Override
    public List<OutboundDO> getOutboundList(Collection<Long> ids) {
        return outboundMapper.selectBatchIds(ids);
    }

    /**
     * 获得出库单分页实现
     *
     * @param pageReqVO 分页查询
     * @return
     */
    @Override
    public PageResult<OutboundDO> getOutboundPage(OutboundPageReqVO pageReqVO) {
        return outboundMapper.selectPage(pageReqVO);
    }

    /**
     * 根据id获取主表和子表的信息
     *
     * @param id@return
     */
    @Override
    public Map<String, Object> getOutboundGoodsInfoById(Long id) {
        //根据主表id查询单条数据
        OutboundDO outboundDO = outboundMapper.selectById(id);
        if (outboundDO == null) {
            throw exception(ErrorCodeConstants.OUTBOUND_NOT_EXISTS);
        }
        //根据主表所获得的记录绑定查询子表的所有数据
        List<OutboundGoodsDO> outboundGoodsDOList = outboundGoodsMapper.selectList(OutboundGoodsDO::getId, outboundDO.getVendorId());
        Map<String, Object> result = new HashMap<>();
        result.put("outboundDO", outboundDO); //将主表信息放入Map中
        result.put("outboundGoodsDOList",outboundGoodsDOList); // 将子表列表放入Map中
        return result;
    }

    /**
     * 根据单号查询主单,再根据主单查询子单
     * @param outBoundId
     * @return
     */
    @Override
    public OutboundDTO  getOutboundGoodsInfoOutBoundId(String outBoundId) {
        // 根据出库单号查询主单信息
        OutboundDO outboundDO = outboundMapper.selectOne(new QueryWrapper<OutboundDO>()
                .eq("out_bound_id", outBoundId));
        if (outboundDO == null) {
            return null;
        }
        // 根据主单ID查询子单信息
        List<OutboundGoodsDO> outboundGoodsList = outboundGoodsMapper.selectList(new QueryWrapper<OutboundGoodsDO>()
                .eq("out_bound_id", outBoundId));
        // 将主单和子单组装成DTO返回
        OutboundDTO outboundDTO = new OutboundDTO();
        BeanUtils.copyProperties(outboundDO, outboundDTO);
        outboundDTO.setOutboundGoodsList(outboundGoodsList);
        return outboundDTO;
    }

    /**
     * 上链
     *
     * @param id id
     */
    @Override
    public void upChain(Long id) {
        OutboundDO outboundInfo = outboundMapper.selectById(id);
        if (outboundInfo == null){
            throw ServiceExceptionUtil.exception(cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.OUTBOUND_NOT_EXISTS);
        }
        if (!outboundInfo.getBlockState().equals(BlockStateEnum.NOT_ON_CHAIN.getBlockState())){
            throw ServiceExceptionUtil.exception(cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.OUTBOUND_NOT_ON_CHAIN_HARVEST_INFO_CAN_DO_UP_CHAIN);
        }
        //封装出库
        ArrayList<UpChainObjReqDTO> operateOutbounds = new ArrayList<>();
        UpChainObjReqDTO upChainObjReqDTO = new UpChainObjReqDTO();
        upChainObjReqDTO.setOperateObjId(id);
        operateOutbounds.add(upChainObjReqDTO);

        BlockCertificateUpChainReqDTO upChainReqDTO = new BlockCertificateUpChainReqDTO();
        upChainReqDTO.setOperateType(OperateTypeEnum.OUT_BOUND.getType());
        upChainReqDTO.setOperateName(OperateTypeEnum.OUT_BOUND.getName());
        upChainReqDTO.setOperateObjs(operateOutbounds);
        upChainReqDTO.setBelongBusinessId(outboundInfo.getBelongBusinessId());
        upChainReqDTO.setBelongBusinessName(outboundInfo.getBelongBusinessName());

        //远程管理
        List<BlockCertificateRespDTO> batchResults = blockCertificateApi.createAndUpChainBatch(upChainReqDTO).getData();
        if (batchResults == null){
            throw ServiceExceptionUtil.exception(cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.CREATE_BLOCK_CERTIFICATE_FAIL);
        }

        //将要更新的类
        OutboundDO outboundDO = new OutboundDO();
        outboundDO.setId(id);
        outboundDO.setBlockChainBlockIdentity(batchResults.get(0).getChainBlockIdentity());
        outboundDO.setBlockChainId(batchResults.get(0).getChainOperateId());
        outboundDO.setBlockState(BlockStateEnum.ACTIVATION.getBlockState());// 改为激活状态
        outboundMapper.updateById(outboundDO);
    }

    /**
     * 更新链状态
     *
     * @param id    id
     * @param state 状态
     */
    @Override
    public void updateChainStatus(Long id, Integer state) {
        OutboundDO outboundInfo = outboundMapper.selectById(id);
        if (outboundInfo == null){
            throw ServiceExceptionUtil.exception(cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.OUTBOUND_NOT_EXISTS);
        }
        if (outboundInfo.getBlockState().equals(BlockStateEnum.NOT_ON_CHAIN.getBlockState())){
            throw ServiceExceptionUtil.exception(cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.OUTBOUND_ON_CHAIN_HARVEST_INFO_CAN_DO_UPDATE_CHAIN_STATUS);
        }
        Integer blockState = state.equals(BlockStateEnum.ACTIVATION.getBlockState()) ? BlockStateEnum.ACTIVATION.getBlockState() : BlockStateEnum.FREEZE.getBlockState();
        Integer chainStatus = state.equals(BlockStateEnum.ACTIVATION.getBlockState()) ? ChainStatusEnum.ACTIVE.getStatus() : ChainStatusEnum.FREEZE.getStatus();

        // 修改主单的链状态时，将同步修改子单的链状态
        List<UpdateStatusReqDTO> batchsReqDTOS = new ArrayList<>();
        UpdateStatusReqDTO harvestReqDTO = new UpdateStatusReqDTO();
        harvestReqDTO.setChainOperateId(outboundInfo.getBlockChainId());
        harvestReqDTO.setChainStatus(chainStatus);
        batchsReqDTOS.add(harvestReqDTO);


        OutboundDO updateDO = new OutboundDO();
        updateDO.setId(id);
        updateDO.setBlockState(blockState);

        batchsReqDTOS.forEach(batch -> batch.setChainStatus(blockState));

        outboundMapper.updateById(updateDO);

    }


    /**
     * 获得首页出库的统计数据
     *
     * @return 出库数据
     */
    @Override
    public OutboundInfoDataRespVO getOutboundInfoDataStatistics() {
        OutboundInfoDataRespVO result = new OutboundInfoDataRespVO();
        //查询本月的每个商品的出库总量
        result.setGoodsWeekStatisticsDatas(outboundMapper.getDatasGroupByGoodsId("month"));

        //查询最新7天的出库总量
        result.setGoodsMonthStatisticsDatas(outboundMapper.getSixDatasByGoodsId());

        //将每个商品的出库总量进行相加
        result.setTodayOutboundCount(outboundMapper.getTodayOutboundCount());

        return result;
    }
}

