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 com.google.ortools.Loader;
import com.google.ortools.sat.CpModel;
import com.google.ortools.sat.CpSolver;
import com.google.ortools.sat.CpSolverStatus;
import com.google.ortools.sat.IntVar;
import com.google.ortools.sat.IntervalVar;
import com.google.ortools.sat.LinearArgument;
import com.google.ortools.sat.LinearExpr;
import com.google.ortools.sat.Literal;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 基于 OR-Tools CP-SAT 的柔性作业车间调度（FJSP）建模器
 * 用于生成高质量的初始染色体编码（machineSelection + operationSequencing）
 *
 * 建模方式：每个工序使用 Literal (BoolVar) 表示选择哪台机器，
 * 通过 optional IntervalVar + NoOverlap 约束保证机器容量。
 *
 * 作者：佟礼
 * 时间：2026-05-21
 */
public class CpSatFjspModel {

    private static boolean nativeLibAvailable = false;
    static {
        try {
            Loader.loadNativeLibraries();
            nativeLibAvailable = true;
        } catch (Exception ignored) {
            System.err.println("[CP-SAT] 原生库加载失败，将回退到启发式初始化: " + ignored.getMessage());
        }
    }

    public static boolean isAvailable() {
        return nativeLibAvailable;
    }

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

    private final int operationCount;

    private CpModel model;
    private final List<IntVar> startVars = new ArrayList<>();
    private final List<IntVar> endVars = new ArrayList<>();
    private final List<Literal[]> presenceMatrix = new ArrayList<>();
    private final Map<Long, List<IntervalVar>> machineToIntervals = new LinkedHashMap<>();
    private IntVar makespanVar;
    private int horizonSeconds;

    public CpSatFjspModel(List<GlobalOperationInfo> globalOpList,
                          List<Entry> allOperations,
                          List<Machine> machines,
                          List<Order> orders,
                          LocalDateTime baseTime) {
        this.globalOpList = globalOpList;
        this.allOperations = allOperations;
        this.machines = machines;
        this.operationCount = globalOpList.size();
        this.orders=orders;
        this.baseTime=baseTime;
    }

    private int estimateHorizon() {
        double totalProcessing = 0;
        for (GlobalOperationInfo gop : globalOpList) {
            Entry op = gop.getOp();
            if (op.getMachineOptions() != null && !op.getMachineOptions().isEmpty()) {
                double avgTime = op.getMachineOptions().stream()
                        .mapToDouble(MachineOption::getProcessingTime)
                        .average()
                        .orElse(0);
                totalProcessing += avgTime;
            }
        }
        int machineCount = Math.max(machines.size(), 1);
        return Math.max((int) Math.ceil(totalProcessing * 4.0 / machineCount) + 86400, 3600);
    }

    /**
     * 构建 FJSP 的 CP-SAT 模型（创建新模型实例）
     */
    public CpModel build() {
        model = new CpModel();
        startVars.clear();
        endVars.clear();
        presenceMatrix.clear();
        machineToIntervals.clear();
        buildModelOnModel(model);
        return model;
    }

