package com.aps.service.Algorithm;

import com.aps.common.util.FileHelper;
import com.aps.entity.Algorithm.Chromosome;
import com.aps.entity.Algorithm.GlobalOperationInfo;
import com.aps.entity.basic.Entry;
import com.aps.entity.basic.Machine;
import com.aps.entity.basic.MachineOption;
import com.aps.entity.basic.Order;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

/**
 * CP-SAT 自适应初始种群生成器
 * 根据工序规模自动选择策略，确保不阻塞主流程
 *
 * 策略分级：
 *   ≤100道   ：CP-SAT 全力求解，生成多个高质量解
 *   100~500道：CP-SAT 限时截断，配合启发式补充
 *   500~800道：瓶颈分解 + 分组 CP-SAT
 *   >800道   ：纯启发式 fallback
 *
 * 作者：佟礼
 * 时间：2026-05-21
 */
public class CpSatInitializer {

    private static final int SMALL_SCALE_THRESHOLD  = 100;
    private static final int MEDIUM_SCALE_THRESHOLD = 500;
    private static final int LARGE_SCALE_THRESHOLD  = 800;

    private final List<Entry> allOperations;
    private final List<Machine> machines;
    private final List<Order> orders;
    private final LocalDateTime baseTime;

    public CpSatInitializer(List<Entry> allOperations,
                            List<Machine> machines,
                            List<Order> orders,
                            LocalDateTime baseTime) {
        this.allOperations = allOperations;
        this.machines = machines;
        this.orders = orders;
        this.baseTime = baseTime;
    }

    /**
     * 自适应生成初始种群（带 fallback）
     *
     * @param globalOpList   全局工序列表
     * @param targetCount    目标 CP-SAT 解的数量
     * @param timeBudgetSec  总时间预算（秒）
     * @return CP-SAT 生成的 Chromosome 列表，失败时返回空列表
     */
    public List<Chromosome> generate(List<GlobalOperationInfo> globalOpList,
                                     int targetCount,
                                     int timeBudgetSec) {
        if (!CpSatFjspModel.isAvailable()) {
            FileHelper.writeLogFile("[CP-SAT] 原生库不可用，跳过");
            return Collections.emptyList();
        }

        int opCount = globalOpList.size();
        FileHelper.writeLogFile(String.format(
                "[CP-SAT] 工序数=%d, 目标解数=%d, 时间预算=%ds", opCount, targetCount, timeBudgetSec));

        try {
            if (opCount <= SMALL_SCALE_THRESHOLD) {
                return smallScaleSolve(globalOpList, targetCount, timeBudgetSec);
            } else if (opCount <= MEDIUM_SCALE_THRESHOLD) {
                return mediumScaleSolve(globalOpList, targetCount, timeBudgetSec);
            } else if (opCount <= LARGE_SCALE_THRESHOLD) {
                return largeScaleDecomposed(globalOpList, targetCount, timeBudgetSec);
            } else {

                return largeScaleDecomposed(globalOpList, targetCount, timeBudgetSec);


               // FileHelper.writeLogFile("[CP-SAT] 工序数超过阈值，跳过CP-SAT，回退启发式");
               // return Collections.emptyList();
            }
        } catch (Exception e) {
            FileHelper.writeLogFile("[CP-SAT] 求解异常，回退启发式: " + e.getMessage());
            return Collections.emptyList();
        }
    }

    /**
     * 小规模：全力求解多个高质量解
     */
    private List<Chromosome> smallScaleSolve(List<GlobalOperationInfo> globalOpList,
                                             int targetCount, int timeBudgetSec) {
        CpSatFjspModel model = new CpSatFjspModel(globalOpList, allOperations, machines,orders, baseTime);
        return model.generateDiverseSolutions(
                Math.min(targetCount, 8), timeBudgetSec, true);
    }

    /**
     * 中规模：限时截断，少生成几个解
     */
    private List<Chromosome> mediumScaleSolve(List<GlobalOperationInfo> globalOpList,
                                              int targetCount, int timeBudgetSec) {
        CpSatFjspModel model = new CpSatFjspModel(globalOpList, allOperations, machines,orders, baseTime);
        int effectiveTarget = Math.min(targetCount, 5);
        int perSolveTime = Math.max(timeBudgetSec, 15);
        return model.generateDiverseSolutions(effectiveTarget, perSolveTime, true);
    }

