/*
 *  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.basicdata.service.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.youchain.basicdata.domain.Item;
import com.youchain.basicdata.domain.Point;
import com.youchain.basicdata.domain.Stock;
import com.youchain.basicdata.service.ItemService;
import com.youchain.basicdata.service.PointService;
import com.youchain.businessdata.domain.*;
import com.youchain.businessdata.service.*;
import com.youchain.utils.*;
import lombok.RequiredArgsConstructor;
import com.youchain.basicdata.repository.StockRepository;
import com.youchain.basicdata.service.StockService;
import com.youchain.basicdata.service.dto.StockDto;
import com.youchain.basicdata.service.dto.StockSmallDto;
import com.youchain.basicdata.service.dto.StockQueryCriteria;
import com.youchain.basicdata.service.mapstruct.StockMapper;
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.sql.Timestamp;
import java.util.*;
import java.io.IOException;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.servlet.http.HttpServletResponse;

import static org.springframework.http.HttpStatus.BAD_REQUEST;

/**
 * @author liuxue
 * @website https://eladmin.vip
 * @description 服务实现
 * @date 2023-07-28
 **/
@Service
@RequiredArgsConstructor
@Slf4j
public class StockServiceImpl implements StockService {

    private final StockRepository stockRepository;
    private final StockMapper stockMapper;
    private final EntityManager entityMapper;
    private final AgvTaskService agvTaskService;
    private final ItemService itemService;
    private final InventoryService inventoryService;
    private final AsnDetailService asnDetailService;
    private final PickDetailService pickDetailService;
    private final ItemKeyService itemKeyService;
    private final PointService pointService;
    private final TaskService taskService;

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

