package cn.iocoder.yudao.module.system.service.assets;

import cn.hutool.core.collection.CollectionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.module.system.controller.admin.assets.dto.AssetAllocationDTO;
import cn.iocoder.yudao.module.system.controller.admin.assets.dto.AssetsChangeDTO;
import cn.iocoder.yudao.module.system.controller.admin.assets.vo.*;
import cn.iocoder.yudao.module.system.dal.dataobject.assets.AssetsDO;
import cn.iocoder.yudao.module.system.dal.dataobject.assets.DeptAssetsDO;
import cn.iocoder.yudao.module.system.dal.dataobject.assets.DeptAssetsInOutStockDO;
import cn.iocoder.yudao.module.system.dal.mysql.assets.AssetsMapper;
import cn.iocoder.yudao.module.system.dal.mysql.assets.DeptAssetsInOutStockMapper;
import cn.iocoder.yudao.module.system.dal.mysql.assets.DeptAssetsMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.STOCK_EXCEEDS;

/**
 * 部门资产出入库 Service 实现类
 *
 * @author 艾楷
 */
@Service
@Validated
public class DeptAssetsInOutStockServiceImpl implements DeptAssetsInOutStockService {

    @Resource
    private DeptAssetsInOutStockMapper deptAssetsInOutStockMapper;
    @Resource
    private AssetsMapper assetsMapper;
    @Resource
    private DeptAssetsMapper deptAssetsMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DeptAssetsInOutStockVO createDeptAssetsInOutStock(Long outDeptId, List<DeptAssetsInOutStockSaveReqVO> vos) {
        DeptAssetsInOutStockVO deptAssetsInOutStockVO = new DeptAssetsInOutStockVO();
        List<Long> ids = vos.stream().map(DeptAssetsInOutStockSaveReqVO::getAssetsId).distinct().collect(Collectors.toList());
        // 从vos中根据资产id分组 并且讲相同资产id的数量求和 -- 注意 - 这里搜索资产列表 要搜索到 资产为可用的 0 or -1
        Map<Long, Integer> map = vos.stream().collect(Collectors.groupingBy(DeptAssetsInOutStockSaveReqVO::getAssetsId, Collectors.summingInt(DeptAssetsInOutStockSaveReqVO::getNum)));
        // 从vos中根据资产id分组获取到List<Long,List<DeptAssetsInOutStockSaveReqVO>> 对象 并且将相同资产id的列表合并成在value中
        Map<Long, List<DeptAssetsInOutStockSaveReqVO>> inOutStockMap = vos.stream().collect(Collectors.groupingBy(DeptAssetsInOutStockSaveReqVO::getAssetsId));
        Map<Long, List<DeptAssetsInOutStockSaveReqVO>> inOutStockByDeptMap = vos.stream().collect(Collectors.groupingBy(DeptAssetsInOutStockSaveReqVO::getInDeptId));

        // 根据出库部门id 和 资产id 获取资产信息
        List<AssetsChangeDTO> dtos = this.buildDtoByOutDeptId(outDeptId == -1 ? null : outDeptId, ids);
        // -判断资产是否够
        List<DeptAssetsInOutStockDO> saveList = new ArrayList<>();
        List<String> error = new ArrayList<>();
        List<AssetsDO> updateAssetsList = new ArrayList<>();
        List<Long> deptAssetDeptIdDelIds = new ArrayList<>();
        List<Long> deptAssetAssetsIdDelIds = new ArrayList<>();
        List<DeptAssetsDO> updateDeptAssetsList = new ArrayList<>();
        Map<Long, Integer> systemInStockMap = new HashMap<>();
        Map<String, Integer> deptInStockMap = new HashMap<>();
        Map<Long, AssetsDO> assetsDOMap = new HashMap<>();
        Map<String, DeptAssetsDO> deptAssetsDOMap = new HashMap<>();

        if (outDeptId == -1L) {
            List<AssetsDO> assetsDOS = assetsMapper.selectBatchIds(ids);
            assetsDOMap = assetsDOS.stream().collect(Collectors.toMap(AssetsDO::getId, v -> v));
        } else {
            List<DeptAssetsDO> deptAssetsDOS = deptAssetsMapper.selectList(new LambdaQueryWrapper<DeptAssetsDO>()
                    .in(DeptAssetsDO::getAssetsId, ids)
                    .eq(DeptAssetsDO::getDeptId, outDeptId));
            deptAssetsDOMap = deptAssetsDOS.stream().collect(Collectors.toMap(v -> v.getDeptId() + "_" + v.getAssetsId(), v -> v));
        }
        for (AssetsChangeDTO dto : dtos) {
            if (map.get(dto.getAssetsId()) > dto.getStock()) {
                error.add("资产编号为:" + dto.getAssetsNo() + "库存超出!");
                continue;
            }
            List<DeptAssetsInOutStockSaveReqVO> list = inOutStockMap.get(dto.getAssetsId());
            if (CollectionUtil.isNotEmpty(list)) {
                for (DeptAssetsInOutStockSaveReqVO vo : list) {
                    DeptAssetsInOutStockDO deptAssetsInOutStock = new DeptAssetsInOutStockDO();
                    deptAssetsInOutStock.setOutDeptId(outDeptId == -1 ? null : outDeptId);
                    deptAssetsInOutStock.setInDeptId(vo.getInDeptId());
                    deptAssetsInOutStock.setNum(vo.getNum());
                    deptAssetsInOutStock.setInOutNo(IdWorker.get32UUID());
                    deptAssetsInOutStock.setAssetsId(vo.getAssetsId());
                    saveList.add(deptAssetsInOutStock);
                    // -- 入库的部门 得加上数量 -
                    if (vo.getInDeptId() == -1L) {
                        // - 如果是系统入库
                        List<DeptAssetsInOutStockSaveReqVO> stockSaveReqVOS = inOutStockByDeptMap.get(-1L);
                        if (!stockSaveReqVOS.isEmpty()) {
                            Integer num = 0;
                            for (DeptAssetsInOutStockSaveReqVO stockSaveReqVO : stockSaveReqVOS) {
                                num += stockSaveReqVO.getNum();
                            }
                            if (num > 0) {
                                systemInStockMap.put(stockSaveReqVOS.get(0).getAssetsId(), num);
                            }
                        }
                    } else {
                        // - 如果是部门入库 则更新该部门的资产
                        List<DeptAssetsInOutStockSaveReqVO> stockSaveReqVOS = inOutStockByDeptMap.get(vo.getInDeptId());
                        if (!stockSaveReqVOS.isEmpty()) {
                            Integer num = 0;
                            for (DeptAssetsInOutStockSaveReqVO stockSaveReqVO : stockSaveReqVOS) {
                                num += stockSaveReqVO.getNum();
                            }
                            deptInStockMap.put(vo.getInDeptId() + "_" + vo.getAssetsId(), num);
                        }
                    }
                }


                // -- 如果是按件 出库部门 删除该资产 可在出库记录中查询到 -
                // -- 如果是按量的话 出库部门 - 出库的数量 并且如果 <= 0 的话删除该资产 可在出库记录中查询到 -

                Integer outNum = list.stream().mapToInt(DeptAssetsInOutStockSaveReqVO::getNum).sum();
                if (outDeptId == -1L) {
                    AssetsDO assetsDO = assetsDOMap.get(dto.getAssetsId());
                    assetsDO.setStock(assetsDO.getStock() - outNum);
                    updateAssetsList.add(assetsDO);
                } else {
                    DeptAssetsDO deptAssetsDO = deptAssetsDOMap.get(outDeptId + "_" + dto.getAssetsId());
                    if (dto.getType() == 0 && (deptAssetsDO.getStock() - outNum <= 0)) {
                        deptAssetDeptIdDelIds.add(outDeptId);
                        deptAssetAssetsIdDelIds.add(dto.getAssetsId());
                    } else {
                        deptAssetsDO.setStock(deptAssetsDO.getStock() - outNum);
                        updateDeptAssetsList.add(deptAssetsDO);
                    }
                }

            }
        }
        if (!error.isEmpty()) {
            deptAssetsInOutStockVO.setErrors(error);
            return deptAssetsInOutStockVO;
        }
        if (!systemInStockMap.isEmpty()) {
            List<Long> assetsIds = new ArrayList<>(systemInStockMap.keySet());
            List<AssetsDO> assetsDOS = assetsMapper.selectBatchIds(assetsIds);
            for (AssetsDO assetsDO : assetsDOS) {
                assetsDO.setStock(assetsDO.getStock() + systemInStockMap.get(assetsDO.getId()));
            }
            assetsMapper.updateBatch(assetsDOS);
        }
        List<DeptAssetsDO> saveDeptAssetsList = new ArrayList<>();
        List<DeptAssetsDO> editDeptAssetsList = new ArrayList<>();
        if (!deptInStockMap.isEmpty()) {
            List<Long> assetsIds = new ArrayList<>();
            List<Long> deptIds = new ArrayList<>();
            for (String key : deptInStockMap.keySet()) {
                String[] split = key.split("_");
                deptIds.add(Long.parseLong(split[0]));
                assetsIds.add(Long.parseLong(split[1]));
            }
            List<DeptAssetsDO> deptAssetsDOS = deptAssetsMapper.selectList(new LambdaQueryWrapper<DeptAssetsDO>()
                    .in(DeptAssetsDO::getDeptId, deptIds).in(DeptAssetsDO::getAssetsId, assetsIds));
            Map<String, DeptAssetsDO> deptAssetsMap = deptAssetsDOS.stream().collect(Collectors.toMap(v -> v.getDeptId() + "_" + v.getAssetsId(), v -> v));

            for (Map.Entry<String, Integer> entry : deptInStockMap.entrySet()) {
                DeptAssetsDO deptAssetsDO = deptAssetsMap.get(entry.getKey());
                if (deptAssetsDO == null) {
                    String[] split = entry.getKey().split("_");
                    deptAssetsDO = DeptAssetsDO.init(Long.parseLong(split[0]), Long.parseLong(split[1]), entry.getValue());
                    saveDeptAssetsList.add(deptAssetsDO);
                } else {
                    deptAssetsDO.setStock(deptAssetsDO.getStock() + entry.getValue());
                    editDeptAssetsList.add(deptAssetsDO);
                }
            }
            if (!saveDeptAssetsList.isEmpty()) {
                deptAssetsMapper.insertBatch(saveDeptAssetsList);
            }
        }
        if (!deptAssetDeptIdDelIds.isEmpty() && !deptAssetAssetsIdDelIds.isEmpty()) {
            deptAssetsMapper.update(new DeptAssetsDO().setStock(0), new LambdaQueryWrapper<DeptAssetsDO>()
                    .in(DeptAssetsDO::getDeptId, deptAssetDeptIdDelIds)
                    .in(DeptAssetsDO::getAssetsId, deptAssetAssetsIdDelIds));
        }
        if (!updateDeptAssetsList.isEmpty() || !editDeptAssetsList.isEmpty()) {
            updateDeptAssetsList.addAll(editDeptAssetsList);
            deptAssetsMapper.updateBatch(updateDeptAssetsList);
        }
        if (!updateAssetsList.isEmpty()) {
            assetsMapper.updateBatch(updateAssetsList);
        }
        if (!saveList.isEmpty()) {
            deptAssetsInOutStockMapper.insertBatch(saveList);
        }
        return deptAssetsInOutStockVO;
    }

