/*
 *  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.RequestData.ItemDate;
import com.youchain.RequestData.Yclbl;
import com.youchain.RequestData.ZcData;
import com.youchain.basicdata.domain.Item;
import com.youchain.basicdata.domain.Point;
import com.youchain.basicdata.repository.ItemRepository;
import com.youchain.basicdata.repository.PointRepository;
import com.youchain.businessdata.domain.*;
import com.youchain.businessdata.repository.AgvTaskRepository;
import com.youchain.businessdata.repository.PickDetailRepository;
import com.youchain.businessdata.repository.TaskRepository;
import com.youchain.businessdata.service.AgvTaskService;
import com.youchain.config.thread.ThreadPoolExecutorUtil;
import com.youchain.exception.BadRequestException;
import com.youchain.modules.system.service.DeptService;
import com.youchain.utils.*;
import lombok.RequiredArgsConstructor;
import com.youchain.businessdata.repository.PickRepository;
import com.youchain.businessdata.service.PickService;
import com.youchain.businessdata.service.dto.PickDto;
import com.youchain.businessdata.service.dto.PickQueryCriteria;
import com.youchain.businessdata.service.mapstruct.PickMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
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;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * @author huojin
 * @website https://eladmin.vip
 * @description 服务实现
 * @date 2024-06-11
 **/
@Service
@RequiredArgsConstructor
@Slf4j
public class PickServiceImpl implements PickService {

    private final PickRepository pickRepository;
    private final PickDetailRepository pickDetailRepository;
    private final TaskRepository taskRepository;
    private final PointRepository pointRepository;
    private final ItemRepository itemRepository;
    private final AgvTaskRepository agvTaskRepository;
    private final AgvTaskService agvTaskService;
    private final DeptService deptService;
    private final PickMapper pickMapper;
    private final RedisObjectUtils redisObjectUtils;
    private final BatchCreateOrUpdate batchCreateOrUpdate;

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