    /**
     * 在指定 model 实例上构建全部变量和约束
     */
    private void buildModelOnModel(CpModel targetModel) {
        this.model = targetModel;
        horizonSeconds = estimateHorizon();

        for (Machine m : machines) {
            machineToIntervals.put(m.getId(), new ArrayList<>());
        }
//        1. 工序变量创建 + machineIndex → duration 映射
//        2. 同订单工序顺序（groupId + sequence）
//        3. Priority 硬约束
//     ├── 按 distinct Priority 值分组（TreeMap 自动升序）
//     ├── Priority=1 全部完成 → Priority=3 才能开始
//     ├── Priority=3 全部完成 → Priority=5 才能开始
//     └── ... 只需 (distinct数 - 1) 条约束
//        4. 机器容量 no_overlap
//        5. 目标函数：makespan + Priority 加权完工时间
        for (int i = 0; i < operationCount; i++) {
            Entry op = globalOpList.get(i).getOp();
            List<MachineOption> options = op.getMachineOptions();

            if (options == null || options.isEmpty()) {
                throw new RuntimeException(
                        "工序没有设置设备: " + op.getOrderCode() + ":" + op.getSequence());
            }

            int maxDuration = 0;
            for (MachineOption mo : options) {
                maxDuration = Math.max(maxDuration, (int) mo.getProcessingTime());
            }

            IntVar startVar = model.newIntVar(0, horizonSeconds, "s_" + i);
            IntVar endVar = model.newIntVar(0, horizonSeconds, "e_" + i);
            IntervalVar mainInterval = model.newIntervalVar(
                    startVar, model.newConstant(maxDuration), endVar, "iv_" + i);

            Literal[] presences = new Literal[options.size()];
            for (int j = 0; j < options.size(); j++) {
                MachineOption mo = options.get(j);
                int procTime = (int) mo.getProcessingTime();
                Long machineId = mo.getMachineId();

                presences[j] = model.newBoolVar("p_" + i + "_" + j);

                IntervalVar optInterval = model.newOptionalIntervalVar(
                        startVar, model.newConstant(procTime), endVar, presences[j],
                        "opt_" + i + "_m" + machineId);

                List<IntervalVar> list = machineToIntervals.computeIfAbsent(
                        machineId, k -> new ArrayList<>());
                list.add(optInterval);
            }

            model.addBoolOr(presences);
            for (int j = 0; j < presences.length; j++) {
                for (int k = j + 1; k < presences.length; k++) {
                    model.addBoolOr(new Literal[]{
                            presences[j].not(), presences[k].not()});
                }
            }

            startVars.add(startVar);
            endVars.add(endVar);
            presenceMatrix.add(presences);
        }

        Map<Integer, List<Integer>> orderOpsMap = new LinkedHashMap<>();
        for (int i = 0; i < operationCount; i++) {
            orderOpsMap.computeIfAbsent(globalOpList.get(i).getGroupId(),
                    k -> new ArrayList<>()).add(i);
        }
        for (List<Integer> ops : orderOpsMap.values()) {
            ops.sort(Comparator.comparingInt(idx -> globalOpList.get(idx).getSequence()));
            for (int k = 0; k < ops.size() - 1; k++) {
                model.addLessOrEqual(endVars.get(ops.get(k)), startVars.get(ops.get(k + 1)));
            }
        }

        Map<Double, List<Integer>> priorityGroups = new TreeMap<>();
        for (int i = 0; i < operationCount; i++) {
            double p = globalOpList.get(i).getOp().getPriority();
            if (p > 0) {
                priorityGroups.computeIfAbsent(p, k -> new ArrayList<>()).add(i);
            }
        }
        List<Double> sortedPriorities = new ArrayList<>(priorityGroups.keySet());
        for (int k = 0; k < sortedPriorities.size() - 1; k++) {
            List<Integer> hiPriOps = priorityGroups.get(sortedPriorities.get(k));
            List<Integer> loPriOps = priorityGroups.get(sortedPriorities.get(k + 1));

            IntVar hiMaxEnd = model.newIntVar(0, horizonSeconds,
                    "hiEnd_" + k);
            IntVar loMinStart = model.newIntVar(0, horizonSeconds,
                    "loStart_" + k);

            for (int idx : hiPriOps) {
                model.addLessOrEqual(endVars.get(idx), hiMaxEnd);
            }
            for (int idx : loPriOps) {
                model.addGreaterOrEqual(startVars.get(idx), loMinStart);
            }
            model.addLessOrEqual(hiMaxEnd, loMinStart);
        }

        for (Map.Entry<Long, List<IntervalVar>> entry : machineToIntervals.entrySet()) {
            List<IntervalVar> intervals = entry.getValue();
            if (intervals.size() > 1) {
                model.addNoOverlap(intervals.toArray(new IntervalVar[0]));
            }
        }

        makespanVar = model.newIntVar(0, horizonSeconds, "makespan");
        for (int i = 0; i < operationCount; i++) {
            model.addLessOrEqual(endVars.get(i), makespanVar);
        }

        double maxPriority = 0;
        for (int i = 0; i < operationCount; i++) {
            maxPriority = Math.max(maxPriority,
                    globalOpList.get(i).getOp().getPriority());
        }

        List<LinearArgument> objTerms = new ArrayList<>();
        List<Long> objWeights = new ArrayList<>();

        objTerms.add(makespanVar);
        objWeights.add(100L);

        for (int i = 0; i < operationCount; i++) {
            double priority = globalOpList.get(i).getOp().getPriority();
            if (priority > 0) {
                objTerms.add(endVars.get(i));
                objWeights.add((long)(maxPriority - priority + 1));
            }
        }

        // ==================== 新增目标1：Tardiness（延迟时间） ====================
        // 按订单分组，找到每个订单最后一道工序的end_time
        Map<Integer, List<Integer>> groupOps = new LinkedHashMap<>();
        Map<Integer, Long> groupDueDates = new HashMap<>();
        for (int i = 0; i < operationCount; i++) {
            GlobalOperationInfo info = globalOpList.get(i);
            int groupId = info.getGroupId();
            groupOps.computeIfAbsent(groupId, k -> new ArrayList<>()).add(i);
            if (!groupDueDates.containsKey(groupId)) {
                Order order = orders.stream()
                        .filter(o -> info.getOp().getOrderId().equals(o.getOrderId()))
                        .findFirst().orElse(null);
                if (order != null && order.getDueDate() != null) {
                    long dueMin = ChronoUnit.MINUTES.between(baseTime, order.getDueDate());
                    groupDueDates.put(groupId, dueMin * 60);
                }
            }
        }

        long totalTardinessWeight = 80L;
        for (Map.Entry<Integer, List<Integer>> entry : groupOps.entrySet()) {
            int groupId = entry.getKey();
            List<Integer> opIndices = entry.getValue();

            // 找到该订单最后完成的工序
            IntVar lastEnd = model.newIntVar(0, horizonSeconds, "lastEnd_g" + groupId);
            List<LinearArgument> endCandidates = new ArrayList<>();
            for (int idx : opIndices) {
                endCandidates.add(endVars.get(idx));
            }
            model.addMaxEquality(lastEnd, endCandidates.toArray(new LinearArgument[0]));

            Long dueDateSec = groupDueDates.get(groupId);
            if (dueDateSec != null) {
                IntVar tardiness = model.newIntVar(0, horizonSeconds, "tardiness_g" + groupId);
                LinearArgument diff = LinearExpr.sum(new LinearArgument[]{
                        lastEnd, LinearExpr.constant(-dueDateSec)});

                model.addMaxEquality(tardiness, new LinearArgument[]{diff, LinearExpr.constant(0)});
                objTerms.add(tardiness);
                objWeights.add(totalTardinessWeight);
            }
        }

        // ==================== 新增目标2：Machine Load Balance（负载均衡） ====================
        // 简化版本：取每台机器上所有工序的最大end_time，加入目标函数
        // 这样 CP-SAT 会倾向于让各机器的完工时间更均匀
        Map<Long, List<LinearArgument>> machineEndTimes = new HashMap<>();
        for (int i = 0; i < operationCount; i++) {
            GlobalOperationInfo info = globalOpList.get(i);
            List<MachineOption> options = info.getOp().getMachineOptions();
            if (options == null || options.isEmpty()) continue;
            for (MachineOption mo : options) {
                machineEndTimes.computeIfAbsent(mo.getMachineId(), k -> new ArrayList<>()).add(endVars.get(i));
            }
        }

        long loadBalanceWeight = 30L;
        for (Map.Entry<Long, List<LinearArgument>> entry : machineEndTimes.entrySet()) {
            IntVar machineMakespan = model.newIntVar(0, horizonSeconds, "mk_m" + entry.getKey());
            model.addMaxEquality(machineMakespan, entry.getValue().toArray(new LinearArgument[0]));
            objTerms.add(machineMakespan);
            objWeights.add(loadBalanceWeight);
        }

        model.minimize(LinearExpr.weightedSum(
                objTerms.toArray(new LinearArgument[0]),
                objWeights.stream().mapToLong(Long::longValue).toArray()));
    }
   private boolean enableLogging=true;
    /**
     * 单次求解，返回一个 Chromosome
     */
    public Chromosome solveOnce(int timeLimitSeconds) {
        if (model == null) {
            build();
        }

        CpSolver solver = new CpSolver();
        solver.getParameters().setMaxTimeInSeconds(timeLimitSeconds);
        solver.getParameters().setLogSearchProgress(false);
        solver.getParameters().setNumSearchWorkers(4);

        CpSolverStatus status = solver.solve(model);
        if (status == CpSolverStatus.OPTIMAL || status == CpSolverStatus.FEASIBLE) {
            long makespanSec = (long) solver.value(makespanVar);
            FileHelper.log("[CpSatFjsp] 单次求解 状态=" + status
                    + " CP-SAT目标值=" + String.format("%,d", (long) solver.objectiveValue())
                    + " makespan=" + String.format("%,d秒 (%.1f小时)", makespanSec, makespanSec / 3600.0),enableLogging);
            return extractChromosome(solver);
        }
        FileHelper.log("[CpSatFjsp] 单次求解 状态=" + status + "（无解）",enableLogging);
        return null;
    }

