/*
 *  Copyright 2019-2020 Zheng Jie
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.youchain.businessdata.service.impl;

import com.youchain.basicdata.domain.Item;
import com.youchain.basicdata.domain.Point;
import com.youchain.basicdata.domain.Stock;
import com.youchain.basicdata.repository.PointRepository;
import com.youchain.basicdata.service.dto.ItemDto;
import com.youchain.basicdata.service.mapstruct.ItemMapper;
import com.youchain.businessdata.domain.*;
import com.youchain.businessdata.repository.*;
import com.youchain.businessdata.service.dto.InventoryDto;
import com.youchain.exception.BadRequestException;
import com.youchain.modules.quartz.utils.TimeNumberUtils;
import com.youchain.modules.system.domain.Dept;
import com.youchain.service.LogService;
import com.youchain.utils.*;
import lombok.RequiredArgsConstructor;
import com.youchain.businessdata.service.PickDetailService;
import com.youchain.businessdata.service.dto.PickDetailDto;
import com.youchain.businessdata.service.dto.PickDetailQueryCriteria;
import com.youchain.businessdata.service.mapstruct.PickDetailMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.*;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.servlet.http.HttpServletResponse;

/**
 * @author huojin
 * @website https://eladmin.vip
 * @description 服务实现
 * @date 2023-08-16
 **/
@Service
@RequiredArgsConstructor
public class PickDetailServiceImpl implements PickDetailService {
    private final PickDetailRepository pickDetailRepository;
    private final PickRepository pickRepository;
    private final InventoryRepository inventoryRepository;
    private final TaskRepository taskRepository;
    private final AgvTaskRepository agvTaskRepository;
    private final ItemMapper itemMapper;
    private final PointRepository pointRepository;
    private final LogService logService;
    private final EntityManager entityMapper;
    private final PickDetailMapper pickDetailMapper;
    private final StockTypeToAreaMap stockTypeToAreaMap;

