package com.aps.service.Algorithm;

import com.aps.common.util.FileHelper;
import com.aps.common.util.ProductionDeepCopyUtil;
import com.aps.entity.Algorithm.IDAndChildID.GroupResult;
import com.aps.entity.Algorithm.IDAndChildID.NodeInfo;
import com.aps.entity.Algorithm.OperationDependency;
import com.aps.entity.basic.Entry;
import com.aps.entity.basic.GlobalParam;
import com.aps.entity.basic.Machine;
import com.aps.entity.basic.MachineOption;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 多设备工序拆分服务
 * 当 GlobalParam.IsMultipleMachine = true 时，
 * 将拥有多个可选设备的工序按策略拆分为多个子工序，每道子工序分配一台设备，可并行加工。
 *
 * 数据对齐 ScheduleOperationService.SpiltOperation：
 *   - mainId：同源拆分子工序共享的 UUID
 *   - state=1（拆分/原始）、state=2（新建）
 *   - newCreate：新建标记
 *   - execId：第一个保留原始，其余新生成 UUID
 *
 * 作者：佟礼
 * 时间：2026-05-21
 */
public class OperationSplitService {

    /**
     * 对工序列表进行多设备拆分（预处理入口，结合设备负载）
     *
     * 对齐 SpiltOperation 的处理顺序：
     *   1. 先通过 IdGroupingWithDualSerial.addNode 更新 OperatRel，生成新的 GlobalSerial、GroupSerial、NewParentIds、NewChildIds
     *   2. 再从 OperatRel 的 NodeInfo 生成 Entry，设置 NextOperationId 和 PrevOperationId
     *
     * @param allOperations 原始工序列表
     * @param globalParam   全局参数
     * @param machines      设备列表（用于计算各设备总预期负载）
     * @param operatRel     工序关系列表（可变参数，拆分后会被原地更新）
     * @return 拆分后的工序列表
     */
    public static List<Entry> splitMultiMachineOperations(List<Entry> allOperations,
                                                          GlobalParam globalParam,
                                                          List<Machine> machines,
                                                          List<GroupResult> operatRel) {
        if (allOperations == null || allOperations.isEmpty()) {
            return allOperations;
        }
        if (!globalParam.isIsMultipleMachine()) {
            return allOperations;
        }

        boolean hasMultiMachine = allOperations.stream()
                .anyMatch(op -> op.getMachineOptions() != null && op.getMachineOptions().size() > 1);

        if (!hasMultiMachine) {
            FileHelper.writeLogFile("[工序拆分] 没有多设备工序，跳过拆分");
            return allOperations;
        }

        FileHelper.writeLogFile("[工序拆分] 开始多设备工序拆分（负载感知），原始工序数=" + allOperations.size());

        // 旧 Entry 索引（id → Entry）
        Map<Integer, Entry> oldEntryMap = new LinkedHashMap<>();
        for (Entry op : allOperations) {
            oldEntryMap.put(op.getId(), op);
        }

        Map<Long, Double> machineTotalLoad = calcMachineTotalLoad(allOperations);

        // ===== Phase 1: 先更新 OperatRel（结构权威）=====
        List<GroupResult> currentRel = (operatRel != null && !operatRel.isEmpty())
                ? new ArrayList<>(operatRel) : rebuildOperatRel(allOperations);
        Map<String, SplitOpMeta> splitMetas = new LinkedHashMap<>();

        int totalSplitOps = 0;
        int totalSplitCount = 0;

        Set<Entry> splitOriginals = new LinkedHashSet<>();
        for (Entry op : allOperations) {
            List<MachineOption> options = op.getMachineOptions();
            if (options == null || options.size() <= 1) {
                continue;
            }
            splitOriginals.add(op);
        }

        for (Entry op : splitOriginals) {
            List<MachineOption> options = op.getMachineOptions();
            int groupIdx = op.getGroupId() - 1;
            GroupResult gr = currentRel.get(groupIdx);

            NodeInfo origNode = gr.getNodeInfoList().stream()
                    .filter(n -> n.getOriginalId().equals(op.getExecId()))
                    .findFirst().orElse(null);
            if (origNode == null) {
                continue;
            }

            List<Integer> parentIds = origNode.getNewParentIds() != null
                    ? new ArrayList<>(origNode.getNewParentIds()) : new ArrayList<>();
            List<Integer> childIds = origNode.getNewChildIds() != null
                    ? new ArrayList<>(origNode.getNewChildIds()) : new ArrayList<>();

            String mainId = UUID.randomUUID().toString().replace("-", "");
            double[] qtys = calcQuantityDistribution(op, options, machineTotalLoad);

            SplitOpMeta meta = new SplitOpMeta(mainId, op);
            meta.subExecIds.add(op.getExecId());
            meta.subQtys.add(qtys[0]);
            meta.subMachineOptions.add(options.get(0));

            for (int i = 1; i < options.size(); i++) {
                String newExecId = UUID.randomUUID().toString().replace("-", "");
                currentRel = IdGroupingWithDualSerial.addNode(
                        currentRel, groupIdx, newExecId, parentIds, childIds, op.getExecId());
                meta.subExecIds.add(newExecId);
                meta.subQtys.add(qtys[i]);
                meta.subMachineOptions.add(options.get(i));
            }

            splitMetas.put(op.getExecId(), meta);
            totalSplitOps++;
            totalSplitCount += options.size();
        }

        // 写回 operatRel
        if (operatRel != null) {
            operatRel.clear();
            operatRel.addAll(currentRel);
        }

        FileHelper.writeLogFile(String.format(
                "[工序拆分] OperatRel已更新，添加了%d个新节点，共%d个分组",
                totalSplitCount - splitOriginals.size(), currentRel.size()));

        // ===== Phase 2: 从更新后的 OperatRel 生成 Entry =====
        List<Entry> result = new ArrayList<>();

        for (int gi = 0; gi < currentRel.size(); gi++) {
            GroupResult gr = currentRel.get(gi);
            int groupId = gi + 1;

            for (NodeInfo node : gr.getNodeInfoList()) {
                int globalSerial = node.getGlobalSerial();
                String execId = node.getOriginalId();

                Entry entry = oldEntryMap.get(globalSerial);

                if (entry != null) {
                    entry.setId(globalSerial);
                    entry.setSequence(node.getGroupSerial());
                    entry.setGroupId(groupId);
                    setEntryDepsFromNodeInfo(entry, node);

                    SplitOpMeta meta = splitMetas.get(execId);
                    if (meta != null) {
                        entry.setMainId(meta.mainId);
                        entry.setSplitSourceId(meta.originalEntry.getId());
                        entry.setState(1);
                        entry.setNewCreate(false);
                        entry.setQuantity(meta.subQtys.get(0));
                        MachineOption mo = meta.subMachineOptions.get(0);
                        entry.setSelectMachineID(mo.getMachineId());
                        entry.setMachineOptions(Collections.singletonList(mo));
                    }
                } else {
                    SplitOpMeta meta = findMetaForExecId(splitMetas, execId);
                    if (meta != null) {
                        int idx = meta.subExecIds.indexOf(execId);
                        if (idx > 0) {
                            Entry sub = ProductionDeepCopyUtil.deepCopy(meta.originalEntry, Entry.class);
                            sub.setId(globalSerial);
                            sub.setExecId(execId);
                            sub.setSequence(node.getGroupSerial());
                            sub.setGroupId(groupId);
                            setEntryDepsFromNodeInfo(sub, node);
                            sub.setMainId(meta.mainId);
                            sub.setSplitSourceId(meta.originalEntry.getId());
                            sub.setState(2);
                            sub.setNewCreate(true);
                            sub.setSelectMachineID(meta.subMachineOptions.get(idx).getMachineId());
                            sub.setMachineOptions(
                                    Collections.singletonList(meta.subMachineOptions.get(idx)));
                            sub.setQuantity(meta.subQtys.get(idx));
                            entry = sub;
                            oldEntryMap.put(globalSerial, entry);
                        }
                    }
                }

                if (entry != null) {
                    result.add(entry);
                }
            }
        }

        FileHelper.writeLogFile(String.format(
                "[工序拆分] 拆分了%d道多设备工序 → 产生%d道子工序，最终总工序数=%d",
                totalSplitOps, totalSplitCount, result.size()));

        return result;
    }

