package com.aps.macroplanner;

import com.google.ortools.Loader;
import com.google.ortools.linearsolver.MPSolver;
import com.aps.macroplanner.constraint.ConstraintFactory;
import com.aps.macroplanner.data.*;
import com.aps.macroplanner.model.MacroPlannerModel;
import com.aps.macroplanner.model.VariableFactory;
import com.aps.macroplanner.objective.ObjectiveBuilder;
import com.aps.macroplanner.objective.StrategyLevel;
import com.aps.macroplanner.output.ResultWriter;
import com.aps.macroplanner.output.SolutionPrinter;

import java.io.FileOutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * MacroPlanner 核心优化器 — 供应链产能规划混合整数规划(MIP)模型
 *
 * 将 Quintiq CapacityPlanningSuboptimizer 的核心模型迁移到 Google OR-Tools Java API。
 *
 * <h2>模型概述</h2>
 * <pre>
 * 优化目标: 在满足物料平衡、产能、库存、供应、批次等约束的前提下，
 *           最小化加权惩罚项 (需求缺口、库存偏差、产能超载、批次偏差等) 的加权和。
 *
 * 决策变量 (14 类):
 *   PTQty                  — 生产量
 *   InvQty                 — 期末库存
 *   SalesDemandQty         — 销售需求满足量
 *   DemandSlack            — 需求松弛 (防止不可行)
 *   OperationDemandQty     — BOM 依赖需求 (原材料消耗)
 *   DependentDemandInPISPIP— PISPIP 总依赖需求
 *   产能松弛    (CapacityOverloaded, CapacityNotMet)
 *   库存松弛    (MinInvQtyUnder, MaxInvQtyOver, InvQtyUnderTarget)
 *   供应松弛    (SupplyTargetQtyUnder, MinSupplyQtyUnder, MaxSupplyQtyOver)
 *   批次松弛    (PTLotSizeOver, PTLotSizeUnder)
 *
 * 约束 (7 类):
 *   物料平衡   — 每周期流入 = 流出
 *   BOM 依赖需求— 生产消耗 = 产量 × BOM 因子
 *   产能       — 设备产能上下限
 *   库存规格   — 最小/最大/目标库存
 *   供应规格   — 最小/最大/目标供应
 *   批次大小   — 批量生产约束
 *   KPI 汇总   — 各松弛变量汇总到全局 KPI
 *
 * 目标函数:
 *   最小化 Σ (KPI权重 × KPI汇总变量)
 * </pre>
 *
 * <h2>模块架构</h2>
 * <pre>
 * MacroPlannerOptimizer (编排器 — 流程控制)
 *   ├── MacroPlannerModel      (模型容器 — 求解器 + 所有变量)
 *   ├── VariableFactory        (变量工厂 — 创建所有决策变量)
 *   ├── ConstraintFactory      (约束工厂 — 统一调度所有约束构建)
 *   │   ├── BalanceConstraint      (物料平衡约束)
 *   │   ├── BomConstraint           (BOM 依赖需求约束)
 *   │   ├── CapacityConstraint      (产能约束)
 *   │   ├── InventorySpecConstraint (库存规格约束)
 *   │   ├── SupplySpecConstraint    (供应规格约束)
 *   │   ├── LotSizeConstraint       (批次大小约束)
 *   │   └── KpiAggregator           (KPI 汇总变量)
 *   └── ObjectiveBuilder        (目标函数)
 * </pre>
 *
 * <h2>测试场景</h2>
 * 两级供应链: ProductA(成品) ← OP1 生产, 消耗 ProductB(原材料) × 1.0 ← OP2 生产
 * 4 个周期, 2 个产品, 2 个操作, 共享 1 台设备 (Unit1, 最大产能 200/周期)
 */
public class MacroPlannerOptimizer {

    /** LP 模型文件和日志文件的输出目录 */
    private static final String LOG_DIR = "mp";

    /** LP 模型文件路径 */
    private static final String LP_FILE_PATH = LOG_DIR + "/lp/model.lp";

    /** 运行日志文件路径 */
    private static final String LOG_FILE_PATH = LOG_DIR + "/log/log.txt";
    /** 获取模型容器 (供 ResultWriter 等外部组件使用)。 */
    public MacroPlannerModel getModel() { return model; }

    /** 获取输入数据 (供 ResultWriter 等外部组件使用)。 */
    public TestDataBuilder getData() { return data; }
    // ==================== 核心组件 ====================

    /** 模型容器 — 持有求解器、所有决策变量和 KPI 汇总变量 */
    private final MacroPlannerModel model;

    /** 测试数据构建器, 包含所有输入数据 (产品、操作、BOM、需求、库存规格等) */
    private final TestDataBuilder data;

