package com.jjt.inventory.service.impl;

import com.jjt.common.utils.DateUtils;
import com.jjt.inventory.domain.TwinCalcDayBp;
import com.jjt.inventory.domain.TwinCalcDayPt;
import com.jjt.inventory.domain.TwinCalcHourBp;
import com.jjt.inventory.domain.TwinCalcHourPt;
import com.jjt.inventory.mapper.TwinCalcHourBpMapper;
import com.jjt.inventory.mapper.TwinCalcHourPtMapper;
import com.jjt.inventory.service.*;
import com.jjt.inventory.vo.StockVO;
import com.jjt.system.service.ISysConfigService;
import com.jjt.utils.MssqlService;
import com.jjt.utils.Tools;
import javafx.util.Pair;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.ToIntFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * * 库存管理接口Service业务层处理
 *
 * @author wukai
 * @date 2025-01-16
 */
@Service
public class InventoryServiceImpl implements IInventoryService {
    @Resource
    private MssqlService mssqlService;
    @Resource
    private SqlSessionFactory factory;
    @Resource
    private ITwinCalcHourBpService hourBpService;
    @Resource
    private ITwinCalcHourPtService hourPtService;
    @Resource
    private ISysConfigService configService;
    @Resource
    private ITwinCalcDayPtService dayPtService;
    @Resource
    private ITwinCalcDayBpService dayBpService;

    /**
     * 按小时库存数据
     */
    @Override
    public void hour() {
        LocalDateTime ldt = Tools.currWholeTime();
        LocalDateTime start = ldt.minusHours(1);
        LocalDateTime end = ldt.minusSeconds(1);
        calc(start, end);
    }

    /**
     * 指定时间库存数据
     *
     * @param start 开始
     * @param end   结束
     */
    @Override
    public void calc(LocalDateTime start, LocalDateTime end) {
        String sql = "SELECT WAREHOUSE_ID,QTY,SKU,SKU_DESCRC,UOM,LOT07,LOT08 FROM V_WMS_STOCK";
        List<Map<String, Object>> list = mssqlService.executeQuery(sql);
        List<StockVO> ptList = new ArrayList<>();
        List<StockVO> bpList = new ArrayList<>();
        list.forEach(map -> {
            StockVO vo = new StockVO();
            vo.setUom(map.get("UOM") != null ? (String) map.get("UOM") : "");
            vo.setQty(map.get("QTY") != null ? map.get("QTY").toString() : "0");
            vo.setSku(map.get("SKU") != null ? (String) map.get("SKU") : "");
            vo.setSkuDesc(map.get("SKU_DESCRC") != null ? (String) map.get("SKU_DESCRC") : "");
            vo.setType(map.get("WAREHOUSE_ID") != null ? (String) map.get("WAREHOUSE_ID") : "");
            String PANTOU_WAREHOUSE = "WH03-盘头仓库";
            String BAIPI_WAREHOUSE = "WH04-白坯仓库";

            if (PANTOU_WAREHOUSE.equals(vo.getType())) {
                ptList.add(vo);
            }
            if (BAIPI_WAREHOUSE.equals(vo.getType()) && "KG".equals(vo.getUom())) {
                String lot07 = map.get("LOT07") != null ? (String) map.get("LOT07") : "0";
                String lot08 = map.get("LOT08") != null ? (String) map.get("LOT08") : "0";
                vo.setLot07(new BigDecimal(lot07).intValue());
                vo.setLot08(new BigDecimal(lot08).intValue());
                bpList.add(vo);
            }
        });
        String sqlSuffix = " AND [时间] BETWEEN CONVERT(DATETIME, '" + start.toString().replace("T", " ") + "') AND CONVERT(DATETIME, '" + end.toString().replace("T", " ") + "')";
        ptProcess(ptList, sqlSuffix, start);
        bpProcess(bpList, sqlSuffix, start);
    }

    /**
     * 盘头库数据处理
     *
     * @param ptList 盘头库数据
     */