    /**
     * 按设备总负载比例拆分单道工序（预处理用）
     * 设备负载越高，分到的数量越少
     */
    public static List<Entry> splitEntryByLoad(Entry original, List<MachineOption> options,
                                               int startId, Map<Long, Double> machineTotalLoad) {
        return doSplit(original, options, startId, machineTotalLoad, null, null);
    }

    /**
     * 按设备即时负载比例拆分单道工序（初始化 GS/LS 用）
     * 设备当前累计负载越高，分到的数量越少
     */
    public static List<Entry> splitEntryByRuntimeLoad(Entry original, List<MachineOption> options,
                                                      int startId, Map<Long, Double> machineRuntimeLoad) {
        return doSplit(original, options, startId, machineRuntimeLoad, null, null);
    }

    /**
     * 随机比例拆分单道工序（随机初始化 / 启发式用）
     */
    public static List<Entry> splitEntryRandom(Entry original, List<MachineOption> options,
                                               int startId, Random rnd) {
        return doSplit(original, options, startId, null, null, rnd);
    }

    /**
     * 随机选择部分设备拆分（概率随机的部分拆分）
     */
    public static List<Entry> splitEntryRandomPartial(Entry original, List<MachineOption> options,
                                                      int startId, Random rnd) {
        List<MachineOption> selected = new ArrayList<>();
        for (MachineOption mo : options) {
            if (rnd.nextBoolean()) {
                selected.add(mo);
            }
        }
        if (selected.size() < 2) {
            selected = options;
        }
        return doSplit(original, selected, startId, null, null, rnd);
    }