    /**
     * 构造优化器实例 (使用默认简单测试数据)。
     */
    public MacroPlannerOptimizer() {
        this(new TestDataBuilder());
    }

    /**
     * 构造优化器实例 (使用自定义测试数据)。
     * @param data 测试数据构建器 (支持子类如 ComprehensiveTestDataBuilder)
     */
    public MacroPlannerOptimizer(TestDataBuilder data) {
        this.data = data;
        this.model = new MacroPlannerModel();
    }

    /**
     * 配置日志级别, 输出安全库存天数折算的详细过程。
     *
     * <p>INFO 级别: 输出每个安全库存约束的构建摘要</p>
     * <p>FINE 级别: 输出每个周期的折算详情 + 折算公式</p>
     * <p>设为 Level.OFF 可关闭日志</p>
     */
    private static void configureLogging() {
        // 配置根 Logger 使用 ConsoleHandler
        Logger rootLogger = Logger.getLogger("");
        rootLogger.setLevel(Level.INFO);

        // 清除默认 handler, 使用自定义格式
        for (java.util.logging.Handler h : rootLogger.getHandlers()) {
            rootLogger.removeHandler(h);
        }

        ConsoleHandler handler = new ConsoleHandler();
        handler.setLevel(Level.ALL);
        handler.setFormatter(new java.util.logging.SimpleFormatter() {
            @Override
            public synchronized String format(java.util.logging.LogRecord record) {
                return String.format("  [%s] %s%n",
                        record.getLevel().getLocalizedName(), record.getMessage());
            }
        });
        rootLogger.addHandler(handler);
    }

    // ==================== 模型构建流程 ====================

    /**
     * 构建完整的优化模型。
     *
     * 构建顺序 (通过工厂类统一调度, MacroPlannerModel 协作):
     *   1. 创建所有决策变量          → VariableFactory.createAll()
     *   2. 创建所有约束和 KPI 汇总   → ConstraintFactory.buildAll()
     *   3. 创建目标函数 (加权求和)    → ObjectiveBuilder.build()
     */
    public void buildModel() {
        // 启用安全库存天数的详细日志 (INFO 级别)
        // 设为 FINE 可输出每个周期的折算详情
        configureLogging();

        System.out.println("=== 开始构建 MacroPlanner 优化模型 ===\n");

        // 0. 数据完整性检查 (在构建模型前验证)
        DataValidator validator = new DataValidator(data);
        if (!validator.validate()) {
            System.out.println("  ⚠️ 数据检查发现错误, 求解结果可能不可靠\n");
        } else {
            System.out.println("  [OK] 数据检查通过\n");
        }

        // 1. 决策变量
        VariableFactory.createAll(model, data);
        System.out.println("  [OK] 决策变量创建完成");

        // 2. 约束 + KPI 汇总 (由 ConstraintFactory 统一调度)
        ConstraintFactory.buildAll(model, data);
        System.out.println("  [OK] 约束与KPI汇总创建完成");

        // 3. 目标函数 (加权求和)
        ObjectiveBuilder.build(model, data);
        System.out.println("  [OK] 目标函数创建完成");

        // 4. 导出 LP 模型文件
        exportLpModel();

        System.out.println("\n模型统计: 变量=" + model.getSolver().numVariables()
                + ", 约束=" + model.getSolver().numConstraints() + "\n");
    }

    /** 求解开始时间 (用于计算耗时) */
    private long startTimeMs;

    /**
     * 导出 LP 模型文件到磁盘。
     *
     * <p>LP 文件包含完整的线性规划模型: 目标函数、所有变量和约束,
     * 可用 LP 求解器 (如 Gurobi、CPLEX) 直接读取,
     * 便于调试、验证和审计模型结构。</p>
     */
    private void exportLpModel() {
        try {
            Path logDir = Paths.get(LOG_DIR);
            if (!Files.exists(logDir)) {
                Files.createDirectories(logDir);
            }
            String lpContent = model.getSolver().exportModelAsLpFormat();
            Path lpPath = Paths.get(LP_FILE_PATH).toAbsolutePath();
            Files.write(lpPath, lpContent.getBytes(StandardCharsets.UTF_8));
            System.out.println("  [OK] LP模型文件已导出: " + lpPath);
        } catch (Exception e) {
            System.err.println("  [WARN] LP模型导出失败: " + e.getMessage());
        }
    }

    // ==================== 求解 ====================

    /**
     * 分层优化结果记录。
     */
    private static class LevelResult {
        final StrategyLevel level;
        final double optimalValue;
        final MPSolver.ResultStatus status;

        LevelResult(StrategyLevel level, double optimalValue, MPSolver.ResultStatus status) {
            this.level = level;
            this.optimalValue = optimalValue;
            this.status = status;
        }
    }

