package com.cf.imes.module.executor.service.plan;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.cf.imes.framework.common.util.date.LocalDateTimeUtils;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.mybatis.core.query.MPJLambdaWrapperX;
import com.cf.imes.framework.mybatis.core.query.QueryWrapperX;
import com.cf.imes.module.executor.dal.dataobject.goods.GoodsDO;
import com.cf.imes.module.executor.dal.dataobject.module.ModuleDO;
import com.cf.imes.module.executor.dal.dataobject.order.OrderDO;
import com.cf.imes.module.executor.dal.dataobject.orderBody.OrderBodyDO;
import com.cf.imes.module.executor.dal.dataobject.orderItem.OrderItemDO;
import com.cf.imes.module.executor.dal.dataobject.planitem.PlanItemDO;
import com.cf.imes.module.executor.dal.dataobject.plate.PlateDO;
import com.cf.imes.module.executor.dal.mysql.goods.GoodsMapper;
import com.cf.imes.module.executor.dal.mysql.module.ModuleMapper;
import com.cf.imes.module.executor.dal.mysql.order.OrderMapper;
import com.cf.imes.module.executor.dal.mysql.orderBody.OrderBodyMapper;
import com.cf.imes.module.executor.dal.mysql.orderItem.OrderItemMapper;
import com.cf.imes.module.executor.dal.mysql.planitem.PlanItemMapper;
import com.cf.imes.module.executor.dal.mysql.plate.PlateMapper;
import com.cf.imes.module.system.api.machine.MachineApi;
import com.cf.imes.module.system.api.machine.dto.CuttingRespDTO;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

import com.cf.imes.module.executor.controller.admin.plan.vo.*;
import com.cf.imes.module.executor.dal.dataobject.plan.PlanDO;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.pojo.PageParam;
import com.cf.imes.framework.common.util.object.BeanUtils;

import com.cf.imes.module.executor.dal.mysql.plan.PlanMapper;

import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.module.executor.enums.ErrorCodeConstants.*;

/**
 * 生产单开料排单 Service 实现类
 *
 * @author 晨丰科技
 */
@Service
@Validated
public class PlanServiceImpl implements PlanService {

    @Resource
    private PlanMapper planMapper;

    @Resource
    private GoodsMapper goodsMapper;

    @Resource
    private PlanItemMapper planItemMapper;

    @Resource
    private OrderItemMapper orderItemMapper;

    @Resource
    private OrderMapper orderMapper;

    @Resource
    private PlateMapper plateMapper;

    @Resource
    private MachineApi machineApi;

    @Resource
    private ModuleMapper moduleMapper;