    private static List<Entry> doSplit(Entry original, List<MachineOption> options,
                                       int startId, Map<Long, Double> loadMap,
                                       Map<Long, Double> percentMap, Random rnd) {
        List<Entry> subOps = new ArrayList<>();
        int n = options.size();

        String mainId = UUID.randomUUID().toString().replace("-", "");

        double[] weights = new double[n];
        double totalWeight = 0;

        for (int i = 0; i < n; i++) {
            MachineOption mo = options.get(i);
            if (rnd != null) {
                weights[i] = rnd.nextDouble() + 0.5;
            } else if (loadMap != null) {
                double load = loadMap.getOrDefault(mo.getMachineId(), 0.0);
                weights[i] = 1.0 / Math.max(load + mo.getProcessingTime(), 0.001);
            } else {
                weights[i] = 1.0 / Math.max(mo.getProcessingTime(), 0.001);
            }
            totalWeight += weights[i];
        }

        double remainingQty = original.getQuantity();
        int id = startId;

        for (int i = 0; i < n; i++) {
            MachineOption mo = options.get(i);

            Entry sub = ProductionDeepCopyUtil.deepCopy(original, Entry.class);
            sub.setId(id);
            sub.setSplitSourceId(original.getId());
            sub.setMainId(mainId);
            sub.setSelectMachineID(mo.getMachineId());
            sub.setMachineOptions(Collections.singletonList(mo));

            if (i == 0) {
                sub.setState(1);
                sub.setNewCreate(false);
            } else {
                sub.setState(2);
                sub.setNewCreate(true);
                sub.setExecId(UUID.randomUUID().toString().replace("-", ""));
            }

            double proportion = (i == n - 1) ? 1.0 : weights[i] / totalWeight;
            double splitQty = Math.max(1, Math.round(original.getQuantity() * proportion * 100.0) / 100.0);
            if (i == n - 1) {
                splitQty = remainingQty;
            }
            splitQty = Math.min(splitQty, remainingQty);
            remainingQty -= splitQty;
            sub.setQuantity(splitQty);

            fixDependencyReferences(sub.getPrevEntryIds(), original.getId(), id, true);
            fixDependencyReferences(sub.getNextEntryIds(), original.getId(), id, false);

            subOps.add(sub);
            id++;
        }

        return subOps;
    }

    /**
     * 对启发式初始化用的工序列表进行随机拆分（直接修改列表，返回拆分后的新列表）
     * 拆分后每个 group 内重新编号 sequence
     */
    public static List<Entry> splitOpsForHeuristic(List<Entry> ops, Random rnd) {
        Map<Integer, List<Entry>> groupMap = new LinkedHashMap<>();
        for (Entry op : ops) {
            groupMap.computeIfAbsent(op.getGroupId(), k -> new ArrayList<>()).add(op);
        }

        int maxId = ops.stream().mapToInt(Entry::getId).max().orElse(0);
        int nextId = maxId + 1;

        Map<Integer, List<Integer>> splitMapping = new LinkedHashMap<>();
        List<Entry> result = new ArrayList<>();
        for (Map.Entry<Integer, List<Entry>> groupEntry : groupMap.entrySet()) {
            List<Entry> expanded = new ArrayList<>();
            for (Entry op : groupEntry.getValue()) {
                List<MachineOption> options = op.getMachineOptions();
                if (op.getSplitSourceId() == null && options != null && options.size() > 1) {
                    List<Entry> subOps = splitEntryRandomPartial(op, options, nextId, rnd);
                    nextId += subOps.size();
                    splitMapping.put(op.getId(),
                            subOps.stream().map(Entry::getId).collect(Collectors.toList()));
                    expanded.addAll(subOps);
                } else {
                    expanded.add(op);
                }
            }
            for (int i = 0; i < expanded.size(); i++) {
                expanded.get(i).setSequence(i + 1);
            }
            result.addAll(expanded);
        }

        updateAllDependencies(result, splitMapping);
        return result;
    }

