/*
 *  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.service.PointService;
import com.youchain.basicdata.service.StockService;
import com.youchain.businessdata.domain.*;
import com.youchain.businessdata.repository.*;
import com.youchain.businessdata.service.*;
import com.youchain.exception.BadRequestException;
import com.youchain.service.LogService;
import com.youchain.utils.*;
import lombok.RequiredArgsConstructor;
import com.youchain.businessdata.service.dto.TaskDto;
import com.youchain.businessdata.service.dto.TaskQueryCriteria;
import com.youchain.businessdata.service.mapstruct.TaskMapper;
import lombok.extern.slf4j.Slf4j;
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 javax.servlet.http.HttpServletResponse;

/**
 * @author houjianlan
 * @website https://eladmin.vip
 * @description 服务实现
 * @date 2023-08-16
 **/
@Service
@RequiredArgsConstructor
@Slf4j
public class TaskServiceImpl implements TaskService {

    private final TaskRepository taskRepository;
    private final TaskMapper taskMapper;
    private final PointService pointService;
    private final StockService stockService;
    private final PickService pickService;
    private final LogService logService;
    private final PickDetailService pickDetailService;
    private final AgvTaskService agvTaskService;
    private final InventoryService inventoryService;
    private final InventoryLogService inventoryLogService;
    private final StockTypeToAreaMap stockTypeToAreaMap;

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