    @Override
    public List<StockDto> queryAll(StockQueryCriteria criteria) {
        return stockMapper.toDto(stockRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder)));
    }

    @Override
    public List<StockSmallDto> queryKyStockList(String code) {
        List<Map<String, String>> maps = getKyStockList(code);
        String irsStr = JSON.toJSONString(maps);
        List<StockSmallDto> evaUserResps = JSON.parseArray(irsStr, StockSmallDto.class);
        return evaUserResps;
    }

    @Override
    @Transactional
    public StockDto findById(Long id) {
        Stock stock = stockRepository.findById(id).orElseGet(Stock::new);
        ValidationUtil.isNull(stock.getId(), "Stock", "id", id);
        return stockMapper.toDto(stock);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public StockDto create(Stock resources) {
        return stockMapper.toDto(stockRepository.save(resources));
    }

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

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

    @Override
    public void download(List<StockDto> all, HttpServletResponse response) throws IOException {
        List<Map<String, Object>> list = new ArrayList<>();
        for (StockDto stock : all) {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("容器代码", stock.getCode());
            map.put("上级容器ID", stock.getPid());
            map.put("仓库ID", stock.getDept().getName());
            map.put("地标ID", stock.getPoint().getName());
            map.put("子容器数量", stock.getSubCount());
            map.put("容器名称", stock.getName());
            map.put("容器类型", stock.getStockType());
            map.put("容器使用重量", stock.getUseWeight());
            map.put("容器使用体积", stock.getUseSize());
            map.put("容器重量占用率", stock.getUsageWeight());
            map.put("容器体积占用率", stock.getUsageSize());
            map.put("状态", stock.getEnabled());
            map.put("创建人", stock.getCreateBy());
            map.put("修改人", stock.getUpdateBy());
            map.put("创建时间", stock.getCreateTime());
            map.put("修改时间", stock.getUpdateTime());
            map.put("顶级容器ID", stock.getTopId());
            list.add(map);
        }

        FileUtil.downloadExcel(list, response);
    }

    @Override
    public Stock toEntity(StockDto stockDto) {
        return stockMapper.toEntity(stockDto);
    }

    @Override
    public Stock findByCode(String code, String status) {
        return stockRepository.findByCode(code, status);
    }


    @Override
    public Stock findByKStock(String areaName) {
        String hql = "from Stock t where t.point.area.name = :areaName " +
                "and t.point.id > 0 " +
                "and t.point.status = 'USED' " +
                "and t.status = 'FREE' " +
                "and t.point.type = 'STORAGE'";
        Query query = entityMapper.createQuery(hql);
        query.setParameter("areaName", areaName);
        Optional<Stock> ts = query.getResultStream().findFirst();
        return ts.isPresent() ? ts.get() : null;
    }

    @Override
    public String getItemCode(String stockCode) {
        String hql = "FROM Inventory inv WHERE inv.stock.code = :stockCode AND inv.quantity - inv.queuedQty > 0";
        Query query = entityMapper.createQuery(hql);
        query.setParameter("stockCode", stockCode);
        List<Inventory> inventoryList = query.getResultList();
        if (!inventoryList.isEmpty()) {
            return inventoryList.get(0).getItemKey().getItem().getCode();
        }
        return "";
    }

    @Override
    public List<Map<String, String>> getKyStockList(String code) {
        return stockRepository.getKyStockList(code);
    }

    @Override
    public synchronized String containerInJson(String containerCode, String position) {
        JSONObject jsonObject = new JSONObject(new LinkedHashMap<>());
        jsonObject.put("requestId", String.valueOf(new Date().getTime()));//请求 id
        jsonObject.put("containerType", "");//容器类型
        jsonObject.put("containerCode", containerCode);//容器编号
        jsonObject.put("position", position);//容器当前位置
        jsonObject.put("enterOrientation", "");//角度
        return jsonObject.toString();
    }

    @Override
    public synchronized String containerOutJson(String containerCode) {
        JSONObject jsonObject = new JSONObject(new LinkedHashMap<>());
        jsonObject.put("requestId", String.valueOf(new Date().getTime()));//请求 id
        jsonObject.put("containerType", "");//容器类型
        jsonObject.put("containerCode", containerCode);//容器编号
        jsonObject.put("position", "");//容器当前位置
        return jsonObject.toJSONString();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void containerIn(String containerCode, String position, String itemCode) {
        Stock stock = validateStock(containerCode);//验证容器
        Point point = validateSrcPoint(position);//验证点位
        if (!isRestrictedArea(point.getArea().getName())) {
            throw new RuntimeException(point.getCode() + "点位不能入场!");
        }

        switch (point.getArea().getName()) {
            case AreaNameDic.XJQ:
            case AreaNameDic.DJQ:
                if (StringUtils.isEmpty(itemCode)) {
                    throw new RuntimeException(point.getCode() + "点位入场，需扫描物料!");
                }
                break;
        }

        validateAgvResponse(UrlApi.containerIn(), containerInJson(containerCode, position));//验证AGV返回信息


        if (!StringUtils.isEmpty(itemCode)) {
            //入满车
            handleFullContainer(stock, point, itemCode);
        } else {
            //入空车
            handleEmptyContainer(stock, point);
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void containerOut(String containerCode) {
        Stock stock = validateStock(containerCode);//验证容器
        validateAgvResponse(UrlApi.containerOut(), containerOutJson(stock.getCode()));//验证AGV返回信息
        clearInventory(stock);//清空库存
        Point point = stock.getPoint();
        if (point != null) {
            point.setStatus(BaseStatus.FREE);
            pointService.update(point);
        }
        stock.setStatus(BaseStatus.FREE);
        stock.setPoint(null);
        update(stock);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void callContainer(String itemCode, String pointCode, int scene) {
        Point endPoint = validateSrcPoint(pointCode);//验证点位
        checkPointStatus(endPoint);//验证点位状态
        switch (scene) {
            case 2: // 叫满车
                handleFullContainerCall(itemCode, endPoint);
                break;
            default:
                throw new RuntimeException("无效的业务场景!");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void bindContainer(String QRCode, String stockCode, String pointCode) {
        Map<String, String> map = parseString(QRCode);//解析二维码
        String propC1 = map.get("Lot");//批次号
        String boxNumber = map.get("S");//箱号
        String propC3 = map.get("P");//工厂
        String propD1 = map.get("D");//生产日期
        double orderQty = Double.parseDouble(getStringCode(map.get("Q")));//数量
        if (asnDetailService.existsByboxNumber(boxNumber)) {
            throw new RuntimeException(boxNumber + "箱号已入库,请勿重复操作!");
        }
        Item item = validateItem(getStringCode(map.get("M")));//验证物料
        Stock stock = validateStock(stockCode);//验证容器
        validateStockStatus(stock);//验证容器状态
        validateStockAndItem(stock, item);//验证容器类型和物料类型是否匹配
        Point srcPoint = validateSrcPoint(pointCode);//验证点位
        String srcPointAreaName = srcPoint.getArea().getName();
        if (!isValidMCArea(srcPointAreaName)) {
            throw new RuntimeException(srcPoint.getCode() + "为" + srcPointAreaName + "不能入库!");
        }
        //容器类型：小件入库则入小件缓存区、大件入库则入大件缓存区
        Map<String, String> stockTypeToAreaMap = new HashMap<>();
        stockTypeToAreaMap.put("小件入库", "小件存储区");
        stockTypeToAreaMap.put("大件入库", "大件存储区");
        if (!stockTypeToAreaMap.containsKey(stock.getStockType())) {
            throw new RuntimeException(stock.getStockType() + "容器类型错误!");
        }
        String areaName = stockTypeToAreaMap.get(stock.getStockType());

        Point endPoint = validateEndPoint(item, areaName);//验证目标点位
        checkPointStatus(srcPoint);//验证源点位状态
        AgvTask agvTask = createAndSendAgvTask(BizStatus.ASN, stock, srcPoint, endPoint);//生成AGV任务
        AsnDetail asnDetail = asnDetailService.createAsnDetail(item, stock, srcPoint, propC1, boxNumber, propC3, Timestamp.valueOf(DateUtil.formatDateTime(DateUtil.parse(propD1))), orderQty, QRCode);
        ItemKey itemKey = itemKeyService.getItemKey(item, asnDetail.getPropC1(), asnDetail.getOrderNumber());//生成Itemkey
        createTask(item, itemKey, asnDetail, stock, srcPoint, endPoint, agvTask);//生成Task任务
        updateStockAndPoints(stock, srcPoint, endPoint);//更新容器和点位状态
    }

    private void validateStockAndItem(Stock stock, Item item) {
        if (item.getGoodType().contains("大件") || item.getGoodType().contains("小件")) {
            if (!item.getGoodType().substring(0, 2).equals(stock.getStockType().substring(0, 2))) {
                throw new RuntimeException("物料类型和容器类型不匹配!");
            }
        }


    }

    private Map<String, String> parseString(String QRCode) {
        //P:工厂;V:客户编号;M:物料编号/PCS;B:BYD批次;Lot:供应商批次号;S:工单编号;PO:采购订单/箱号;Q:数量;D:生产日期;YX:有效天数;
        // 使用分号分隔字符串
        String[] pairs = QRCode.split(";");
        Map<String, String> resultMap = new HashMap<>();
        for (String pair : pairs) {
            // 使用冒号分隔键和值
            String[] keyValue = pair.split(":");
            if (keyValue.length == 2) {
                String key = keyValue[0].trim();
                String value = keyValue[1].trim();
                resultMap.put(key, value);
            }
        }
        return resultMap;
    }

    private String getStringCode(String str) {
        int index = str.indexOf('/');
        return str.substring(0, index);
    }

    private boolean isValidMCArea(String areaName) {
        return AreaNameDic.DXJRKQ.equals(areaName);
    }

    @Override
    public Map<String, Stock> findByCodes(Set stockCodes) {
        String hql = " from Stock where code in (:stockCodes)";
        Query query = entityMapper.createQuery(hql);
        query.setParameter("stockCodes", stockCodes);
        List<Stock> stockList = query.getResultList();
        Map<String, Stock> stockMap = new HashMap<>();
        for (Stock stock : stockList) {
            stockMap.put(stock.getCode(), stock);
        }
        return stockMap;
    }

    private Item validateItem(String itemCode) {
        Item item = itemService.existItem(itemCode);
        if (item == null) {
            throw new RuntimeException("请扫描正确的物料!");
        }
        return item;
    }

    private Stock validateStock(String stockCode) {
        Stock stock = findByCode(stockCode, null);
        if (stock == null) {
            throw new RuntimeException("请扫描正确的容器!");
        }
        return stock;
    }

    private void validateStockStatus(Stock stock) {
        if (BaseStatus.USED.equals(stock.getStatus())) {
            throw new RuntimeException(stock.getCode() + "容器已占用,请更换容器!");
        }
    }

    private Point validateSrcPoint(String pointCode) {
        Point srcPoint = pointService.findByCode(pointCode, null, null, null, null, null);
        if (srcPoint == null) {
            throw new RuntimeException("请扫描正确点位!");
        }
        return srcPoint;
    }

    private Point validateEndPoint(Item item, String areaName) {
        Double itemHeight = null;
        if ("小件存储区".equals(areaName)) {
            itemHeight = item.getExtendD2();//整托高度
        }
        Point endPoint = pointService.findByCode(null, BaseStatus.FREE, BaseStatus.STORAGE, areaName, null, itemHeight);
        if (endPoint == null) {
            throw new RuntimeException(areaName + "没有空闲点位!");
        }
        return endPoint;
    }


    private void checkPointStatus(Point srcPoint) {
        if (BaseStatus.USED.equals(srcPoint.getStatus())) {
            throw new RuntimeException(srcPoint.getCode() + "点位已有任务，请勿重复操作!");
        }
    }

    private AgvTask createAndSendAgvTask(String bizStatus, Stock stock, Point srcPoint, Point endPoint) {
        AgvTask agvTask = new AgvTask(bizStatus, stock.getCode(), srcPoint.getCode(), endPoint.getCode(), BizStatus.OPEN, "RACK_MOVE");
        agvTaskService.create(agvTask);
        agvTaskService.sendAgvTaskImpl(agvTask);
        return agvTask;
    }

    private void sendAgvTaskAndHandleResponse(AgvTask agvTask) {
        agvTaskService.sendAgvTaskImpl(agvTask);
    }

    private Task createTask(Item item, ItemKey itemKey, AsnDetail asnDetail, Stock stock, Point srcPoint, Point endPoint, AgvTask agvTask) {
        Task task = new Task(item, itemKey, asnDetail.getOrderNumber(), BizStatus.ASN, asnDetail, null, null, null, stock, stock, srcPoint, endPoint, stock.getCode(), stock.getCode(), srcPoint.getCode(), endPoint.getCode(), null, BizStatus.OPEN, asnDetail.getOrderQty(), null, null, item.getDept(), agvTask);
        taskService.create(task);
        return task;
    }

    private void updateStockAndPoints(Stock stock, Point srcPoint, Point endPoint) {
        stock.setStatus(BaseStatus.USED);
        stock.setPoint(srcPoint);
        update(stock);

        endPoint.setStatus(BaseStatus.USED);
        pointService.update(endPoint);

        srcPoint.setStatus(BaseStatus.USED);
        pointService.update(srcPoint);
    }

    private void handleFullContainerCall(String itemCode, Point endPoint) {
        Item item = validateItem(itemCode);//验证物料
        String endPointAreaName = endPoint.getArea().getName();
        //只允许叫料区叫满车
        if (!isValidMCJLArea(endPointAreaName)) {
            throw new RuntimeException(endPoint.getCode() + "点位不能叫满车!");
        }
        String areaName = "";
        List<Inventory> inventoryList = inventoryService.queryInventory(item.getId(), areaName);
        if (inventoryList.isEmpty()) {
            throw new RuntimeException(itemCode + "物料无库存,呼叫失败!");
        }
        PickDetail pickDetail = pickDetailService.createPickDetail(item, "");
        try {
            pickDetailService.allocateAll(pickDetail.getId(), endPoint, areaName);
            List<Task> taskList = taskService.getPickNotAllTask(pickDetail.getId());
            AgvTask agvTask = taskList.get(0).getAgvTask();
            sendAgvTaskAndHandleResponse(agvTask);
            endPoint.setStatus(BaseStatus.USED);
            pointService.update(endPoint);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }


    }

    private boolean isValidMCJLArea(String areaName) {
        return AreaNameDic.JLQ.equals(areaName);
    }

    /**
     * 验证AGV返回信息
     *
     * @param jsonObject
     */
    private void validateAgvResponse(String url, String jsonObject) {
        /*String resultJson = HttpPostUtil.sendPostReq(url, jsonObject);
        if (StringUtils.isEmpty(resultJson)) {
            throw new RuntimeException("AGV返回信息:容器入场接口调用失败!");
        }
        JSONObject resultObject = JSON.parseObject(resultJson);
        if (resultObject == null) {
            throw new RuntimeException("AGV返回信息:容器入场接口数据返回为空!");
        }
        String code = resultObject.getString("code");
        String message = resultObject.getString("message");
        if (!"0".equals(code)) {
            throw new RuntimeException("AGV返回信息:容器入场接口调用失败!" + message);
        }*/
    }

    private boolean isRestrictedArea(String areaName) {
        return areaName.equals(AreaNameDic.XJQ) || areaName.equals(AreaNameDic.DJQ) ||
                areaName.equals(AreaNameDic.CPRKQ);
    }

    private void handleFullContainer(Stock stock, Point point, String itemCode) {
        Item item = validateItem(itemCode);//验证物料
        clearInventory(stock);//清空库存
        AsnDetail asnDetail = asnDetailService.createAsnDetail(item, stock);
        ItemKey itemKey = itemKeyService.getItemKey(item, asnDetail.getPropC1(), asnDetail.getOrderNumber());
        Task task = taskService.createTask(item, itemKey, asnDetail, stock, point);

        task.setMoveQty(task.getPlanQty());
        taskService.update(task);

        inventoryService.updateInventory(task, stock);

        point.setStatus(BaseStatus.USED);
        pointService.update(point);
        stock.setStatus(BaseStatus.USED);
        stock.setPoint(point);
        update(stock);
    }

    /**
     * 清空库存
     *
     * @param stock
     */
    private void clearInventory(Stock stock) {
        List<Inventory> inventoryList = inventoryService.queryInventory(stock);
        Long[] ids = inventoryList.stream().map(Inventory::getId).toArray(Long[]::new);
        if (ids.length > 0) {
            inventoryService.deleteAll(ids);
        }
    }

    private void handleEmptyContainer(Stock stock, Point point) {
        point.setStatus(BaseStatus.USED);
        pointService.update(point);
        stock.setStatus(BaseStatus.FREE);
        stock.setPoint(point);
        update(stock);
    }

}