    @Resource
    private OrderBodyMapper orderBodyMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createPlan(PlanSaveReqVO createReqVO) {
        // 插入
        PlanDO plan = BeanUtils.toBean(createReqVO, PlanDO.class);
        planMapper.insert(plan);
        Long planId = plan.getId();
       /* List<Long> orderIds = createReqVO.getOrderIds();
        if (CollectionUtil.isNotEmpty(orderIds)) {
            ArrayList<PlanOrderDO> planOrderDOS = new ArrayList<>();
            for (Long orderId : orderIds) {
                planOrderDOS.add(PlanOrderDO.builder()
                        .planId(planId)
                        .orderId(orderId)
                        .build());
            }
            planOrderMapper.insertBatch(planOrderDOS);
        }*/
        List<Long> plateIds = createReqVO.getPlateIds();
        if (CollectionUtil.isNotEmpty(plateIds)) {
            ArrayList<PlanItemDO> planItemDOS = new ArrayList<>();
            for (Long plateId : plateIds) {
                planItemDOS.add(PlanItemDO.builder()
                        .planId(planId)
                        .itemId(plateId)
                        .build());
            }
            planItemMapper.insertBatch(planItemDOS);
        }
        // 返回
        return plan.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePlan(PlanSaveReqVO updateReqVO) {
        // 校验存在
        validatePlanExists(updateReqVO.getId());
        // 更新
        PlanDO plan = BeanUtils.toBean(updateReqVO, PlanDO.class);
        planMapper.updateById(plan);
        Long planId = plan.getId();

        /*planOrderMapper.delete(new LambdaQueryWrapperX<PlanOrderDO>().eq(PlanOrderDO::getPlanId, planId));*/
        planItemMapper.delete(new LambdaQueryWrapperX<PlanItemDO>().eq(PlanItemDO::getPlanId, planId));

       /* List<Long> orderIds = updateReqVO.getOrderIds();
        if (CollectionUtil.isNotEmpty(orderIds)) {
            ArrayList<PlanOrderDO> planOrderDOS = new ArrayList<>();
            for (Long orderId : orderIds) {
                planOrderDOS.add(PlanOrderDO.builder()
                        .planId(planId)
                        .orderId(orderId)
                        .build());
            }
            planOrderMapper.insertBatch(planOrderDOS);
        }*/
        List<Long> plateIds = updateReqVO.getPlateIds();
        if (CollectionUtil.isNotEmpty(plateIds)) {
            ArrayList<PlanItemDO> planItemDOS = new ArrayList<>();
            for (Long plateId : plateIds) {
                planItemDOS.add(PlanItemDO.builder()
                        .planId(planId)
                        .itemId(plateId)
                        .build());
            }
            planItemMapper.insertBatch(planItemDOS);
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deletePlan(Long id) {
        // 校验存在
        PlanDO planDO = planMapper.selectById(id);
        if (planDO == null) {
            throw exception(PLAN_NOT_EXISTS);
        }
        if (planDO.getStatus().equals(2) || planDO.getStatus().equals(3)) {
            throw exception(PLAN_NOT_ALLOW_DELETE);
        }
        // 删除
        planMapper.deleteById(id);
        /*planOrderMapper.delete(new LambdaQueryWrapperX<PlanOrderDO>().eq(PlanOrderDO::getPlanId, id));*/
        planItemMapper.delete(new LambdaQueryWrapperX<PlanItemDO>().eq(PlanItemDO::getPlanId, id));
        return Boolean.TRUE;
    }

    private void validatePlanExists(Long id) {
        if (planMapper.selectById(id) == null) {
            throw exception(PLAN_NOT_EXISTS);
        }
    }

    @Override
    public PlanRespVO getPlan(Long id) {
        PlanDO planDO = planMapper.selectById(id);
        MPJLambdaWrapper<OrderItemDO> wrapper = new MPJLambdaWrapperX<OrderItemDO>().select(OrderItemDO::getOrderId)
                .rightJoin(PlanItemDO.class, PlanItemDO::getItemId, OrderItemDO::getId)
                .isNotNull(OrderItemDO::getId);
        Set<Long> orderIds = orderItemMapper.selectJoinList(OrderItemDO.class, wrapper).stream().map(OrderItemDO::getOrderId).collect(Collectors.toSet());

        /*Set<Long> orderIds = planOrderMapper.selectList(new LambdaQueryWrapperX<PlanOrderDO>().eq(PlanOrderDO::getPlanId, planDO.getPlanNo()))
                .stream()
                .map(e -> e.getOrderId())
                .collect(Collectors.toSet());*/
        PlanRespVO respVO = BeanUtils.toBean(planDO, PlanRespVO.class);
        List<GoodsDO> goodsDOS = goodsMapper.selectList(new LambdaQueryWrapperX<GoodsDO>().in(GoodsDO::getOrderId, orderIds));
        List<PlateDO> plateDOS = plateMapper.selectList(new LambdaQueryWrapperX<PlateDO>().in(PlateDO::getGoodsId, goodsDOS.stream().map(GoodsDO::getGoodsId).collect(Collectors.toSet())));
        List<PlateInfoVO> plateInfoVOS = goodsDOS.stream().map(e -> PlateInfoVO.builder()
                        .material(e.getMaterial())
                        .width(e.getWidth())
                        .height(e.getHeight())
                        .thickness(e.getThickness())
                        .area(e.getHeight().multiply(e.getWidth()).setScale(2, RoundingMode.HALF_UP))
                        .count(plateDOS.stream().filter(f -> Objects.equals(f.getGoodsId(), e.getGoodsId())).collect(Collectors.toSet()).size())
                        .build())
                .collect(Collectors.toList());
        respVO.setPlateInfoList(plateInfoVOS);
        return respVO;
    }

    @Override
    public PageResult<PlanRespVO> getPlanPage(PlanPageReqVO pageReqVO) {
        PageResult<PlanDO> planDOPageResult = planMapper.selectPage(pageReqVO);
        if (CollectionUtil.isEmpty(planDOPageResult.getList())) {
            return new PageResult<>();
        }
        Set<Long> macheineIds = planDOPageResult.getList().stream().map(PlanDO::getMachineId).collect(Collectors.toSet());
        List<CuttingRespDTO> machines = machineApi.list(macheineIds).getCheckedData();
        Set<Long> ids = planDOPageResult.getList().stream().map(PlanDO::getId).collect(Collectors.toSet());
        List<GoodsDO> goodsDOS = goodsMapper.selectList(new LambdaQueryWrapperX<GoodsDO>().in(GoodsDO::getOrderId, ids));
        List<PlateDO> plateDOS = plateMapper.selectList(new LambdaQueryWrapperX<PlateDO>().in(PlateDO::getGoodsId, goodsDOS.stream().map(GoodsDO::getGoodsId).collect(Collectors.toSet())));
        PageResult<PlanRespVO> planRespVOPageResult = BeanUtils.toBean(planDOPageResult, PlanRespVO.class);
        planRespVOPageResult.getList().forEach(e -> {
            e.setMachineName(machines.stream().filter(f -> Objects.equals(f.getId(), e.getMachineId())).map(CuttingRespDTO::getName).findAny().orElse(null));
            e.setPlateInfoList(goodsDOS.stream()
                    .filter(f -> Objects.equals(f.getOrderId(), e.getId()))
                    .map(p -> PlateInfoVO.builder()
                            .material(p.getMaterial())
                            .width(p.getWidth())
                            .height(p.getHeight())
                            .thickness(p.getThickness())
                            .area(p.getHeight().multiply(p.getWidth()).setScale(2, RoundingMode.HALF_UP))
                            .count(plateDOS.stream().filter(f -> Objects.equals(f.getGoodsId(), p.getGoodsId())).collect(Collectors.toSet()).size())
                            .build())
                    .collect(Collectors.toList()));
        });
        return planRespVOPageResult;
    }

    @Override
    public PageResult<OrderRespVO> getOrderPage(OrderPageReqVO pageReqVO) {
        PageDTO<OrderRespVO> page = new PageDTO<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());

        QueryWrapperX<OrderDO> queryWrapperX = new QueryWrapperX<>();
        queryWrapperX
                .eqIfPresent("customer", pageReqVO.getConsignee())
                .eqIfPresent("address", pageReqVO.getConsigneeAddress())
                .eqIfPresent("custom_order_no", pageReqVO.getDefaultId())
                .betweenIfPresent("delivery_date", new Date[]{pageReqVO.getBeginDate(), pageReqVO.getEndDate()})
                .isNull("d.order_id")
        ;
        List<Integer> filterTypes = new ArrayList<>();
        if (pageReqVO.isHoleThrough()) {
            filterTypes.add(1);
        }
        if (pageReqVO.isBurrow()) {
            filterTypes.add(2);
        }
        if (pageReqVO.isTwoDimensionalToolPath()) {
            filterTypes.add(4);
        }

        queryWrapperX.inIfPresent("c.filter_type", filterTypes);

        IPage<OrderRespVO> orderDOPageResult = orderMapper.selectOrderPage(page, queryWrapperX);
        if (CollectionUtil.isEmpty(orderDOPageResult.getRecords())) {
            return new PageResult<>();
        }
        return new PageResult<>(orderDOPageResult.getRecords(), orderDOPageResult.getTotal());
    }

    @Override
    public PageResult<PlatePage> getNotPlanPlateListPage(PlateReqPageVO pageVO) {
        PageDTO<PlatePage> page = new PageDTO<>(pageVO.getPageNo(), pageVO.getPageSize());
        IPage<PlatePage> pageRes = plateMapper.selectPlatePage(page, pageVO.getOrderId(), pageVO.getGoodsId());
        return new PageResult<PlatePage>(pageRes.getRecords(), pageRes.getTotal());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addPlate(AddPlateReq req) {
        List<OrderItemDO> orderItemDOS = new ArrayList<>();
        List<PlanItemDO> planItemDOS = new ArrayList<>();
        for (AddPlateReq.Obj obj : req.getList()) {
            orderItemDOS.add(OrderItemDO.builder()
                    .planId(req.getPlanId())
                    .orderId(obj.getOrderId())
                    .plateId(obj.getPlateId())
                    .type(1)
                    .build());

            planItemDOS.add(PlanItemDO.builder()
                    .planId(req.getPlanId())
                    .itemId(obj.getPlateId())
                    .build());
        }
        orderItemMapper.insertBatch(orderItemDOS);
        planItemMapper.insertBatch(planItemDOS);
        return Boolean.TRUE;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean cancellation(Long id) {
        PlanDO planDO = planMapper.selectById(id);
        if (planDO == null) {
            throw exception(PLAN_NOT_EXISTS);
        }
        if (planDO.getStatus().equals(0) || planDO.getStatus().equals(1)) {
            throw exception(PLAN_NOT_ALLOW_CANCEL);
        }
        Set<Long> itemIds = planItemMapper.selectList(new LambdaQueryWrapperX<PlanItemDO>().eq(PlanItemDO::getPlanId, id))
                .stream().map(PlanItemDO::getItemId)
                .collect(Collectors.toSet());
        plateMapper.update(new LambdaUpdateWrapper<PlateDO>().in(PlateDO::getId, itemIds).set(PlateDO::getIsCancel, Boolean.TRUE));
        return Boolean.TRUE;
    }

    @Override
    public List<PlateResList> getPlateByPlanId(GetPlateByPlanIdVO vo) {
        validatePlanExists(vo.getPlanId());
        QueryWrapperX<PlateDO> queryWrapperX = new QueryWrapperX<>();
        List<Integer> filterTypes = new ArrayList<>();
        if (vo.isHoleThrough()) {
            filterTypes.add(1);
        }
        if (vo.isBurrow()) {
            filterTypes.add(2);
        }
        if (vo.isTwoDimensionalToolPath()) {
            filterTypes.add(4);
        }
        queryWrapperX.betweenIfPresent("a.width", new BigDecimal[]{vo.getWidthMinRang(), vo.getWidthMaxRang()})
                .betweenIfPresent("a.height", new BigDecimal[]{vo.getLongMinRang(), vo.getLongMaxRang()})
                .inIfPresent("a.filter_type", filterTypes)
                .betweenIfPresent("f.delivery_date", new Date[]{vo.getBeginDate(), vo.getEndDate()})
                .inIfPresent("a.filter_type", filterTypes)
                .eq("p.plan_id", vo.getPlanId())
        ;


        List<PlateResList> list = plateMapper.selectPlateByPlanId(queryWrapperX);
        Set<Long> bodyIds = list.stream().map(PlateResList::getBodyId).collect(Collectors.toSet());
        Set<Long> roomIds = list.stream().map(PlateResList::getRoomId).collect(Collectors.toSet());
        if(CollectionUtil.isNotEmpty(bodyIds)) {
            List<OrderBodyDO> orderBodyDOS = orderBodyMapper.selectBatchIds(bodyIds);
            for (PlateResList resList : list) {
                resList.setBodyName(orderBodyDOS.stream().filter(e -> Objects.equals(e.getId(), resList.getBodyId())).map(OrderBodyDO::getName).findAny().orElse(null));
                resList.setRoomName(orderBodyDOS.stream().filter(e -> Objects.equals(e.getId(), resList.getRoomId())).map(OrderBodyDO::getRoomName).findAny().orElse(null));
            }
        }
        //todo 获取柜体名称，房间名称
       /* List<ModuleDO> moduleDOS = moduleMapper.selectList(new LambdaQueryWrapperX<ModuleDO>().in(ModuleDO::getId, bodyIds).eq(ModuleDO::getType, 2)
                .or().in(ModuleDO::getId, roomIds).eq(ModuleDO::getType, 1)
        );
        for (PlateResList resList : list) {
            resList.setBodyName(moduleDOS.stream().filter(e -> Objects.equals(e.getId(), resList.getBodyId())).map(ModuleDO::getName).findAny().orElse(null));
            resList.setRoomName(moduleDOS.stream().filter(e -> Objects.equals(e.getId(), resList.getRoomId())).map(ModuleDO::getName).findAny().orElse(null));
        }*/
        return list;
    }

}