    private void ptProcess(List<StockVO> ptList, String sqlSuffix, LocalDateTime start) {
        String inSql = "SELECT [时间] TIME,[仓库编码] CODE,[仓库名称] NAME,[物料编码] SKU,[物料描述] SKU_DESCRC,[当日入库量] NUM,[单托_支数_米数] LENGTH,[单重_KG] WEIGHT FROM V_MES_SHANGJIA t" +
                " WHERE [仓库编码]='WH03'" + sqlSuffix;
//        AND [物料类型]!='容器类'
        List<Map<String, Object>> inList = mssqlService.executeQuery(inSql);
        String outSql = "SELECT [时间] TIME,[仓库编码] CODE,[仓库名称] NAME,[物料编码] SKU,[物料描述] SKU_DESCRC,[当日出库量] NUM,[单托_支数_米数] LENGTH,[单重_KG] WEIGHT,[产线_目标库位名称] TARGET FROM V_MES_FAYUN t" +
                " WHERE [仓库编码]='WH03'" + sqlSuffix;
        List<Map<String, Object>> outList = mssqlService.executeQuery(outSql);
        Map<String, TwinCalcHourPt> stats = new HashMap<>(16);

        // 合并处理入库和出库数据
        List<Map<String, Object>> combinedList = new ArrayList<>();
        combinedList.addAll(inList);
        combinedList.addAll(outList);

        combinedList.forEach(map -> {
            String skuDesc = (String) map.get("SKU_DESCRC");
            TwinCalcHourPt pt = stats.computeIfAbsent(skuDesc, k -> new TwinCalcHourPt(true));
            pt.setName(skuDesc);
            pt.setSku((String) map.get("SKU"));
            if (inList.contains(map)) {
                pt.addInNum(1);
            } else {
                pt.addOutNum(1);
            }
        });

        // 处理库存数据
        ptList.forEach(vo -> {
            TwinCalcHourPt pt = stats.computeIfAbsent(vo.getSkuDesc(), k -> new TwinCalcHourPt(true));
            pt.setName(vo.getSkuDesc());
            pt.setSku(vo.getSku());
            pt.addNum(1);
        });

        List<TwinCalcHourPt> resultList = new ArrayList<>(stats.values());
        resultList.forEach(pt -> {
            pt.setDataDate(DateUtils.toDate(start.toLocalDate()));
            pt.setHour(start.getHour());
            pt.setCreateTime(new Date());
        });

        hourPtService.delete(start.toLocalDate(), start.getHour());
        try (SqlSession sqlSession = factory.openSession(ExecutorType.BATCH, false)) {
            if (resultList.size() > 0) {
                TwinCalcHourPtMapper mapper = sqlSession.getMapper(TwinCalcHourPtMapper.class);
                resultList.forEach(mapper::insertTwinCalcHourPt);
                sqlSession.commit();
            }
        }
    }

    /**
     * 白坯仓库数据处理
     *
     * @param bpList 白坯仓库数据
     */

    private void bpProcess(List<StockVO> bpList, String sqlSuffix, LocalDateTime start) {
        String inSql = "SELECT [时间] TIME,[仓库编码] CODE,[仓库名称] NAME,[物料编码] SKU,[物料描述] SKU_DESCRC,[当日入库量] NUM,[单托_支数_米数] LENGTH,[单重_KG] WEIGHT FROM V_MES_SHANGJIA t" +
                " WHERE [仓库编码]='WH04'" + sqlSuffix;
        List<Map<String, Object>> inList = mssqlService.executeQuery(inSql);
        String outSql = "SELECT [时间] TIME,[仓库编码] CODE,[仓库名称] NAME,[物料编码] SKU,[物料描述] SKU_DESCRC,[当日出库量] NUM,[单托_支数_米数] LENGTH,[单重_KG] WEIGHT,[产线_目标库位名称] TARGET FROM V_MES_FAYUN t" +
                " WHERE [仓库编码]='WH04'" + sqlSuffix;
        List<Map<String, Object>> outList = mssqlService.executeQuery(outSql);

        Map<String, TwinCalcHourBp> stats = new HashMap<>(16);

        // 处理入库和出库数据的通用方法
        BiConsumer<Map<String, Object>, Boolean> processRecord = (map, isIn) -> {
            String skuDesc = (String) map.get("SKU_DESCRC");
            TwinCalcHourBp bp = stats.computeIfAbsent(skuDesc, k -> new TwinCalcHourBp(true));
            bp.setName(skuDesc);
            bp.setSku((String) map.get("SKU"));

            int length = map.get("LENGTH") != null ? new BigDecimal(map.get("LENGTH").toString()).intValue() : 0;
            int weight = map.get("WEIGHT") != null ? new BigDecimal(map.get("WEIGHT").toString()).intValue() : 0;

            if (isIn) {
                bp.addRollIn(1);
                bp.addMeterIn(length);
                bp.addWeightIn(weight);
            } else {
                bp.addRollOut(1);
                bp.addMeterOut(length);
                bp.addWeightOut(weight);
            }
        };

        // 处理入库和出库数据
        inList.forEach(map -> processRecord.accept(map, true));
        outList.forEach(map -> processRecord.accept(map, false));

        // 处理库存数据
        bpList.forEach(vo -> {
            TwinCalcHourBp bp = stats.computeIfAbsent(vo.getSkuDesc(), k -> new TwinCalcHourBp(true));
            bp.setName(vo.getSkuDesc());
            bp.setSku(vo.getSku());

            bp.addRollNum(1);
            bp.addMeterNum(vo.getLot07());
            bp.addWeightNum(vo.getLot08());
        });
        List<TwinCalcHourBp> resultList = new ArrayList<>(stats.values());
        resultList.forEach(bp -> {
            bp.setDataDate(DateUtils.toDate(start.toLocalDate()));
            bp.setHour(start.getHour());
            bp.setCreateTime(new Date());
            // 定义正则表达式模式
            String patternString = "([A-Za-z]+-[A-Za-z])(\\d+)\\(([0-9.]+\\*[0-9.]+)\\)";
            Pattern pattern = Pattern.compile(patternString);

            Matcher matcher = pattern.matcher(bp.getName().replace(" ", ""));
            if (matcher.find()) {
                // 提取规格部分，即第三组匹配的表达式
                String specification = matcher.group(3);
                String[] tmp = specification.split("\\*");
                if (tmp.length > 1) {
                    bp.setMick(Integer.parseInt(tmp[1]));
                }
            }
        });

        hourBpService.delete(start.toLocalDate(), start.getHour());
        try (SqlSession sqlSession = factory.openSession(ExecutorType.BATCH, false)) {
            if (resultList.size() > 0) {
                TwinCalcHourBpMapper mapper = sqlSession.getMapper(TwinCalcHourBpMapper.class);
                resultList.forEach(mapper::insertTwinCalcHourBp);
                sqlSession.commit();
            }
        }

    }