    /**
     * 大规模：瓶颈分解策略
     * 只对瓶颈机器上的工序使用 CP-SAT，其余用贪心分配
     */
    private List<Chromosome> largeScaleDecomposed(List<GlobalOperationInfo> globalOpList,
                                                  int targetCount, int timeBudgetSec) {
        List<Chromosome> results = new ArrayList<>();
        int effectiveTarget = Math.min(targetCount, 3);

        // 1. 识别瓶颈机器：总加工负荷最高的机器
        List<Long> bottleneckMachineIds = findBottleneckMachineIds(globalOpList, 3);

        // 2. 分离瓶颈工序 vs 非瓶颈工序
        List<GlobalOperationInfo> bottleneckOps = new ArrayList<>();
        List<GlobalOperationInfo> nonBottleneckOps = new ArrayList<>();

        for (GlobalOperationInfo gop : globalOpList) {
            Entry op = gop.getOp();
            if (op.getMachineOptions() != null) {
                boolean isOnBottleneck = op.getMachineOptions().stream()
                        .anyMatch(mo -> bottleneckMachineIds.contains(mo.getMachineId()));
                if (isOnBottleneck) {
                    bottleneckOps.add(gop);
                } else {
                    nonBottleneckOps.add(gop);
                }
            } else {
                nonBottleneckOps.add(gop);
            }
        }

        if (bottleneckOps.isEmpty()) {
            FileHelper.writeLogFile("[CP-SAT] 未识别到瓶颈，回退中规模求解");
            return mediumScaleSolve(globalOpList, effectiveTarget, timeBudgetSec);
        }

        FileHelper.writeLogFile(String.format(
                "[CP-SAT] 瓶颈分解: 瓶颈工序=%d, 非瓶颈工序=%d",
                bottleneckOps.size(), nonBottleneckOps.size()));

        // 3. 对瓶颈工序用 CP-SAT
        List<Machine> bottleneckMachines = machines.stream()
                .filter(m -> bottleneckMachineIds.contains(m.getId()))
                .collect(Collectors.toList());
        if (bottleneckMachines.isEmpty()) {
            bottleneckMachines = machines;
        }

        CpSatFjspModel model = new CpSatFjspModel(
                bottleneckOps, allOperations, bottleneckMachines,orders, baseTime);
        List<Chromosome> cpSatResults = model.generateDiverseSolutions(
                effectiveTarget, timeBudgetSec, true);

        // 4. 对非瓶颈工序填入贪心分配
        for (Chromosome chromo : cpSatResults) {
            fillGreedyForNonBottleneck(chromo, bottleneckOps, nonBottleneckOps, globalOpList);
            results.add(chromo);
        }

        return results;
    }

    /**
     * 找出负载最高的前 N 台瓶颈机器
     */
    private List<Long> findBottleneckMachineIds(List<GlobalOperationInfo> globalOpList, int topN) {
        Map<Long, Double> machineTotalLoad = new LinkedHashMap<>();

        for (GlobalOperationInfo gop : globalOpList) {
            Entry op = gop.getOp();
            if (op.getMachineOptions() != null) {
                for (MachineOption mo : op.getMachineOptions()) {
                    machineTotalLoad.put(mo.getMachineId(),
                            machineTotalLoad.getOrDefault(mo.getMachineId(), 0.0)
                                    + mo.getProcessingTime());
                }
            }
        }

        return machineTotalLoad.entrySet().stream()
                .sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
                .limit(topN)
                .map(Map.Entry::getKey)
                .collect(Collectors.toList());
    }

    /**
     * 为非瓶颈工序填入贪心 machineSelection 和 operationSequencing
     */
    private void fillGreedyForNonBottleneck(Chromosome chromo,
                                            List<GlobalOperationInfo> bottleneckOps,
                                            List<GlobalOperationInfo> nonBottleneckOps,
                                            List<GlobalOperationInfo> fullGlobalOpList) {
        if (nonBottleneckOps.isEmpty()) return;

        Map<Long, Double> machineLoad = new LinkedHashMap<>();
        Random rnd = new Random();

        // 按全局顺序收集所有工序的 MS 和 OS
        CopyOnWriteArrayList<Integer> fullMs = new CopyOnWriteArrayList<>();
        CopyOnWriteArrayList<Integer> fullOs = new CopyOnWriteArrayList<>();

        int bottleneckIdx = 0;
        int nonBottleneckIdx = 0;

        List<Integer> cpSatMs = chromo.getMachineSelection();
        List<Integer> cpSatOs = chromo.getOperationSequencing();

        // 重建完整的 machineSelection（保持全局顺序）
        for (GlobalOperationInfo gop : fullGlobalOpList) {
            boolean isBottleneck = bottleneckOps.contains(gop);
            boolean isNonBottleneck = nonBottleneckOps.contains(gop);

            if (isBottleneck && bottleneckIdx < cpSatMs.size()) {
                fullMs.add(cpSatMs.get(bottleneckIdx));
                if (bottleneckIdx < cpSatOs.size()) {
                    fullOs.add(cpSatOs.get(bottleneckIdx));
                }
                bottleneckIdx++;
            } else if (isNonBottleneck) {
                Entry op = gop.getOp();
                List<MachineOption> options = op.getMachineOptions();

                int selectedIdx = selectMinLoadMachine(options, machineLoad, rnd);
                fullMs.add(selectedIdx + 1);

                MachineOption selected = options.get(selectedIdx);
                machineLoad.put(selected.getMachineId(),
                        machineLoad.getOrDefault(selected.getMachineId(), 0.0)
                                + selected.getProcessingTime());

                fullOs.add(gop.getGroupId());
                nonBottleneckIdx++;
            }
        }

        chromo.setMachineSelection(fullMs);
        chromo.setOperationSequencing(fullOs);
    }

    private int selectMinLoadMachine(List<MachineOption> options,
                                     Map<Long, Double> machineLoad,
                                     Random rnd) {
        double minLoad = options.stream()
                .mapToDouble(m -> machineLoad.getOrDefault(m.getMachineId(), 0.0)
                        + m.getProcessingTime())
                .min()
                .orElse(Double.MAX_VALUE);

        List<Integer> candidates = new ArrayList<>();
        for (int i = 0; i < options.size(); i++) {
            MachineOption mo = options.get(i);
            double candidateLoad = machineLoad.getOrDefault(mo.getMachineId(), 0.0)
                    + mo.getProcessingTime();
            if (Math.abs(candidateLoad - minLoad) < 0.01) {
                candidates.add(i);
            }
        }

        return candidates.get(rnd.nextInt(candidates.size()));
    }
}