    @Override
    public List<TaskDto> queryAll(TaskQueryCriteria criteria) {
        return taskMapper.toDto(taskRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder)));
    }

    @Override
    public Task findById(Long id) {
        return taskRepository.findById(id).orElseGet(Task::new);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public TaskDto create(Task resources) {
        return taskMapper.toDto(taskRepository.save(resources));
    }

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

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

    @Override
    public void download(List<TaskDto> all, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (TaskDto task : all) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("序号", task.getId());
            map.put("物料编码", task.getItemKey().getItem().getCode());
            map.put("物料名称", task.getItemKey().getItem().getName());
            map.put("容器编号", task.getSrcStock() == null ? "" : task.getSrcStock().getCode());
            map.put("原点位", task.getSrcPointCode());
            map.put("目标点位", task.getDstPointCode());
            map.put("任务状态", task.getTaskStatus());
            map.put("计划数量", task.getPlanQty());
            map.put("移位数量", task.getMoveQty());
            map.put("创建人", task.getCreateBy());
            map.put("创建时间", task.getCreateTime());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }

    @Override
    public List<Task> findTaskByAgvTask(Long agvTaskId) {
        return taskRepository.findTaskByAgvTask(agvTaskId);
    }

    @Override
    public List<Task> findTaskByCallAgvTask(Long callAgvTaskId) {
        return taskRepository.findTaskByCallAgvTask(callAgvTaskId);
    }

    @Override
    public List<Task> findByPickNotAllTask(Long pickId) {
        return taskRepository.findByPickNotAllTask(pickId);
    }

    @Override
    public List<Task> findByPickAllTask(Long pickId) {
        return taskRepository.findByPickAllTask(pickId);
    }

    @Override
    public List<Task> findByPickDetailNotAllTask(Long pickDetailId) {
        return taskRepository.findByPickDetailNotAllTask(pickDetailId);
    }

    @Override
    public List<Task> findByPickDetailAllTask(Long pickDetailId) {
        return taskRepository.findByPickDetailAllTask(pickDetailId);
    }

    @Override
    public List<Task> findTaskByContains(String taskType, String taskStatus, String srcStockCode) {
        return taskRepository.findTaskByContains(taskType, taskStatus, srcStockCode);
    }

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

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

            List<Point> pointList = pointService.queryPoints(null, BaseStatus.FREE, BaseStatus.BOX, areaName);
            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 = pickDetailService.findByPickDetailNoAllocate(pick.getId());
        for (PickDetail pickDetail : pickDetails) {
            msgList.add(this.allocate(pickDetail.getId(), pickDetail.getOrderQty(), endPoint));
        }

        /* 更新出库单状态*/
        pickService.refreshPickStatus(pick);

        if (BizStatus.ALLOCATE.equals(pick.getStatus()) || BizStatus.ASSIGN.equals(pick.getStatus())) {
            //绑定点位
            pick.setPoint(endPoint);
            pickService.update(pick);

            //占用点位
            pointService.usedPoint(endPoint);
        }


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

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void materialPick(Pick pick) {
        AgvTask agvTask = null;
        List<Task> taskList = this.findByPickNotAllTask(pick.getId());
        for (Task task : taskList) {
            /*生成搬运任务*/
            List<AgvTask> agvTaskList = agvTaskService.findRepeat(task.getSrcStockCode(), task.getSrcPointCode(), task.getDstPointCode());
            if (agvTaskList.isEmpty()) {
                //创建任务
                agvTask = agvTaskService.createAgvTask(BizStatus.PICK, task.getSrcStock(), task.getSrcPoint().getCode(), task.getDstPoint().getCode(), "FORKLIFT_MOVE");

                //终点占用
                pointService.usedPoint(task.getDstPoint());
            } else {
                agvTask = agvTaskList.get(0);
            }
            task.setAgvTask(agvTask);
            task.setTaskStatus(agvTask.getStatus());
            taskRepository.save(task);


           /* //叫料区是否有空车，呼叫到备料缓存区
            this.callEmptyStock(task.getPickDetail().getItem().getGoodType());*/
        }
        /*更新出库单状态*/
        pick.setStatus(BizStatus.PICKUP);
        pickService.update(pick);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void callEmptyStock(String goodType) {
        String srcAreaCode = "小件".equals(goodType) ? AreaNameDic.XJJLQ : AreaNameDic.DJJLQ;

        String dstAreaCode = "小件".equals(goodType) ? AreaNameDic.XJFBCQ : AreaNameDic.DJFBCQ;

        List<Stock> emptyStock = stockService.findByFreeOrUsedStock(srcAreaCode, BaseStatus.FREE);
        if (emptyStock.isEmpty()) {
            return;
        }
        //容器
        Stock stock = emptyStock.get(0);

        //起点
        Point srcPoint = stock.getPoint();

        //终点
        List<Point> pointList = pointService.queryPoints(null, BaseStatus.FREE, BaseStatus.BOX, dstAreaCode);
        if (pointList.isEmpty()) {
            return;
        }
        Point endPoint = pointList.get(0);

        //有任务则直接返回
        if(!agvTaskService.findByEndSlotCode(endPoint.getCode(),BizStatus.CALL_RETURN,"RACK_MOVE")){
            return;
        }

        //终点占用
        pointService.usedPoint(endPoint);
        //创建任务
        agvTaskService.createAgvTask(BizStatus.CALL_RETURN, stock, srcPoint.getCode(), endPoint.getCode(), "RACK_MOVE");




    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void callJlTask(Pick pick) {
        if (pick.getStock() == null) {
            throw new BadRequestException(pick.getGdCode() + "请翻包拣货!");
        }
        if (pick.getCallPoint() == null) {
            throw new BadRequestException(pick.getGdCode() + "工单没有叫料请求!");
        }

        //起点
        Point srcPoint = pick.getStock().getPoint();

        //终点
        Point endPoint = pick.getCallPoint();

        //容器
        Stock stock = pick.getStock();

        //创建任务
        AgvTask agvTask = agvTaskService.createAgvTask(BizStatus.CALL_PICK, stock, srcPoint.getCode(), endPoint.getCode(), "RACK_MOVE");
        agvTask.setLineSlotCode(pick.getCode());
        agvTaskService.update(agvTask);

        //绑定任务
        List<Task> taskList = this.findByPickAllTask(pick.getId());
        for (Task task : taskList) {
            task.setCallAgvTaskId(agvTask.getId());
            taskRepository.save(task);
        }

        //起点占用
        pointService.usedPoint(srcPoint);

        //终点占用
        pointService.usedPoint(endPoint);

        //更新出库单状态为叫料中
        pick.setStatus(BizStatus.SHIP_PART);
        pickService.update(pick);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public synchronized String allocate(long id, double quantity, Point endPoint) {
        //出库明细
        PickDetail pickDetail = pickDetailService.findById(id);

        //物料
        Item item = pickDetail.getItem();

        //出库单
        Pick pick = pickDetail.getPick();

        if (pickDetail.getAllocatedQty() >= pickDetail.getOrderQty()) {
            return pick.getCode() + "出库单;" + item.getCode() + "数量已分配！";
        }
        String areaName = stockTypeToAreaMap.getValueByKey(item.getGoodType());
        List<Inventory> inventoryList = inventoryService.queryInventory(null, item.getId(), null, areaName, item.getDept().getId());
        if (inventoryList.isEmpty()) {
            return pick.getCode() + "出库单;" + item.getCode() + "物料库存不足，请先补充库存！";
        }
        double allocateQty = 0;
        //未分配数量
        double unQty = quantity;
        Set<String> tps = new HashSet<>();
        for (Inventory inv : inventoryList) {
            //判断托盘是否有任务
            if (this.existsByStock(pick.getCode(), 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);
            inventoryService.update(inv);
            unQty -= allocateQty;
            /* 更新出库单明细状态*/
            pickDetail.setAllocatedQty(pickDetail.getAllocatedQty() + allocateQty);
            pickDetailService.update(pickDetail);

            //生成Task任务
            this.createTask(item, allocateQty, inv.getItemKey(), null, BizStatus.PICK, pickDetail, inv.getId(), inv.getStock(), startPoint, endPoint, null);

        }
        if (tps.isEmpty()) {
            return pick.getCode() + "出库单中" + item.getCode() + "物料分配成功！";
        }
        return tps + "托盘有任务！";
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public synchronized void cancelAllocate(long id, double quantity) {
        PickDetail pd = pickDetailService.findById(id);
        List<Task> tasks = this.findByPickDetailNotAllTask(id);
        for (Task task : tasks) {
            //根据task找到对应的库存
            Inventory inv = inventoryService.findById(task.getInvId());
            inv.setQueuedQty(inv.getQueuedQty() - pd.getAllocatedQty());
            inventoryService.update(inv);

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

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

        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public synchronized void pickConfirm(Long taskId, double pickedQuantity) {
        Task task = this.findById(taskId);
        if (task.getPlanQty() > task.getMoveQty()) {
            PickDetail pickDetail = task.getPickDetail();
            task.setMoveQty(pickedQuantity);
            task.setTaskStatus(BizStatus.FINISH);
            taskRepository.save(task);

            if (task.getInvId() == null) {
                throw new BadRequestException("未找到库存相应记录！");
            }
            Inventory inv = inventoryService.findById(task.getInvId());
            inv.setQuantity(inv.getQuantity() - pickedQuantity);
            inv.setQueuedQty(inv.getQueuedQty() - pickedQuantity);
            inventoryService.update(inv);

            //释放容器
            stockService.usedStock(inv.getStock(), inv.getPoint(), BaseStatus.FREE);

            if (inv.getQuantity() <= 0) {
                inventoryService.deleteAll(new Long[]{inv.getId()});
            }

            //添加库存日志
            inventoryLogService.createInventoryLog(BizStatus.PICK_DOWN, BizStatus.REDUCE, task.getPickDetail().getPo(), task.getItemKey(), task.getSrcPoint(), task.getDstPoint(), task.getSrcStock(), task.getDstStock(), inv.getQuantity(), pickedQuantity, BizStatus.PICK, task.getId(), inv.getId(), "");

            //回写拣货数量
            pickDetail.setPickedQty(pickDetail.getPickedQty() + pickedQuantity);
            pickDetailService.update(pickDetail);

            //刷新状态
            pickService.refreshPickStatus(pickDetail.getPick());
        }


    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public synchronized void pickBarBack(long taskId, double pickedQuantity) {
        Task task = findById(taskId);
        PickDetail pickDetail = task.getPickDetail();
        if (task.getInvId() == null) {
            throw new BadRequestException("未找到库存相应记录！");
        }
        Inventory inv = inventoryService.findById(task.getInvId());

        //退回库存
        double srcQty = inv.getQueuedQty();
        inv.setQueuedQty(inv.getQueuedQty() + pickedQuantity);
        inv.setQuantity(inv.getQuantity() + pickedQuantity);
        inventoryService.update(inv);

        //添加库存日志
        inventoryLogService.createInventoryLog(BizStatus.PICK_CANCEL, BizStatus.ADD, task.getPickDetail().getPo(), task.getItemKey(), task.getDstPoint(), task.getSrcPoint(), task.getSrcStock(), task.getDstStock(), srcQty, pickedQuantity, BizStatus.PICK, task.getId(), inv.getId(), "");


        //退回Task
        task.setMoveQty(task.getMoveQty() - pickedQuantity);
        taskRepository.save(task);

        //退回拣货数量
        pickDetail.setPickedQty(pickDetail.getPickedQty() - pickedQuantity);
        pickDetailService.update(pickDetail);

        //更新出库单状态
        pickService.refreshPickStatus(pickDetail.getPick());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Task createTask(Item item, Double quantity, ItemKey itemKey, AsnDetail asnDetail, String taskType, PickDetail pickDetail, Long invId, Stock stock, Point srcPoint, Point dstPoint, AgvTask agvTask) {
        Task task = new Task();
        task.setItem(item);
        task.setItemKey(itemKey);
        task.setBillCode(asnDetail == null ? "" : asnDetail.getOrderNumber());
        task.setTaskType(taskType);
        task.setAsnDetail(asnDetail);
        task.setPickDetail(pickDetail);
        task.setInvId(invId);
        task.setSrcStock(stock);
        task.setDstStock(stock);
        task.setSrcPoint(srcPoint);
        task.setDstPoint(dstPoint);
        task.setSrcStockCode(stock == null ? null : stock.getCode());
        task.setDstStockCode(stock == null ? null : stock.getCode());
        task.setSrcPointCode(srcPoint == null ? null : srcPoint.getCode());
        task.setDstPointCode(dstPoint == null ? null : dstPoint.getCode());
        task.setTaskStatus(BizStatus.OPEN);
        task.setPlanQty(quantity);
        task.setDept(item.getDept());
        task.setAgvTask(agvTask);
        taskRepository.save(task);
        return task;
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public Task reassignTask(Task task, String orderNumber) {
        Inventory dstinventory = inventoryService.findByOrderNumber(orderNumber, task.getSrcStockCode());
        if (dstinventory == null) {
            throw new BadRequestException(orderNumber + "箱号无库存，请更换箱号！");
        }
        double kyQty = dstinventory.getQuantity() - dstinventory.getQueuedQty();
        if (kyQty < task.getPlanQty()) {
            throw new BadRequestException(orderNumber + "箱号库存数量不足，请更换箱号！");
        }
        Inventory srcinventory = inventoryService.findById(task.getInvId());
        srcinventory.setQueuedQty(srcinventory.getQueuedQty() - task.getPlanQty());
        inventoryService.update(srcinventory);
        dstinventory.setQueuedQty(dstinventory.getQueuedQty() + task.getPlanQty());
        inventoryService.update(dstinventory);
        task.setInvId(dstinventory.getId());
        task.setItemKey(dstinventory.getItemKey());
        taskRepository.save(task);
        return task;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void moveInventory(Task task, Stock dstStock, double moveQty) {
        //将占用数量移位，原库存回库内
        Inventory inventory = inventoryService.findById(task.getInvId());
        inventory.setQueuedQty(inventory.getQueuedQty() - moveQty);
        inventory.setQuantity(inventory.getQuantity() - moveQty);
        inventoryService.update(inventory);

        //生成新库存
        Inventory newInventory = inventoryService.createInventory(inventory.getItemKey(), dstStock.getPoint(), dstStock, inventory.getDept(), moveQty);

        //更新Task
        task.setInvId(newInventory.getId());
        task.setMoveQty(moveQty);
        task.setTaskStatus(BizStatus.FINISH);
        task.setDstStock(dstStock);
        task.setDstStockCode(dstStock.getCode());
        taskRepository.save(task);

        PickDetail pickDetail = task.getPickDetail();
        pickDetail.setPickedQty(pickDetail.getPickedQty() + moveQty);
        pickDetailService.update(pickDetail);

        //删除库存
        if (inventory.getQuantity() <= 0) {
            inventoryService.deleteAll(new Long[]{inventory.getId()});
        }

        //容器占用
        stockService.usedStock(dstStock, dstStock.getPoint(), BaseStatus.USED);

        //库存日志
        inventoryLogService.createInventoryLog(BizStatus.MOVE, BizStatus.ADD, task.getPickDetail().getPo(), task.getItemKey(), task.getDstPoint(), dstStock.getPoint(), task.getSrcStock(), dstStock, 0d, moveQty, BizStatus.PICK, task.getId(), newInventory.getId(), "");

    }

    @Override
    public int existsByStock(String pickCode, String stockCode) {
        return taskRepository.existsByStock(pickCode, stockCode);
    }

}