    /**
     * 按天库存数据
     *
     * @param localDate 当前时间
     */
    @Override
    public void day(LocalDate localDate) {
        ptDay(localDate);
        bpDay(localDate);

    }

    private void bpDay(LocalDate date) {
        //先删除当前日期数据
        dayBpService.delete4date(date.toString());
        TwinCalcHourBp search = new TwinCalcHourBp();
        Map<String, Object> params = new HashMap<>(16);
        LocalDateTime st = LocalDateTime.of(date, LocalTime.MIN).plusHours(7);
        LocalDateTime ed = st.plusHours(23);
        params.put("sTime", DateUtils.toDate(st));
        params.put("eTime", DateUtils.toDate(ed));
        search.setParams(params);
        List<TwinCalcHourBp> hourBps = hourBpService.selectTwinCalcHourBpList(search);
        Map<Integer, List<TwinCalcHourBp>> hourMap = hourBps.stream().collect(Collectors.groupingBy(TwinCalcHourBp::getHour));
        // 统计方法
        BiFunction<Integer, ToIntFunction<TwinCalcHourBp>, Pair<Integer, Integer>> stats;
        stats = (defaultHour, mapper) -> {
            Optional<Map.Entry<Integer, List<TwinCalcHourBp>>> maxEntry = hourMap.entrySet().stream()
                    .max(Comparator.comparingInt(entry -> entry.getValue().stream().mapToInt(mapper).sum()));
            Optional<Map.Entry<Integer, List<TwinCalcHourBp>>> minEntry = hourMap.entrySet().stream()
                    .min(Comparator.comparingInt(entry -> entry.getValue().stream().mapToInt(mapper).sum()));
            int maxHour = maxEntry.map(Map.Entry::getKey).orElse(defaultHour);
            int minHour = minEntry.map(Map.Entry::getKey).orElse(defaultHour);

            return new Pair<>(maxHour, minHour);
        };

        // 卷数统计
        Pair<Integer, Integer> rollStats = stats.apply(-1, TwinCalcHourBp::getRollNum);
        int rollMaxHour = rollStats.getKey();
        int rollMinHour = rollStats.getValue();
        int rollMax = hourMap.getOrDefault(rollMaxHour, Collections.emptyList()).stream().mapToInt(TwinCalcHourBp::getRollNum).sum();
        int rollMin = hourMap.getOrDefault(rollMinHour, Collections.emptyList()).stream().mapToInt(TwinCalcHourBp::getRollNum).sum();
        BigDecimal rollAvg = BigDecimal.valueOf(hourBps.stream().mapToInt(TwinCalcHourBp::getRollNum).sum())
                .divide(BigDecimal.valueOf(hourMap.size()), 2, RoundingMode.HALF_UP);
        // 米数统计
        Pair<Integer, Integer> meterStats = stats.apply(-1, TwinCalcHourBp::getMeterNum);
        int meterMaxHour = meterStats.getKey();
        int meterMinHour = meterStats.getValue();
        int meterMax = hourMap.getOrDefault(meterMaxHour, Collections.emptyList()).stream().mapToInt(TwinCalcHourBp::getMeterNum).sum();
        int meterMin = hourMap.getOrDefault(meterMinHour, Collections.emptyList()).stream().mapToInt(TwinCalcHourBp::getMeterNum).sum();
        BigDecimal meterAvg = BigDecimal.valueOf(hourBps.stream().mapToInt(TwinCalcHourBp::getMeterNum).sum())
                .divide(BigDecimal.valueOf(hourMap.size()), 2, RoundingMode.HALF_UP);
        // 重量统计
        Pair<Integer, Integer> weightStats = stats.apply(-1, TwinCalcHourBp::getWeightNum);
        int weightMaxHour = weightStats.getKey();
        int weightMinHour = weightStats.getValue();
        int weightMax = hourMap.getOrDefault(weightMaxHour, Collections.emptyList()).stream().mapToInt(TwinCalcHourBp::getWeightNum).sum();
        int weightMin = hourMap.getOrDefault(weightMinHour, Collections.emptyList()).stream().mapToInt(TwinCalcHourBp::getWeightNum).sum();
        BigDecimal weightAvg = BigDecimal.valueOf(hourBps.stream().mapToInt(TwinCalcHourBp::getWeightNum).sum())
                .divide(BigDecimal.valueOf(hourMap.size()), 2, RoundingMode.HALF_UP);

        TwinCalcDayBp day = new TwinCalcDayBp();
        day.setRollMax(rollMax);
        day.setRollMin(rollMin);
        day.setRollAvg(rollAvg);
        day.setMeterMax(meterMax);
        day.setMeterMin(meterMin);
        day.setMeterAvg(meterAvg);
        day.setWeightMax(weightMax);
        day.setWeightMin(weightMin);
        day.setWeightAvg(weightAvg);
        day.setRollData(rollMaxHour + "," + rollMinHour);
        day.setMeterData(meterMaxHour + "," + meterMinHour);
        day.setWeightData(weightMaxHour + "," + weightMinHour);

        day.setTime(DateUtils.toDate(date));
        dayBpService.insertTwinCalcDayBp(day);
    }