    /**
     * 生成多个多样性解（通过随机种子 + 不同目标权重）
     */
    public List<Chromosome> generateDiverseSolutions(int targetCount,
                                                     int timeBudgetSec,
                                                     boolean randomPerturb) {
        List<Chromosome> results = new ArrayList<>();
        if (targetCount <= 0) return results;

        Random rnd = new Random();
        int perSolveTime = Math.max(timeBudgetSec / Math.min(targetCount, 5), 3);
        int maxRounds = Math.min(targetCount * 2, 15);

        for (int round = 0; round < maxRounds && results.size() < targetCount; round++) {
            model = new CpModel();
            startVars.clear();
            endVars.clear();
            presenceMatrix.clear();
            machineToIntervals.clear();
            buildModelOnModel(model);

            if (round % 3 == 1 && operationCount > 0) {
                double maxPriority = 0;
                for (int i = 0; i < operationCount; i++) {
                    maxPriority = Math.max(maxPriority,
                            globalOpList.get(i).getOp().getPriority());
                }

                int priorityCount = 0;
                for (int i = 0; i < operationCount; i++) {
                    if (globalOpList.get(i).getOp().getPriority() > 0) priorityCount++;
                }

                int totalTerms = 1 + operationCount + priorityCount;
                LinearArgument[] objVars = new LinearArgument[totalTerms];
                long[] objCoeffs = new long[totalTerms];
                objVars[0] = makespanVar;
                objCoeffs[0] = 100;
                int t = 1;

                for (int i = 0; i < operationCount; i++) {
                    objVars[t] = endVars.get(i);
                    objCoeffs[t] = 1;
                    t++;
                }

                for (int i = 0; i < operationCount; i++) {
                    double priority = globalOpList.get(i).getOp().getPriority();
                    if (priority > 0) {
                        objVars[t] = endVars.get(i);
                        objCoeffs[t] = (long)(maxPriority - priority + 1);
                        t++;
                    }
                }
                model.minimize(LinearExpr.weightedSum(objVars, objCoeffs));
            }

            CpSolver solver = new CpSolver();
            solver.getParameters().setMaxTimeInSeconds(perSolveTime);
            solver.getParameters().setLogSearchProgress(false);
            solver.getParameters().setNumSearchWorkers(4);
            solver.getParameters().setRandomSeed((int) (round * 1000L + rnd.nextInt(1000)));

            CpSolverStatus status = solver.solve(model);
            if (status == CpSolverStatus.OPTIMAL || status == CpSolverStatus.FEASIBLE) {
                long msSec = (long) solver.value(makespanVar);
                FileHelper.log("[CpSatFjsp] 第" + (round + 1) + "轮 状态=" + status
                        + " CP-SAT目标=" + String.format("%,d", (long) solver.objectiveValue())
                        + " makespan=" + String.format("%,d秒(%.1f小时)", msSec, msSec / 3600.0),enableLogging);
                Chromosome chromo = extractChromosome(solver);
                if (chromo != null && !containsDuplicate(results, chromo)) {
                    chromo.setGsOrls(4);
                    chromo.setGenerateType("CP-SAT");
                    results.add(chromo);
                }
            } else {
                FileHelper.log("[CpSatFjsp] 第" + (round + 1) + "轮 状态=" + status + "（无解）",enableLogging);
            }
        }

        FileHelper.log("[CpSatFjsp] 多样性解生成完成，共" + results.size() + "个解",enableLogging);
        return results;
    }