    private static Map<Long, Double> calcMachineTotalLoad(List<Entry> allOperations) {
        Map<Long, Double> load = new LinkedHashMap<>();
        for (Entry op : allOperations) {
            if (op.getMachineOptions() != null) {
                for (MachineOption mo : op.getMachineOptions()) {
                    load.put(mo.getMachineId(),
                            load.getOrDefault(mo.getMachineId(), 0.0)
                                    + mo.getProcessingTime() * op.getQuantity());
                }
            }
        }
        return load;
    }

    private static void setEntryDepsFromNodeInfo(Entry entry, NodeInfo node) {
        if (node.getNewParentIds() != null && !node.getNewParentIds().isEmpty()) {
            List<OperationDependency> prevDeps = new ArrayList<>();
            for (int parentId : node.getNewParentIds()) {
                if (parentId > 0) {
                    OperationDependency dep = new OperationDependency();
                    dep.setPrevOperationId(parentId);
                    dep.setNextOperationId(entry.getId());
                    prevDeps.add(dep);
                }
            }
            entry.setPrevEntryIds(prevDeps);
        }

        if (node.getNewChildIds() != null && !node.getNewChildIds().isEmpty()) {
            List<OperationDependency> nextDeps = new ArrayList<>();
            for (int childId : node.getNewChildIds()) {
                if (childId > 0) {
                    OperationDependency dep = new OperationDependency();
                    dep.setPrevOperationId(entry.getId());
                    dep.setNextOperationId(childId);
                    nextDeps.add(dep);
                }
            }
            entry.setNextEntryIds(nextDeps);
        }
    }

    private static double[] calcQuantityDistribution(Entry original, List<MachineOption> options,
                                                     Map<Long, Double> loadMap) {
        int n = options.size();
        double[] weights = new double[n];
        double totalWeight = 0;

        for (int i = 0; i < n; i++) {
            MachineOption mo = options.get(i);
            if (loadMap != null) {
                double load = loadMap.getOrDefault(mo.getMachineId(), 0.0);
                weights[i] = 1.0 / Math.max(load + mo.getProcessingTime(), 0.001);
            } else {
                weights[i] = 1.0 / Math.max(mo.getProcessingTime(), 0.001);
            }
            totalWeight += weights[i];
        }

        double[] quantities = new double[n];
        double remaining = original.getQuantity();

        for (int i = 0; i < n; i++) {
            double proportion = (i == n - 1) ? 1.0 : weights[i] / totalWeight;
            double qty = Math.max(1, Math.round(original.getQuantity() * proportion * 100.0) / 100.0);
            if (i == n - 1) {
                qty = remaining;
            }
            qty = Math.min(qty, remaining);
            remaining -= qty;
            quantities[i] = qty;
        }

        return quantities;
    }

    private static SplitOpMeta findMetaForExecId(Map<String, SplitOpMeta> metas, String execId) {
        for (SplitOpMeta meta : metas.values()) {
            if (meta.subExecIds.contains(execId)) {
                return meta;
            }
        }
        return null;
    }

    private static class SplitOpMeta {
        final String mainId;
        final Entry originalEntry;
        final List<String> subExecIds = new ArrayList<>();
        final List<Double> subQtys = new ArrayList<>();
        final List<MachineOption> subMachineOptions = new ArrayList<>();

        SplitOpMeta(String mainId, Entry originalEntry) {
            this.mainId = mainId;
            this.originalEntry = originalEntry;
        }
    }