    private void ptDay(LocalDate date) {
        Integer maxLimit = Integer.parseInt(configService.selectConfigByKey("inventory.pt.max"));
        //先删除当前日期数据
        dayPtService.delete4date(date.toString());
        TwinCalcHourPt search = new TwinCalcHourPt();
        Map<String, Object> params = new HashMap<>(16);
        LocalDateTime st = LocalDateTime.of(date, LocalTime.MIN).plusHours(7);
        LocalDateTime ed = st.plusHours(23);
        params.put("sTime", DateUtils.toDate(st));
        params.put("eTime", DateUtils.toDate(ed));
        search.setParams(params);
        List<TwinCalcHourPt> hourPts = hourPtService.selectTwinCalcHourPtList(search);
        Map<Integer, List<TwinCalcHourPt>> hourMap = hourPts.stream().collect(Collectors.groupingBy(TwinCalcHourPt::getHour));
        BigDecimal avg = BigDecimal.valueOf(hourPts.stream().mapToInt(TwinCalcHourPt::getNum).average().orElse(0)).setScale(2, RoundingMode.HALF_UP);

        //找到最大值所在的小时及其小时值
        Optional<Map.Entry<Integer, List<TwinCalcHourPt>>> maxEntry = hourMap.entrySet().stream()
                .max(Comparator.comparingInt(entry -> entry.getValue().stream().mapToInt(TwinCalcHourPt::getNum).sum()));
        int maxHour = maxEntry.map(Map.Entry::getKey).orElse(-1);
        List<TwinCalcHourPt> tempList = maxEntry.map(Map.Entry::getValue).orElse(new ArrayList<>());
        int max = tempList.stream().mapToInt(TwinCalcHourPt::getNum).sum();
        TwinCalcDayPt day = new TwinCalcDayPt();
        day.setMaxNum(max);
        BigDecimal rate = BigDecimal.valueOf(max).divide(BigDecimal.valueOf(maxLimit), 4, RoundingMode.HALF_UP);
        day.setRate(rate);
        day.setAvgNum(avg);
        List<Map<String, Object>> dataList = new ArrayList<>();
        tempList.stream().filter(item -> item.getNum() > 0).forEach(item -> {
            BigDecimal ratio = BigDecimal.valueOf(item.getNum()).divide(BigDecimal.valueOf(max), 4, RoundingMode.HALF_UP);
            Map<String, Object> map = new HashMap<>(16);
            map.put("name", item.getName());
            map.put("num", item.getNum());
            map.put("ratio", ratio);
            dataList.add(map);

        });
        day.setData(maxHour + "");
        day.setTime(DateUtils.toDate(date));
        dayPtService.insertTwinCalcDayPt(day);
    }
}
