package cn.iocoder.yudao.module.smartfactory.service.factorydata;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.module.smartfactory.controller.admin.factorydata.vo.*;
import cn.iocoder.yudao.module.smartfactory.controller.admin.factorydata.vo.sizeData.FactoryDataSizeCreateReqVO;
import cn.iocoder.yudao.module.smartfactory.controller.admin.factorydata.vo.sizeData.FactoryDataSizeDetailRespVO;
import cn.iocoder.yudao.module.smartfactory.controller.admin.factorydata.vo.sizeData.FactoryDataSizeSaveReqVO;
import cn.iocoder.yudao.module.smartfactory.dal.dataobject.factorydata.FactoryDataDO;
import cn.iocoder.yudao.module.smartfactory.dal.dataobject.factorydata.FactoryDataSizeDO;
import cn.iocoder.yudao.module.smartfactory.dal.dataobject.factoryinfo.FactoryInfoDO;
import cn.iocoder.yudao.module.smartfactory.dal.mysql.factorydata.FactoryDataMapper;
import cn.iocoder.yudao.module.smartfactory.dal.mysql.factorydata.FactoryDataSizeMapper;
import cn.iocoder.yudao.module.smartfactory.framework.util.StringUtil;
import cn.iocoder.yudao.module.smartfactory.service.factoryinfo.FactoryInfoService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.context.annotation.Lazy;
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.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.module.smartfactory.enums.ErrorCodeConstants.*;


/**
 * 工厂出库入库数据 Service 实现类
 *
 * @author 艾楷
 */
@Service
@Validated
public class FactoryDataServiceImpl implements FactoryDataService {

    @Resource
    private FactoryDataMapper dataMapper;

    @Resource
    @Lazy // 循环依赖，避免报错
    private FactoryDataSizeService dataSizeService;

    @Resource
    private FactoryInfoService factoryInfoService;

    @Resource
    private FactoryDataSizeMapper factoryDataSizeMapper;