    private static void fixDependencyReferences(List<OperationDependency> deps,
                                                int originalId, int newId, boolean isPrev) {
        if (deps == null) return;
        for (OperationDependency dep : deps) {
            if (isPrev) {
                if (dep.getNextOperationId() == originalId) {
                    dep.setNextOperationId(newId);
                }
            } else {
                if (dep.getPrevOperationId() == originalId) {
                    dep.setPrevOperationId(newId);
                }
            }
        }
    }

    private static void updateAllDependencies(List<Entry> allOps,
                                              Map<Integer, List<Integer>> splitMapping) {
        if (splitMapping.isEmpty()) return;

        for (Entry op : allOps) {
            if (op.getPrevEntryIds() != null && !op.getPrevEntryIds().isEmpty()) {
                List<OperationDependency> expanded = new ArrayList<>();
                for (OperationDependency dep : op.getPrevEntryIds()) {
                    List<Integer> splitIds = splitMapping.get(dep.getPrevOperationId());
                    if (splitIds != null) {
                        for (int splitId : splitIds) {
                            OperationDependency newDep = new OperationDependency();
                            newDep.setPrevOperationId(splitId);
                            newDep.setNextOperationId(dep.getNextOperationId());
                            newDep.setDependencyType(dep.getDependencyType());
                            expanded.add(newDep);
                        }
                    } else {
                        expanded.add(dep);
                    }
                }
                op.setPrevEntryIds(expanded);
            }

            if (op.getNextEntryIds() != null && !op.getNextEntryIds().isEmpty()) {
                List<OperationDependency> expanded = new ArrayList<>();
                for (OperationDependency dep : op.getNextEntryIds()) {
                    List<Integer> splitIds = splitMapping.get(dep.getNextOperationId());
                    if (splitIds != null) {
                        for (int splitId : splitIds) {
                            OperationDependency newDep = new OperationDependency();
                            newDep.setPrevOperationId(dep.getPrevOperationId());
                            newDep.setNextOperationId(splitId);
                            newDep.setDependencyType(dep.getDependencyType());
                            expanded.add(newDep);
                        }
                    } else {
                        expanded.add(dep);
                    }
                }
                op.setNextEntryIds(expanded);
            }
        }

        FileHelper.writeLogFile(String.format(
                "[工序拆分] 更新了%d个拆分的依赖引用", splitMapping.size()));
    }

    /**
     * 从拆分后的 Entry 列表重建 OperatRel (GroupResult 列表)
     * 对齐 IdGroupingWithDualSerial 的输出格式：
     *   originalId = execId, globalSerial = id, groupSerial = sequence
     *   newParentIds = 从 prevEntryIds 提取的 prevOperationId
     *   newChildIds = 从 nextEntryIds 提取的 nextOperationId
     */
    public static List<GroupResult> rebuildOperatRel(List<Entry> allOperations) {
        Map<Integer, List<Entry>> groupMap = new LinkedHashMap<>();
        for (Entry op : allOperations) {
            groupMap.computeIfAbsent(op.getGroupId(), k -> new ArrayList<>()).add(op);
        }

        List<GroupResult> results = new ArrayList<>();
        for (Map.Entry<Integer, List<Entry>> groupEntry : groupMap.entrySet()) {
            List<Entry> groupOps = groupEntry.getValue();
            groupOps.sort(Comparator.comparingInt(Entry::getSequence));

            List<NodeInfo> nodeInfoList = new ArrayList<>();
            Map<String, Integer> originalToGlobalSerial = new HashMap<>();

            for (Entry op : groupOps) {
                List<Integer> parentIds = new ArrayList<>();
                if (op.getPrevEntryIds() != null) {
                    for (OperationDependency dep : op.getPrevEntryIds()) {
                        if (dep.getPrevOperationId() > 0) {
                            parentIds.add(dep.getPrevOperationId());
                        }
                    }
                }

                List<Integer> childIds = new ArrayList<>();
                if (op.getNextEntryIds() != null) {
                    for (OperationDependency dep : op.getNextEntryIds()) {
                        if (dep.getNextOperationId() > 0) {
                            childIds.add(dep.getNextOperationId());
                        }
                    }
                }

                String execId = op.getExecId() != null ? op.getExecId() : "";
                int globalSerial = op.getId();
                int groupSerial = op.getSequence();

                originalToGlobalSerial.put(execId, globalSerial);
                nodeInfoList.add(new NodeInfo(execId, globalSerial, groupSerial, parentIds, childIds));
            }

            results.add(new GroupResult(nodeInfoList, originalToGlobalSerial));
        }

        return results;
    }
}