    /** 各层级求解结果 (solve() 填充) */
    private final List<LevelResult> levelResults = new ArrayList<>();

    /**
     * 执行分层优化求解。
     *
     * <h3>分层优化流程</h3>
     * <pre>
     *   1. 定义策略层级 (需求 → 产能 → 业务KPI → 软约束)
     *   2. 逐层求解:
     *      a) 清除上层目标, 设置当前层目标
     *      b) 调用 solver.solve()
     *      c) 记录最优值
     *      d) 添加边界约束: 上层目标 ≤ 最优值 × (1 + slack)
     *   3. 输出最终结果 (最后一层的解为最终解)
     * </pre>
     *
     * <p>该实现对应 Quintiq 中 StrategyLevel 的 HierarchicalSolver 机制。
     * 每个层级独立求解, 上层最优值作为下层约束, 确保严格优先级顺序。</p>
     */
    public void solve() {
        System.out.println("=== 开始分层求解 ===\n");
        startTimeMs = System.currentTimeMillis();

        KPIWeights w = data.getKpiWeights();
        List<StrategyLevel> levels = defineLevels(w);

        levelResults.clear();

        for (int i = 0; i < levels.size(); i++) {
            StrategyLevel level = levels.get(i);
            if (!level.hasKpis()) continue;

            System.out.printf("--- 第 %d/%d 层: %s (松弛=%.0f%%) ---%n",
                    i + 1, levels.size(), level.getName(),
                    level.getRelativeGoalSlack() * 100);

            // 清除上层目标, 设置当前层目标
            ObjectiveBuilder.clearObjective(model);
            ObjectiveBuilder.setLevelObjective(model, level);

            // 求解 (计时)
            long levelStartMs = System.currentTimeMillis();
            final MPSolver.ResultStatus status = model.getSolver().solve();
            long levelElapsedMs = System.currentTimeMillis() - levelStartMs;
            double optimalValue = model.getSolver().objective().value();

            // SCIP 风格求解摘要
            System.out.printf("  SCIP Status        : %s%n", status);
            System.out.printf("  Solving Time (sec) : %.2f%n", levelElapsedMs / 1000.0);
            System.out.printf("  Primal Bound       : %+.6e%n", optimalValue);

            // 输出当前层各 KPI 值
            for (StrategyLevel.KPIEntry kpi : level.getKpis()) {
                double kpiValue = kpi.variable.solutionValue();
                double penalty = kpi.effectiveCoefficient() * kpiValue;
                System.out.printf("    %s: %.2f (系数=%.1f, 惩罚=%.2f)%n",
                        kpi.name, kpiValue, kpi.effectiveCoefficient(), penalty);
            }

            levelResults.add(new LevelResult(level, optimalValue, status));

            // 添加边界约束 (最后一层不需要)
            if (i < levels.size() - 1 && level.getRelativeGoalSlack() >= 0.0) {
                ObjectiveBuilder.addLevelBoundConstraint(model, level, optimalValue);
                System.out.printf("  已添加边界约束: 上层目标 ≤ %.2f%n",
                        optimalValue * (1.0 + level.getRelativeGoalSlack()));
            }

            System.out.println();
        }

        // 输出最终结果
        MPSolver.ResultStatus finalStatus = model.getSolver().solve();
        if (finalStatus == MPSolver.ResultStatus.OPTIMAL
                || finalStatus == MPSolver.ResultStatus.FEASIBLE) {
            SolutionPrinter printer = new SolutionPrinter(model, data, startTimeMs);
            printer.printAll();
            // 构建层级最优值列表
            List<Double> levelObjValues = new ArrayList<>();
            for (LevelResult r : levelResults) {
                levelObjValues.add(r.optimalValue);
            }
            printer.printHierarchicalSummary(levels, levelObjValues);

            // 回写业务对象到 JSON 文件
            ResultWriter rw = new ResultWriter(model, data, startTimeMs);
            boolean jsonPath = rw.saveResultToFile("1");
            if (jsonPath) {
                System.out.println("\n[OK] 优化结果JSON已导出: " + jsonPath);
            }
        } else {
            System.out.println("求解失败! 状态: " + finalStatus);
        }
    }