    @Override
    public List<PickDto> queryAll(PickQueryCriteria criteria) {
        return pickMapper.toDto(pickRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder)));
    }

    @Override
    @Transactional
    public PickDto findById(Long id) {
        Pick pick = pickRepository.findById(id).orElseGet(Pick::new);
        ValidationUtil.isNull(pick.getId(), "Pick", "id", id);
        return pickMapper.toDto(pick);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PickDto create(Pick resources) {
        return pickMapper.toDto(pickRepository.save(resources));
    }

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

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

    @Override
    public void download(List<PickDto> all, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (PickDto pick : all) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("创建人", pick.getCreateBy());
            map.put("修改人", pick.getUpdateBy());
            map.put("创建时间", pick.getCreateTime());
            map.put("修改时间", pick.getUpdateTime());
            map.put("出库单头", pick.getCode());
            map.put("序号", pick.getLineNo());
            map.put("状态", pick.getStatus());
            list.add(map);
        }
        FileUtil.downloadExcel(list, response);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void bigItemCall(String gdNo, String completeCode, String station, String pointCode) {
        Pick pick = pickRepository.findByPick(gdNo, completeCode, station);
        if (pick == null) {
            throw new IllegalArgumentException(gdNo + "工单未备料，叫料失败!");
        }

        List<Point> pointList = pointRepository.findByCode(pointCode, null, null, null, null, null);
        if (pointList.isEmpty()) {
            throw new IllegalArgumentException(pointCode + "点位系统不存在，请维护!");
        }

        if (pick.getIsCall()) {
            throw new IllegalArgumentException(gdNo + "工单已叫料，请勿重复叫料!");
        }
        pick.setIsCall(true);
        pick.setCallPoint(pointList.get(0));
        pickRepository.save(pick);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void itemCall(String gdNo, String itemCode, String station, String pointCode) {
        Pick pick = pickRepository.findByPick(gdNo, itemCode, station);
        if (pick == null) {
            throw new IllegalArgumentException(gdNo + "工单未备料，叫料失败!");
        }

        List<Point> pointList = pointRepository.findByCode(pointCode, null, null, null, null, null);
        if (pointList.isEmpty()) {
            throw new IllegalArgumentException(pointCode + "点位系统不存在，请维护!");
        }

        if (pick.getIsCall()) {
            throw new IllegalArgumentException(gdNo + "工单已叫料，请勿重复叫料!");
        }


        pick.setIsCall(true);
        pick.setCallPoint(pointList.get(0));
        pickRepository.save(pick);
    }

    @Override
    public synchronized void materialBl(Yclbl yclbl) {

        /** 验证参数有效性 */
        validatePick(yclbl);

        /** 验证出库单是否存在 */
        checkIfGdExists(yclbl);

        /** 验证物料是否存在 */
        Map<String, Item> itemMap = checkIfItemExists(yclbl);

        List<Pick> createPicks = new ArrayList<>();
        List<PickDetail> createPickDetails = new ArrayList<>();
        AtomicInteger lineNo = new AtomicInteger(1);

        String orderNo = yclbl.getOrderNo();
        String taskCode = yclbl.getTaskCode();

        /** 遍历成品集合 */
        yclbl.getBlzc().forEach(zcData -> {
            Pick pick = createPick(orderNo, taskCode, lineNo.getAndIncrement(), zcData.getCompleteCode(), zcData.getStation(), BaseStatus.GD_TYPE_CT);
            createPicks.add(pick);
            zcData.getBlzcmx().forEach(itemDate -> {
                Item item = itemMap.get(itemDate.getItemCode());
                PickDetail pickDetail = createPickDetail(pick, item, BaseStatus.GD_TYPE_CT, itemDate.getItemQty(), zcData.getCompleteQty());
                createPickDetails.add(pickDetail);
            });
        });

        /** 遍历单品品集合 */
        yclbl.getBlzcmx().forEach(itemDate -> {
            Pick pick = createPick(orderNo, taskCode, lineNo.getAndIncrement(), itemDate.getItemCode(), itemDate.getStation(), BaseStatus.GD_TYPE_DP);
            createPicks.add(pick);
            Item item = itemMap.get(itemDate.getItemCode());
            PickDetail pickDetail = createPickDetail(pick, item, BaseStatus.GD_TYPE_DP, itemDate.getItemQty(), null);
            createPickDetails.add(pickDetail);
        });

        /** 批量插入 */
        try {
            batchCreateOrUpdate.batchInsert(createPicks);
        } catch (Exception e) {
            throw new BadRequestException("出库单创建失败!"+e);
        }

        try {
            batchCreateOrUpdate.batchInsert(createPickDetails);
        } catch (Exception e) {
            throw new BadRequestException("出库单明细创建失败!"+e);
        }


    }


    /**
     * 验证参数有效性
     *
     * @param yclbl -> 备料参数
     */
    private void validatePick(Yclbl yclbl) {
        if (StringUtils.isEmpty(yclbl.getOrderNo())) {
            throw new IllegalArgumentException("工单号必填!");
        }
        if (StringUtils.isEmpty(yclbl.getTaskCode())) {
            throw new IllegalArgumentException("任务号必填!");
        }

        if (yclbl.getBlzc().isEmpty() && yclbl.getBlzcmx().isEmpty()) {
            throw new IllegalArgumentException("成品明细和单品明细至少一项必填!");
        }
    }

    /**
     * 验证出库单是否存在
     *
     * @param yclbl -> 备料参数
     */
    private void checkIfGdExists(Yclbl yclbl) {
        List<Pick> pick = pickRepository.findByPickGdCode(yclbl.getOrderNo());
        if (!pick.isEmpty()) {
            throw new IllegalArgumentException(yclbl.getOrderNo() + "出库单已存在!");
        }
    }

    private Map<String, Item> checkIfItemExists(Yclbl yclbl) {
        // 获取所有成品和单品的itemCode
        Set<String> itemCodes = yclbl.getBlzc().stream().flatMap(zcData -> zcData.getBlzcmx().stream()).map(ItemDate::getItemCode).collect(Collectors.toSet());

        itemCodes.addAll(yclbl.getBlzcmx().stream().map(ItemDate::getItemCode).collect(Collectors.toSet()));

        // 查找数据库中存在的items
        List<Item> items = itemRepository.findByCodes(itemCodes);
        Set<String> newItemCodes = items.stream().map(Item::getCode).collect(Collectors.toSet());

        // 取itemCodes和newItemCodes的差集，说明有不存在的物料
        Set<String> diffItemCodes = itemCodes.stream().filter(code -> !newItemCodes.contains(code)).collect(Collectors.toSet());

        if (!diffItemCodes.isEmpty()) {
            throw new IllegalArgumentException("WMS不存在的物料集合,请维护：" + diffItemCodes);
        }

        // 构建itemCode到Item的映射
        return items.stream().collect(Collectors.toMap(Item::getCode, item -> item));
    }

    /**
     * 取两个集合的不同的元素
     */
    private Set<String> diffItemCodes(Set set1, Set set2) {
        Set<String> difference = new HashSet<>(set1);
        difference.addAll(set2);
        Set<String> tmp = new HashSet<>(set1);
        tmp.retainAll(set2);
        difference.removeAll(tmp);
        return difference;
    }

    /**
     * 验证单品是否存在
     *
     * @param itemCode -> 单品编码
     * @return
     */
    private Item validateItem(String itemCode) {
        Item item = redisObjectUtils.getObjectFromCache(itemCode, () -> itemRepository.findByItemCode(itemCode), itemCode + "单品物料不存在，请维护!");
        return item;
    }

    public PickDetail createPickDetail(Pick pick, Item item, String orderType, Double itemQty, Double bigItemQty) {
        AtomicInteger lineNo = new AtomicInteger(1);
        PickDetail pickDetail = new PickDetail();
        pickDetail.setPick(pick);
        pickDetail.setItem(item);
        pickDetail.setLineNo(lineNo.getAndIncrement());
        pickDetail.setStatus(BizStatus.OPEN);
        if (BaseStatus.GD_TYPE_CT.equals(orderType)) {
            pickDetail.setOrderQty(itemQty * bigItemQty);
        } else {
            pickDetail.setOrderQty(itemQty);
        }
        pickDetail.setDept(pick.getDept());
        return pickDetail;
    }

    public Pick createPick(String gdCode, String taskCode, int lineNo, String cpCodeOrpCode, String station,String orderType) {
        Pick pick = new Pick();
        pick.setTaskCode(taskCode);
        pick.setLineNo(lineNo);
        pick.setCode(gdCode + "_" + lineNo);
        pick.setGdCode(gdCode);
        pick.setStation(station);
        pick.setCpCodeOrDpCode(cpCodeOrpCode);
        pick.setStatus(BizStatus.OPEN);
        pick.setIsCall(false);
        pick.setDept(deptService.getDefaultDept());
        pick.setOrderType(orderType);
        return pick;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void materialPick(Pick pick) {
        AgvTask agvTask = null;
        List<Task> taskList = taskRepository.findByNotPickTask(pick.getId());
        for (Task task : taskList) {
            /*生成搬运任务*/
            List<AgvTask> agvTaskList = agvTaskRepository.findRepeat(task.getSrcStockCode());
            if (agvTaskList.size() > 0) {
                agvTask = agvTaskList.get(0);
            } else {
                agvTask = new AgvTask(BizStatus.PICK, task.getSrcStockCode(), task.getSrcPointCode(), task.getDstPointCode(), BizStatus.OPEN, "FORKLIFT_MOVE");
                agvTaskRepository.save(agvTask);
                agvTaskService.sendAgvTaskCCImpl(agvTask);


            }
            task.setAgvTask(agvTask);
            task.setTaskStatus(agvTask.getStatus());
            taskRepository.save(task);

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

    @Override
    public List<PickDetail> queryPickDetail(Long pickId) {
        return pickRepository.queryPickDetail(pickId);
    }


}