    @Override
    public Map<String, Object> queryAll(PickDetailQueryCriteria criteria, Pageable pageable) {
        Page<PickDetail> page = pickDetailRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);
        return PageUtil.toPage(page.map(pickDetailMapper::toDto));
    }

    @Override
    public List<PickDetailDto> queryAll(PickDetailQueryCriteria criteria) {
        return pickDetailMapper.toDto(pickDetailRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder)));
    }

    @Override
    @Transactional
    public PickDetailDto findById(Long id) {
        PickDetail pickDetail = pickDetailRepository.findById(id).orElseGet(PickDetail::new);
        ValidationUtil.isNull(pickDetail.getId(), "PickDetail", "id", id);
        return pickDetailMapper.toDto(pickDetail);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PickDetailDto create(PickDetail resources) {
        return pickDetailMapper.toDto(pickDetailRepository.save(resources));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(PickDetail resources) {
        PickDetail pickDetail = pickDetailRepository.findById(resources.getId()).orElseGet(PickDetail::new);
        ValidationUtil.isNull(pickDetail.getId(), "PickDetail", "id", resources.getId());
        pickDetail.copy(resources);
        pickDetailRepository.save(pickDetail);
    }

    @Override
    public void deleteAll(Long[] ids) {
        for (Long id : ids) {
            pickDetailRepository.deleteById(id);
        }
    }

    @Override
    public void download(List<PickDetailDto> all, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        int i = 1;
        for (PickDetailDto pickDetail : all) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("序号", i++);
            map.put("任务号", pickDetail.getPo());
            map.put("物料编码", pickDetail.getItem().getCode());
            map.put("物料名称", pickDetail.getItem().getName());
            map.put("状态", pickDetail.getStatus());
            map.put("订单数量", pickDetail.getOrderQty());
            map.put("分配数量", pickDetail.getAllocatedQty());
            map.put("拣货数量", pickDetail.getPickedQty());
            map.put("备注", pickDetail.getRemark());
            map.put("创建人", pickDetail.getCreateBy());
            map.put("创建时间", pickDetail.getCreateTime());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public synchronized String allocate(long id, double quantity, Point endPoint) {

        PickDetailDto pickDetailDto = findById(id);
        PickDetail pd = toEntity(pickDetailDto);//Dto转实体
        ItemDto itemDto = pickDetailDto.getItem();
        Item item = itemMapper.toEntity(itemDto);
        Dept dept = item.getDept();//仓库
        Pick pick = pd.getPick();


        if (pd.getAllocatedQty() >= pd.getOrderQty()) {
            return pick.getCode() + "出库单;" + item.getCode() + "数量已分配！";
        }
        String areaCode = stockTypeToAreaMap.getValueByKey(item.getGoodType());
        List<Inventory> inventoryList = inventoryRepository.findByItemIdAndAreaCode(item.getId(), areaCode);
        if (inventoryList.isEmpty()) {
            return pick.getCode() + "出库单;" + item.getCode() + "物料库存不足，请先补充库存！";
        }
        double allocateQty = 0;
        double unQty = quantity;//未分配数量
        boolean beSuccess = false;
        Set<String> tps = new HashSet<>();
        for (Inventory inv : inventoryList) {
            //判断托盘是否有任务
            if (taskRepository.existsByStock(inv.getStockCode()) >= 1) {
                tps.add(inv.getStockCode());
                continue;
            }
            Point startPoint = inv.getPoint();//起始点位
            if (unQty == 0) {
                break;
            }
            allocateQty = inv.getQuantity() - inv.getQueuedQty();//库存可用数量
            if (allocateQty <= 0) {
                continue;
            }

            if (unQty < allocateQty) {
                allocateQty = unQty;
            }
            inv.setQueuedQty(inv.getQueuedQty() + allocateQty);
            inventoryRepository.save(inv);
            unQty -= allocateQty;
            /* 更新出库单明细状态*/
            pd.setAllocatedQty(pd.getAllocatedQty() + allocateQty);
            pickDetailRepository.save(pd);

            /** 生成Task任务 */
            createTask(inv, pd, allocateQty, item, startPoint, endPoint, dept);
            beSuccess = true;
        }
        if (beSuccess) {
            return pick.getCode() + "出库单;" + item.getCode() + "分配成功！";
        } else {
            return tps + "托盘有任务！";
        }


    }

    private void createTask(Inventory inv, PickDetail pd, double allocateQty, Item item, Point startPoint, Point endPoint, Dept dept) {
        Task task = new Task();
        task.setTaskType(BizStatus.PICK);
        task.setItem(item);
        task.setItemKey(inv.getItemKey());
        task.setPickDetail(pd);
        task.setBillCode(pd.getPo());
        task.setSrcStock(inv.getStock());
        task.setDstStock(inv.getStock());
        task.setSrcPoint(startPoint);
        task.setDstPoint(endPoint);
        task.setSrcStockCode(inv.getStock().getCode());
        task.setSrcPointCode(startPoint.getCode());
        task.setDstPointCode(endPoint.getCode());
        task.setInvStatus(inv.getStatus());
        task.setTaskStatus(BizStatus.OPEN);
        task.setPlanQty(allocateQty);
        task.setInvId(inv.getId());
        task.setDept(dept);
        taskRepository.save(task);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public synchronized void cancelAllocate(long id, double quantity) throws Exception {

        PickDetailDto pickDetailDto = findById(id);
        PickDetail pd = toEntity(pickDetailDto);
        List<Task> tasks = taskRepository.findByPickNotAllTask(id);
        for (Task task : tasks) {
            //根据task找到对应的库存
            Inventory inv = inventoryRepository.findById(task.getInvId()).get();
            inv.setQueuedQty(inv.getQueuedQty() - pd.getAllocatedQty());
            inventoryRepository.save(inv);

            //删除Task
            taskRepository.delete(task);

            pd.setAllocatedQty(pd.getAllocatedQty() - quantity);
            if (pd.getAllocatedQty() == 0) {
                pd.setStatus(BizStatus.OPEN);
            }
            pickDetailRepository.save(pd);

        }

    }

    @Override
    public long count() {
        return pickDetailRepository.count();
    }

    @Override
    public PickDetail toEntity(PickDetailDto pickDetailDto) {
        return pickDetailMapper.toEntity(pickDetailDto);
    }

    public List<Object[]> queryOut(String type, String date) {
        String hql = "";
        if ("Day".equals(type)) {
            hql = "select sum(d.orderQty),sum(d.pickedQty) from PickDetail d " + " where DATE_FORMAT(d.createTime,'%Y-%m-%d')=DATE_FORMAT(now(),'%Y-%m-%d')";
        } else if ("Month".equals(type)) {
            hql = "select sum(d.orderQty),sum(d.pickedQty) from PickDetail d " + " where DATE_FORMAT(d.createTime,'%Y-%m')=DATE_FORMAT(now(),'%Y-%m')";
        } else if ("InOutDay".equals(type)) {
            hql = "select DATE_FORMAT(d.createTime,'%Y-%m-%d'),sum(d.pickedQty) from PickDetail d " + " where DATE_FORMAT(d.createTime,'%Y-%m-%d')>='" + date + "' " + " group by DATE_FORMAT(d.createTime,'%Y-%m-%d')";
        }
        Query query = entityMapper.createQuery(hql);
        List<Object[]> ts = query.getResultList();
        return ts;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public  void allocatePick(Pick pick) {

        //目标库位
        Point endPoint = null;
        if (pick.getPoint() == null) {
            String areaCode = null;
            if (BaseStatus.GD_TYPE_CT.equals(pick.getOrderType())) {
                areaCode = AreaNameDic.XJFBQ;
            } else if (BaseStatus.GD_TYPE_DP.equals(pick.getOrderType())) {
                areaCode = AreaNameDic.DJFBQ;
            }else{
                throw new BadRequestException("出库单类型错误！");
            }

            List<Point> pointList = pointRepository.findByCode(null, BaseStatus.FREE, BaseStatus.BOX, areaCode, null, null);
            if (pointList.isEmpty()) {
                logService.saveLogInfo(pick.getCode(), pick.getCode(), "/pick/allocatePick", "分配失败，翻包区没有空闲点位!", "出库单分配", 200, "info");
                throw new BadRequestException(pick.getCode() + "分配失败，翻包区没有空闲点位!");
            }
            endPoint = pointList.get(0);
        } else {
            endPoint = pick.getPoint();
        }


        List<String> msgList = new ArrayList<>();//存放提示信息
        //查询出待分配的pickDetail
        List<PickDetail> pickDetails = pickDetailRepository.findByPickDetailNoAllocate(pick.getId());
        for (PickDetail pickDetail : pickDetails) {
            msgList.add(allocate(pickDetail.getId(), pickDetail.getOrderQty(), endPoint));
        }

        /* 更新出库单状态并写入目标点位*/
        refreshPickStatus(pick, endPoint);

        //保存日志
        logService.saveLogInfo(pick.getCode(), pick.getCode(), "/pick/allocatePick", msgList.toString(), "出库单分配", 200, "info");
    }

    /**
     * 刷新出库单状态
     *
     * @param pick
     * @param endPoint
     */
    private void refreshPickStatus(Pick pick, Point endPoint) {
        //查询出库单的所有明细
        List<PickDetail> allocatePickDetailList = pickDetailRepository.findByAllPickDetail(pick.getId());
        //判断是否全部分配完
        boolean allAllocated = allocatePickDetailList.stream().allMatch(detail -> detail.getOrderQty().equals(detail.getAllocatedQty()));
        //判断是否有部分分配完
        boolean assAllocated = allocatePickDetailList.stream().anyMatch(detail -> detail.getOrderQty().equals(detail.getAllocatedQty()));
        // 当前状态
        String status = pick.getStatus();

        //更新状态且点位占用
        if (allAllocated) {
            status = BizStatus.ALLOCATE;
        } else if (assAllocated) {
            status = BizStatus.ASSIGN;
        }
        // 更新pick状态和点位状态
        pick.setStatus(status);
        pick.setPoint(endPoint);
        pickRepository.save(pick);

        endPoint.setStatus(BaseStatus.USED);
        pointRepository.save(endPoint);

    }
}