    /**
     * 定义策略层级 — 将 KPI 按优先级分组。
     *
     * <h3>层级划分</h3>
     * <pre>
     *   Level 1 (需求满足):   Fulfillment — 需求缺口必须最小化
     *   Level 2 (产能):       UnitCapacity — 物理产能约束
     *   Level 3 (业务KPI):    LotSize, TargetInventory, SupplyTarget, SalesPriority
     *   Level 4 (软约束):     MaxInventory, MinInventory, MinSupply, MaxSupply
     * </pre>
     *
     * @param w KPI 权重配置
     * @return 策略层级列表 (按优先级排序)
     */
    private List<StrategyLevel> defineLevels(KPIWeights w) {
        List<StrategyLevel> levels = new ArrayList<>();

        // === Level 1: 需求满足 (最高优先级, 严格分层, slack=0%) ===
        StrategyLevel l1 = new StrategyLevel(1, "需求满足", 0.0);
        l1.addKPI("需求缺口", model.getTotalFulfillment(), w.getFulfillmentWeight());
        levels.add(l1);

        // === Level 2: 产能约束 (物理硬约束, 严格分层, slack=0%) ===
        StrategyLevel l2 = new StrategyLevel(2, "产能约束", 0.0);
        l2.addKPI("产能超载", model.getTotalUnitCapacity(), w.getUnitCapacityWeight());
        levels.add(l2);

        // === Level 3: 业务KPI (允许 5% 退化, slack=5%) ===
        StrategyLevel l3 = new StrategyLevel(3, "业务KPI", 0.05);
        l3.addKPI("批次偏差", model.getTotalLotSize(), w.getLotSizeWeight());
        l3.addKPI("目标库存偏差", model.getTotalTargetInvLevel(), w.getTargetInventoryLevelWeight());
        l3.addKPI("供应目标偏差", model.getTotalSupplyTarget(), w.getSupplyTargetWeight());
        l3.addKPI("销售优先级", model.getTotalSalesDemandPriority(),
                w.getSalesDemandPriorityWeight(), true); // 负系数 = 最大化
        levels.add(l3);

        // === Level 4: 软约束 (允许 10% 退化, slack=10%) ===
        StrategyLevel l4 = new StrategyLevel(4, "软约束", 0.10);
        l4.addKPI("超库存", model.getTotalMaxInventoryLevel(), w.getMaxInventoryLevelWeight());
        l4.addKPI("欠库存", model.getTotalMinInventoryLevel(), w.getMinInventoryLevelWeight());
        l4.addKPI("最小供应不足", model.getTotalMinSupply(), w.getMinSupplyWeight());
        l4.addKPI("最大供应超出", model.getTotalMaxSupply(), w.getMaxSupplyWeight());
        levels.add(l4);

        return levels;
    }

    // ==================== 主入口 ====================

    /**
     * 程序入口。
     * 执行流程:
     *   1. 加载 OR-Tools 本地库 (JNI)
     *   2. 创建优化器实例
     *   3. 构建模型 (变量 + 约束 + 目标)
     *   4. 导出 LP 模型文件
     *   5. 求解并输出结果 (同时写入日志文件)
     */
    public static void main(String[] args) {
        // 准备日志目录
        try {
            Path logDir = Paths.get(LOG_DIR);
            if (!Files.exists(logDir)) {
                Files.createDirectories(logDir);
            }
        } catch (Exception e) {
            System.err.println("无法创建日志目录: " + e.getMessage());
        }

        // 将控制台输出同时写入日志文件 (Tee模式)
        PrintStream originalOut = System.out;
        try {
            String logPath = Paths.get(LOG_FILE_PATH).toAbsolutePath().toString();
            PrintStream fileOut = new PrintStream(new FileOutputStream(logPath));
            PrintStream teeOut = new TeePrintStream(originalOut, fileOut);
            System.setOut(teeOut);
            System.setErr(teeOut);
        } catch (Exception e) {
            System.err.println("无法创建日志文件: " + e.getMessage());
        }

        // 输出运行时间戳
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println("=== MacroPlanner 优化器运行日志 ===");
        System.out.println("=== 运行时间: " + timestamp + " ===\n");

        // 加载 OR-Tools 本地库 (包含 SCIP 求解器的 C++ 实现)
        Loader.loadNativeLibraries();

        MacroPlannerOptimizer optimizer = new MacroPlannerOptimizer();
        optimizer.buildModel();
        optimizer.solve();

        System.out.println("=== 运行结束 ===");

        // 恢复原始 System.out
        System.setOut(originalOut);
        System.setErr(originalOut);
    }

    /**
     * TeePrintStream — 将输出同时写入两个 PrintStream (控制台 + 文件)。
     */
    private static class TeePrintStream extends PrintStream {
        private final PrintStream secondary;

        TeePrintStream(PrintStream primary, PrintStream secondary) {
            super(primary);
            this.secondary = secondary;
        }

        @Override
        public void write(int b) {
            super.write(b);
            secondary.write(b);
        }

        @Override
        public void write(byte[] buf, int off, int len) {
            super.write(buf, off, len);
            secondary.write(buf, off, len);
        }

        @Override
        public void flush() {
            super.flush();
            secondary.flush();
        }

        @Override
        public void close() {
            super.close();
            secondary.close();
        }
    }
}