    private Chromosome extractChromosome(CpSolver solver) {
        Chromosome chromo = new Chromosome();
        chromo.setGsOrls(4);
        chromo.setGenerateType("CP-SAT");

        CopyOnWriteArrayList<Integer> ms = new CopyOnWriteArrayList<>();
        for (int i = 0; i < operationCount; i++) {
            Literal[] pres = presenceMatrix.get(i);
            int selectedIdx = 0;
            for (int j = 0; j < pres.length; j++) {
                if (solver.booleanValue(pres[j])) {
                    selectedIdx = j;
                    break;
                }
            }
            ms.add(selectedIdx + 1);
        }
        chromo.setMachineSelection(ms);

        List<OpTimeRecord> records = new ArrayList<>();
        for (int i = 0; i < operationCount; i++) {
            GlobalOperationInfo gop = globalOpList.get(i);
            int machineIdx = ms.get(i) - 1;
            records.add(new OpTimeRecord(
                    gop.getGroupId(),
                    gop.getSequence(),
                    (int) solver.value(startVars.get(i)),
                    machineIdx,
                    i));
        }

        records.sort(Comparator
                .comparingInt(OpTimeRecord::getStartTime)
                .thenComparingInt(OpTimeRecord::getMachineIdx));

        CopyOnWriteArrayList<Integer> os = new CopyOnWriteArrayList<>();
        for (OpTimeRecord rec : records) {
            os.add(rec.getGroupId());
        }
        chromo.setOperationSequencing(os);

        chromo.setMakespan(solver.value(makespanVar));
        return chromo;
    }

    private boolean containsDuplicate(List<Chromosome> list, Chromosome candidate) {
        String candidateKey = candidate.getGeneStr();
        for (Chromosome c : list) {
            if (c.getGeneStr().equals(candidateKey)) {
                return true;
            }
        }
        return false;
    }

    private static class OpTimeRecord {
        private final int groupId;
        private final int sequence;
        private final int startTime;
        private final int machineIdx;
        private final int globalIdx;

        OpTimeRecord(int groupId, int sequence, int startTime, int machineIdx, int globalIdx) {
            this.groupId = groupId;
            this.sequence = sequence;
            this.startTime = startTime;
            this.machineIdx = machineIdx;
            this.globalIdx = globalIdx;
        }

        int getGroupId() { return groupId; }
        int getSequence() { return sequence; }
        int getStartTime() { return startTime; }
        int getMachineIdx() { return machineIdx; }
    }
}