    @Override
    public Long createData(List<FactoryDataDO> factoryDataDOS) {

        dataMapper.insertBatch(factoryDataDOS);

        // 返回
        return (long) factoryDataDOS.size();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateData(FactoryDataSizeCreateReqVO updateReqVO) throws Exception {

        // 修改 规格明细数据
        dataSizeService.updateData(updateReqVO);

        //校验 出入库统计数据是否存在
        List<FactoryDataDO> factoryDataDOS = dataMapper.selectListByFactoryAndDate(updateReqVO.getFactoryId(),
                updateReqVO.getDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
                updateReqVO.getType());
        if (factoryDataDOS != null && !factoryDataDOS.isEmpty()) {

            // 更新
            factoryDataDOS.forEach(data -> {

                switch (data.getDataType()) {

                    //出库数据
                    case 1:
                        data.setTotalNum(updateReqVO.getItems().stream().mapToInt(FactoryDataSizeSaveReqVO::getOutNum).sum());
                        data.setDamageNum(updateReqVO.getItems().stream().mapToInt(date -> Optional.ofNullable(date.getOutDamageNum()).orElse(0)).sum());
                        break;
                    //入库数据
                    case 2:

                        data.setTotalNum(updateReqVO.getItems().stream().mapToInt(FactoryDataSizeSaveReqVO::getInNum).sum());
                        data.setDamageNum(updateReqVO.getItems().stream().mapToInt(date -> Optional.ofNullable(date.getInDamageNum()).orElse(0)).sum());
                }
            });

            //更新 出入库统计数据
            dataMapper.updateBatch(factoryDataDOS);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteData(Long factoryId, String date) {

        //删除出入库 规格数据
        dataSizeService.deleteData(factoryId, date);
    }

    @Override
    public FactoryDataSizeDetailRespVO getData(Long factoryId, String date) {

        FactoryDataSizeDetailRespVO respVO = new FactoryDataSizeDetailRespVO();
        respVO.setFactoryId(factoryId);
        respVO.setDate(LocalDate.parse(date));

        //获得 规格明细数据
        List<FactoryDataInfoRespVO> infoRespVOS = dataSizeService.getDetailList(factoryId, new LocalDate[]{LocalDate.parse(date), LocalDate.parse(date)}, null);

        List<FactoryDataSizeDetailRespVO.InItem> inItems = infoRespVOS.stream()
                .filter(info -> info.getInNum() != null || info.getInDamageNum() != null)
                .map(info ->  {
                    FactoryDataSizeDetailRespVO.InItem inItem = BeanUtil.toBean(info, FactoryDataSizeDetailRespVO.InItem.class);
                    inItem.setIsEnable(1);
                    return inItem;
                })
                .collect(Collectors.toList());
        respVO.setInItems(inItems);

        respVO.setOutItems(
                infoRespVOS.stream()
                        .filter(info -> info.getOutNum() != null || info.getOutDamageNum() != null)
                        .map(info -> BeanUtil.toBean(info, FactoryDataSizeDetailRespVO.OutItem.class))
                        .collect(Collectors.toList()));

        if (CollUtil.isNotEmpty(infoRespVOS)) {
            LocalDate localDate = infoRespVOS.get(0).getCreateTime().toLocalDate();
            if (localDate.isEqual(LocalDate.now())) {
                respVO.setIsEdit(1);
            }else {
                respVO.setIsEdit(0);
            }
        }
        return respVO;
    }

    @Override
    public PageResult<FactoryDataDetailRespVO> getDataPage(FactoryDataPageReqVO pageReqVO) {

        //获得 出入库数据分页列表
        IPage<FactoryDataInfoRespVO> mpPage = MyBatisUtils.buildPage(pageReqVO);
        IPage<FactoryDataInfoRespVO> page = dataMapper.selectDetailPage(mpPage, pageReqVO);

        List<FactoryDataInfoRespVO> respVOS = page.getRecords();
        List<FactoryDataDetailRespVO> toDayData = new ArrayList<>();
        if (!respVOS.isEmpty()) {

            // 按 工厂id和日期 进行分组
            Map<Long, Map<LocalDate, List<FactoryDataInfoRespVO>>> groupedByFactoryIdAndDate = respVOS.stream()
                    .collect(Collectors.groupingBy(FactoryDataInfoRespVO::getFactoryId,
                            Collectors.groupingBy(FactoryDataInfoRespVO::getDate)));

            //获得工厂信息列表
            Map<Long, FactoryInfoDO> factoryMaps = convertMap(factoryInfoService.getFactoryList(groupedByFactoryIdAndDate.keySet())
                    , FactoryInfoDO::getId);
            groupedByFactoryIdAndDate.forEach((factoryId, dateMap) -> {

                //按日期倒叙 并遍历
                dateMap.entrySet().stream().sorted(Map.Entry.<LocalDate, List<FactoryDataInfoRespVO>>comparingByKey().reversed()).forEach((date) -> {

                    FactoryDataDetailRespVO dataDetailRespVO = new FactoryDataDetailRespVO();

                    dataDetailRespVO.setFactoryId(factoryId);
                    dataDetailRespVO.setFactoryName(factoryMaps.get(factoryId).getName());
                    dataDetailRespVO.setDate(date.getKey());
                    //设置入库总数
                    int inSum = date.getValue().stream()
                            .map(info -> Optional.ofNullable(info.getInNum()).orElse(0))
                            .mapToInt(Integer::intValue).sum();
                    dataDetailRespVO.setInTotalNum(inSum == 0 ? null : inSum);

                    //设置入库平方总数数
                    dataDetailRespVO.setSquareTotalNum(date.getValue().stream().map(FactoryDataInfoRespVO::getSquareNum)
                            .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));

                    //设置出库总数
                    int outTotalNum = date.getValue().stream().filter(info -> info.getOutNum() != null)
                            .mapToInt(FactoryDataInfoRespVO::getOutNum).sum();
                    dataDetailRespVO.setOutTotalNum(outTotalNum == 0 ? null : outTotalNum);

                    //设置 出入库破损总数
                    int sum = date.getValue().stream().map(info -> {
                        FactoryDataInfoRespVO respVO = BeanUtils.toBean(info, FactoryDataInfoRespVO.class);
                        respVO.setInDamageNum(Optional.ofNullable(info.getInDamageNum()).orElse(0));
                        respVO.setOutDamageNum(Optional.ofNullable(info.getOutDamageNum()).orElse(0));
                        return respVO;
                    }).mapToInt(info -> info.getInDamageNum() + info.getOutDamageNum()).sum();
                    dataDetailRespVO.setDamageNum(sum == 0 ? null : sum);

                    // 设置出入库规格详情
                    dataDetailRespVO.setItems(date.getValue());

                    if (date.getValue().get(0).getCreateTime().toLocalDate().equals(LocalDate.now())) {

                        //设置 可编辑状态
                        dataDetailRespVO.setIsEdit(1);
                    }

                    toDayData.add(dataDetailRespVO);
                });
            });
        }

        return new PageResult<>(toDayData, page.getTotal());
    }

    @Override
    public FactoryDataTotalVO getDataTotal(Long factoryId, LocalDate[] date) {

        return dataMapper.selectDataSum(factoryId, date) == null ? new FactoryDataTotalVO() : dataMapper.selectDataSum(factoryId, date);
    }

    @Override
    public List<FactoryDataTotalVO> getDataTotalByFactory() {

        return dataMapper.selectDataSumByFactory();
    }

    @Override
    public List<FactoryDataDO> getTodayInboundAndOutboundOverview(Date time) {
        String timeStr = null;
        if (time != null) {
            timeStr = DateUtils.dateFormat(time, "yyyy-MM-dd");
        }
        return dataMapper.selectList(new LambdaQueryWrapperX<FactoryDataDO>()
                .eq(timeStr != null, FactoryDataDO::getDate, timeStr));
    }

    @Override
    public List<FactoryDataDO> getByTimeAndFactoryId(Long factoryId, Date beginDay, Date endDay) {
        return dataMapper.selectList(new LambdaQueryWrapperX<FactoryDataDO>()
                .ge(FactoryDataDO::getDate, beginDay)
                .le(FactoryDataDO::getDate, endDay)
                .eq(FactoryDataDO::getFactoryId, factoryId));
    }

    @Override
    public FactoryDataDO getListByType(Long factoryId, LocalDate date, Integer type) {

        return dataMapper.selectListByType(factoryId, date, type);
    }

    @Override
    @Transactional(rollbackFor = Exception.class) // 添加事务，异常则回滚所有导入
    public FactoryImportRespVO importUserList(List<FactoryDataImportVO> list, Integer type) {

        if (CollUtil.isEmpty(list)) {
            throw exception(FACTORY_IMPORT_LIST_IS_EMPTY);
        }

        FactoryImportRespVO respVO = FactoryImportRespVO.builder().createUsernames(new ArrayList<>()).
                updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build();

        list.forEach(data -> {

            //校验，判断是否有不符合的原因
            Long factoryId = null;
            try {
                factoryId = validateFactoryForCreate(data, type);
            } catch (Exception ex) {

                respVO.getFailureUsernames().put(data.getFactoryName() + "-" + data.getDate(), ex.getMessage());
                return;
            }

            // 获取 规格ID
            Long sizeId = Long.valueOf(data.getSizeName().split(":")[0]);
            // 获取 规格名称
            String sizeName = data.getSizeName().split(":")[1];

            List<FactoryDataSizeDO> factoryDataSizeDOs = new ArrayList<>();

            if (data.getNum() != null || data.getSquareNum() != null) {

                FactoryDataSizeDO dataSizeDO = new FactoryDataSizeDO();
                //设置规格信息
                dataSizeDO.setSizeId(sizeId);
                dataSizeDO.setSizeName(sizeName);
                //设置工厂和日期
                dataSizeDO.setFactoryId(factoryId);
                dataSizeDO.setDate(data.getDate());
                // 根据type 设置入库数还是出库数
                dataSizeDO.setType(type == 1 ? 4 : 2);
                try {

                    if (sizeName.contains("*")) {
                        dataSizeDO.setNum(
                                data.getSquareNum() != null ? Integer.parseInt(convertSquare(data.getSquareNum(), 2, sizeName)) : data.getNum()
                        );
                        dataSizeDO.setSquareNum(
                                data.getNum() != null ? new BigDecimal(convertSquare(data.getNum(), 1, sizeName)) : data.getSquareNum()
                        );
                    }else {
                        dataSizeDO.setNum(data.getNum());
                        dataSizeDO.setSquareNum(data.getSquareNum());
                    }
                } catch (Exception ex) {
                    respVO.getFailureUsernames().put(data.getFactoryName() + "-" + data.getDate(), "请正确填写入库数或者入库平方数");
                    return;
                }

                factoryDataSizeDOs.add(dataSizeDO);
            }else {
                respVO.getFailureUsernames().put(data.getFactoryName() + "-" + data.getDate(), "请正确填写入库数或者入库平方数");
                return;
            }

            if (data.getDamageNum() != null) {

                FactoryDataSizeDO dataSizeDamageDO = new FactoryDataSizeDO();
                //设置规格信息
                dataSizeDamageDO.setSizeId(sizeId);
                dataSizeDamageDO.setSizeName(sizeName);
                //设置工厂和日期
                dataSizeDamageDO.setFactoryId(factoryId);
                dataSizeDamageDO.setDate(data.getDate());
                // 根据type 设置入库破损还是出库破损
                dataSizeDamageDO.setType(type == 1 ? 1 : 3);
                dataSizeDamageDO.setNum(data.getDamageNum());

                factoryDataSizeDOs.add(dataSizeDamageDO);
            }

            factoryDataSizeMapper.insertBatch(factoryDataSizeDOs);

//            respVO.getCreateUsernames().add(data.getFactoryName() + "-" + data.getDate());
        });

        return respVO;
    }

    private Long validateFactoryForCreate(FactoryDataImportVO data, Integer type) {

        String factoryName = data.getFactoryName();
        String sizeName = data.getSizeName();
        LocalDate date = data.getDate();

        if (factoryName == null) {
            throw exception(FACTORY_INFO_NOT_EXISTS);
        }

        if (sizeName == null) {
            throw exception(SIZE_NOT_EXISTS);
        }

        FactoryInfoDO factoryInfoDO = factoryInfoService.getFactoryByName(factoryName);

        List<Integer> dataType = new ArrayList<>();
        if ((data.getNum() != null || data.getSquareNum() != null) && data.getDamageNum() != null) {
            dataType = type == 1 ? Arrays.asList(1,4) : Arrays.asList(2,3);
        }

        if ((data.getNum() != null || data.getSquareNum() != null) && data.getDamageNum() == null) {
            dataType = type == 1 ? Collections.singletonList(4) : Collections.singletonList(2);
        }

        if (data.getNum() == null && data.getSquareNum() == null && data.getDamageNum() != null) {
            dataType = type == 1 ? Collections.singletonList(1) : Collections.singletonList(3);
        }

        // 获取 规格ID
        Long sizeId = Long.valueOf(sizeName.split(":")[0]);
        Long count = factoryDataSizeMapper.selectCountByType(factoryInfoDO.getId(), sizeId, date, dataType);

        if (count > 0L) {
            throw exception(PACKAGE_DATA_EXISTS);
        }

        return factoryInfoDO.getId();
    }

    @Override
    public String convertSquare(Object num, Integer type, String sizeName) {

        int size = Integer.parseInt(sizeName.split("\\*")[0]);
        int size1 = Integer.parseInt(sizeName.split("\\*")[1].replaceAll("[^0-9]", ""));

        // 转换规格片数
        int unit = StringUtil.sizeNameAnalysis(sizeName);

        // 判断是箱数转换平方数的情况
        if (type == 1) {

            //计算平方数
            return BigDecimal.valueOf((double) size / 1000)
                    .multiply(BigDecimal.valueOf((double) size1 / 1000))
                    .multiply(BigDecimal.valueOf(unit))
                    .multiply(BigDecimal.valueOf((Integer) num)).toString();
        }else {

            // 计算箱数
            BigDecimal sizeNum = BigDecimal.valueOf((double) size / 1000)
                    .multiply(BigDecimal.valueOf((double) size1 / 1000))
                    .multiply(BigDecimal.valueOf(unit));

            BigDecimal squareNum = new BigDecimal(String.valueOf(num));
            return squareNum.divide(sizeNum, 0, RoundingMode.HALF_UP).toString();
        }
    }
}