    private List<AssetsChangeDTO> buildDtoByOutDeptId(Long outDeptId, List<Long> ids) {
        List<AssetsChangeDTO> dtos;
        // -- 获取系统的
        if (outDeptId == null) {
            dtos = assetsMapper.selectAssetsChangeByIds(ids);
        } else {
            dtos = deptAssetsMapper.selectAssetsChangeByIds(ids, outDeptId);
        }
        return dtos;
    }

    @Override
    public void updateDeptAssetsInOutStock(DeptAssetsInOutStockSaveReqVO updateReqVO) {
        // 更新
        DeptAssetsInOutStockDO updateObj = BeanUtils.toBean(updateReqVO, DeptAssetsInOutStockDO.class);
        deptAssetsInOutStockMapper.updateById(updateObj);
    }

    @Override
    public void deleteDeptAssetsInOutStock(Long id) {
        // 删除
        deptAssetsInOutStockMapper.deleteById(id);
    }

    @Override
    public DeptAssetsInOutStockDO getDeptAssetsInOutStock(Long id) {
        return deptAssetsInOutStockMapper.selectById(id);
    }

    @Override
    public PageResult<DeptAssetsInOutStockRespVO> getDeptAssetsInOutStockPage(DeptAssetsInOutStockPageReqVO pageReqVO) {
        IPage<DeptAssetsInOutStockRespVO> vos = deptAssetsInOutStockMapper.getDeptAssetsInOutStockPage(pageReqVO, MyBatisUtils.buildPage(pageReqVO));
        return new PageResult<>(vos.getRecords(), vos.getTotal());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void assetAllocation(AssetsDO assetsDO, Long outDeptId, List<AssetAllocationDTO> assetAllocationDTOS) {
        // -- 出库如果是null的话 说明是从系统中扣减的 - 去扣资产的库存
        int num = assetAllocationDTOS.stream().filter(a -> a.getNum() != null).mapToInt(AssetAllocationDTO::getNum).sum();
        if (outDeptId == null) {
            if (num > assetsDO.getStock()) {
                throw exception(STOCK_EXCEEDS);
            }
            assetsDO.setStock(assetsDO.getStock() - num);
            assetsMapper.updateById(assetsDO);
        } else {
            // -- 如果有值的话 那就是从部门出库 - 去扣部门的资产库
            DeptAssetsDO deptAssetsDO = deptAssetsMapper.selectById(outDeptId);
            if (deptAssetsDO == null) {
                deptAssetsDO = DeptAssetsDO.init(outDeptId, assetsDO.getId());
                deptAssetsMapper.insert(deptAssetsDO);
            }
            if (num > deptAssetsDO.getStock()) {
                throw exception(STOCK_EXCEEDS);
            }
            deptAssetsDO.setStock(deptAssetsDO.getStock() - num);
            deptAssetsMapper.updateById(deptAssetsDO);
        }
        // -- 获取部门资产列表

        if (CollectionUtil.isNotEmpty(assetAllocationDTOS)) {
            List<Long> ids = assetAllocationDTOS.stream().map(AssetAllocationDTO::getDeptId).collect(Collectors.toList());
            List<DeptAssetsDO> deptAssetsDOS = deptAssetsMapper.selectList(new LambdaQueryWrapper<DeptAssetsDO>()
                    .eq(DeptAssetsDO::getAssetsId, assetsDO.getId())
                    .in(DeptAssetsDO::getDeptId, ids));
            // 根据部门id分组 - 如果没有的话 再临时初始化一个 - 最后再批量新增
            Map<Long, DeptAssetsDO> map = deptAssetsDOS.stream().collect(Collectors.toMap(DeptAssetsDO::getDeptId, a -> a));
            List<DeptAssetsDO> saveOrUpdateItem = new ArrayList<>();
            List<DeptAssetsInOutStockDO> deptAssetsInOutStockDOS = new ArrayList<>();
            for (AssetAllocationDTO assetAllocationDTO : assetAllocationDTOS) {
                DeptAssetsDO deptAssetsItem = map.get(assetAllocationDTO.getDeptId());
                if (deptAssetsItem == null) {
                    deptAssetsItem = DeptAssetsDO.init(assetAllocationDTO.getDeptId(), assetsDO.getId());
                }
                deptAssetsItem.setStock(deptAssetsItem.getStock() + assetAllocationDTO.getNum());
                // saveOrUpdate
                saveOrUpdateItem.add(deptAssetsItem);
                // -- 添加出入库记录

                // - 出库 - 入库 记录
                DeptAssetsInOutStockDO outStock = new DeptAssetsInOutStockDO();
                outStock.setInDeptId(assetAllocationDTO.getDeptId());
                outStock.setOutDeptId(outDeptId);
                outStock.setAssetsId(assetsDO.getId());
                outStock.setNum(assetAllocationDTO.getNum());
                outStock.setInOutNo(IdWorker.get32UUID());
                deptAssetsInOutStockDOS.add(outStock);
            }

            //批量新增修改
            if (!saveOrUpdateItem.isEmpty()) {
                deptAssetsMapper.insertOrUpdateBatch(saveOrUpdateItem);
            }
            //批量新增出入库记录
            if (!deptAssetsInOutStockDOS.isEmpty()) {
                deptAssetsInOutStockMapper.insertBatch(deptAssetsInOutStockDOS);
            }
        }
    }

    @Override
    public List<InOutStockGroupByAssetsIdVO> getInOutStockGroupByAssetsId(Long deptId, Long assetsId) {
        List<DeptAssetsInOutStockDO> list = deptAssetsInOutStockMapper.unionAllInOutStockByAssetsId(deptId, assetsId);
        // 将list根据DeptId分组合并 然后计算num 如果type==0 的话则进行加法 如果 type==1的则进行减法
        Map<Long, List<DeptAssetsInOutStockDO>> result = list.stream().collect(Collectors.groupingBy(DeptAssetsInOutStockDO::getDeptId));
        List<InOutStockGroupByAssetsIdVO> inOutStockGroupByAssetsIdVOS = new ArrayList<>();
        for (Map.Entry<Long, List<DeptAssetsInOutStockDO>> entry : result.entrySet()) {
            InOutStockGroupByAssetsIdVO vo = new InOutStockGroupByAssetsIdVO();
            vo.setDeptId(entry.getKey());
            Integer num = 0;
            for (DeptAssetsInOutStockDO deptAssetsInOutStockDO : entry.getValue()) {
                num = deptAssetsInOutStockDO.getType() == 0 ? num + deptAssetsInOutStockDO.getNum() : num - deptAssetsInOutStockDO.getNum();
            }
            vo.setNum(num);
            inOutStockGroupByAssetsIdVOS.add(vo);
        }
        return inOutStockGroupByAssetsIdVOS;
    }
}