package com.aps.macroplanner.data;

import java.util.*;
import java.util.logging.Logger;

/**
 * 数据完整性检查器 — 在求解前验证数据模型的完整性和一致性。
 *
 * <h3>检查项</h3>
 * <ol>
 *   <li>产品→库存点映射: 每个产品至少有一个库存点</li>
 *   <li>产品→工序覆盖: 有需求的产品必须有对应的生产工序</li>
 *   <li>BOM 一致性: 输入物料的产品/库存点存在, 无循环依赖</li>
 *   <li>工序→单元映射: 每个工序的 Unit 在 UnitPeriod 中存在</li>
 *   <li>引用完整性: 所有数据引用的产品/库存点/周期/工序存在</li>
 *   <li>提前期可行性: leadTime 不超过周期范围</li>
 *   <li>原材料供应: 被 BOM 消耗但无生产的原材料需有初始库存或在途供应</li>
 *   <li>孤立产品: 无需求、无生产、无 BOM 角色的产品</li>
 * </ol>
 */
public class DataValidator {

    private static final Logger LOG = Logger.getLogger(DataValidator.class.getName());

    private final TestDataBuilder data;
    private final List<String> errors = new ArrayList<>();
    private final List<String> warnings = new ArrayList<>();

    public DataValidator(TestDataBuilder data) {
        this.data = data;
    }

    /** 执行所有检查, 返回是否有错误 */
    public boolean validate() {
        errors.clear();
        warnings.clear();

        checkProductStockingPoint();
        checkProductOperationCoverage();
        checkBomConsistency();
        checkOperationUnitMapping();
        checkReferenceIntegrity();
        checkLeadTimeFeasibility();
        checkRawMaterialSupply();
        checkOrphanProducts();

        // 输出结果
        if (!errors.isEmpty()) {
            LOG.severe("========== 数据检查: " + errors.size() + " 个错误 ==========");
            for (String e : errors) {
                LOG.severe("  ❌ " + e);
            }
        }
        if (!warnings.isEmpty()) {
            LOG.warning("========== 数据检查: " + warnings.size() + " 个警告 ==========");
            for (String w : warnings) {
                LOG.warning("  ⚠️ " + w);
            }
        }
        if (errors.isEmpty() && warnings.isEmpty()) {
            LOG.info("✅ 数据检查通过: 无错误, 无警告");
        } else if (errors.isEmpty()) {
            LOG.info("✅ 数据检查通过: 无错误, " + warnings.size() + " 个警告");
        }

        return errors.isEmpty();
    }

    // ==================== 1. 产品→库存点映射 ====================

    private void checkProductStockingPoint() {
        for (Product prod : data.getProducts()) {
            List<StockingPoint> sps = data.getStockingPointsForProduct(prod.getId());
            if (sps.isEmpty()) {
                errors.add("产品 " + prod.getId() + " 没有指定库存点 (缺少 ProductSpMapping)");
            }
        }
    }

    // ==================== 2. 产品→工序覆盖 ====================

    private void checkProductOperationCoverage() {
        // 收集哪些产品有外部需求
        Set<String> productsWithDemand = new HashSet<>();
        for (SalesDemand sd : data.getSalesDemands()) {
            productsWithDemand.add(sd.getProduct().getId());
        }

        // 收集哪些产品有 BOM 消耗 (被其他工序作为输入)
        Set<String> productsWithBomDemand = new HashSet<>();
        for (OperationInput input : data.getOperationInputs()) {
            productsWithBomDemand.add(input.getInputProduct().getId());
        }

        // 收集哪些产品有生产工序
        Set<String> productsWithOperation = new HashSet<>();
        for (Operation op : data.getOperations()) {
            for (OperationOutput oo : op.getOutputs()) {
                productsWithOperation.add(oo.getProductId());
            }
        }

        // 有销售需求但没有生产工序
        for (String prodId : productsWithDemand) {
            if (!productsWithOperation.contains(prodId)) {
                errors.add("产品 " + prodId + " 有销售需求, 但没有生产工序 (缺少 Operation)");
            }
        }

        // 有 BOM 消耗但没有生产工序 (且不是原材料)
        for (String prodId : productsWithBomDemand) {
            if (!productsWithOperation.contains(prodId)) {
                // 检查是否有初始库存或在途供应
                boolean hasSupply = false;
                for (InitialInventory inv : data.getInitialInventories()) {
                    if (inv.getProduct().getId().equals(prodId)) {
                        hasSupply = true;
                        break;
                    }
                }
                for (InTransitSupply its : data.getInTransitSupplies()) {
                    if (its.getProduct().getId().equals(prodId)) {
                        hasSupply = true;
                        break;
                    }
                }
                if (!hasSupply) {
                    errors.add("产品 " + prodId + " 被 BOM 消耗, 但没有生产工序, 也没有初始库存或在途供应");
                } else {
                    warnings.add("产品 " + prodId + " 被 BOM 消耗但没有生产工序, 依赖初始库存/在途供应 (消耗完后将无法补货)");
                }
            }
        }
    }

    // ==================== 3. BOM 一致性 ====================

    private void checkBomConsistency() {
        Set<String> productIds = new HashSet<>();
        for (Product p : data.getProducts()) productIds.add(p.getId());

        Set<String> spIds = new HashSet<>();
        for (StockingPoint sp : data.getStockingPoints()) spIds.add(sp.getId());

        Set<String> opIds = new HashSet<>();
        for (Operation op : data.getOperations()) opIds.add(op.getId());

        for (OperationInput input : data.getOperationInputs()) {
            Operation op = input.getOperation();
            Product inputProd = input.getInputProduct();
            StockingPoint inputSp = input.getInputSp();

            // 工序存在
            if (!opIds.contains(op.getId())) {
                errors.add("BOM 输入引用不存在的工序: " + op.getId());
            }
            // 投入产品存在
            if (!productIds.contains(inputProd.getId())) {
                errors.add("BOM 输入引用不存在的产品: " + inputProd.getId()
                        + " (工序 " + op.getId() + ")");
            }
            // 投入库存点存在
            if (!spIds.contains(inputSp.getId())) {
                errors.add("BOM 输入引用不存在的库存点: " + inputSp.getId()
                        + " (工序 " + op.getId() + " 消耗 " + inputProd.getId() + ")");
            }
            // 工序不能消耗自己产出的产品 (自循环) — 仅当输入和输出在同一库存点时才报错
            // 多工序路由中同一产品可经不同库存点流转 (如: 下料→SP_WIP1→粗加工→SP_WIP2→精加工→SP_FG)
            if (op.producesProductAtSp(inputProd.getId(), inputSp.getId())) {
                errors.add("工序 " + op.getId() + " 消耗自己产出的产品 " + inputProd.getId()
                        + "@" + inputSp.getId() + " (自循环 BOM, 同一库存点)");
            }
            // BOM 因子 > 0
            if (input.getFactor() <= 0) {
                errors.add("BOM 因子必须 > 0: " + op.getId() + " 消耗 " + inputProd.getId()
                        + " 因子=" + input.getFactor());
            }
        }

        // 循环依赖检测 (含库存点: 仅当产品+库存点都匹配时才构成循环)
        // 多工序路由中同一产品经不同库存点流转不构成循环
        for (OperationInput input : data.getOperationInputs()) {
            String consumedProd = input.getInputProduct().getId();
            String consumedSp = input.getInputSp().getId();

            // 遍历消费者工序的所有产出 (支持多产出/联产品)
            for (OperationOutput consumerOutput : input.getOperation().getOutputs()) {
                String consumerProd = consumerOutput.getProductId();
                String consumerSp = consumerOutput.getSpId();

                for (OperationInput other : data.getOperationInputs()) {
                    // 检查 other 是否产出 consumedProd@consumedSp 且消耗 consumerProd@consumerSp
                    if (other.getOperation().producesProductAtSp(consumedProd, consumedSp)
                            && other.getInputProduct().getId().equals(consumerProd)
                            && other.getInputSp().getId().equals(consumerSp)) {
                        errors.add("BOM 循环依赖: " + consumerProd + "@" + consumerSp
                                + " → " + consumedProd + "@" + consumedSp
                                + " → " + consumerProd + "@" + consumerSp);
                    }
                }
            }
        }
    }

    // ==================== 4. 工序→单元映射 ====================

    private void checkOperationUnitMapping() {
        Set<String> unitIds = new HashSet<>();
        for (UnitPeriod up : data.getUnitPeriods()) {
            unitIds.add(up.getUnitId());
        }

        for (Operation op : data.getOperations()) {
            for (UnitOperation uo : op.getUnitOperations()) {
                if (!unitIds.contains(uo.getUnitId())) {
                    errors.add("工序 " + op.getId() + " 的单元 " + uo.getUnitId()
                            + " 在 UnitPeriod 中不存在 (缺少产能定义)");
                }
            }
        }
    }

    // ==================== 5. 引用完整性 ====================

    private void checkReferenceIntegrity() {
        Set<String> productIds = new HashSet<>();
        for (Product p : data.getProducts()) productIds.add(p.getId());

        Set<String> spIds = new HashSet<>();
        for (StockingPoint sp : data.getStockingPoints()) spIds.add(sp.getId());

        Set<String> opIds = new HashSet<>();
        for (Operation op : data.getOperations()) opIds.add(op.getId());

        int maxPeriodIdx = data.getPeriods().size() - 1;

        // 销售需求
        for (SalesDemand sd : data.getSalesDemands()) {
            if (!productIds.contains(sd.getProduct().getId())) {
                errors.add("销售需求引用不存在的产品: " + sd.getProduct().getId());
            }
            if (!spIds.contains(sd.getStockingPoint().getId())) {
                errors.add("销售需求引用不存在的库存点: " + sd.getStockingPoint().getId());
            }
            if (sd.getPeriod().getIndex() > maxPeriodIdx) {
                errors.add("销售需求引用不存在的周期: " + sd.getPeriod().getIndex());
            }
            if (sd.getQuantity() < 0) {
                errors.add("销售需求数量不能为负: " + sd.getProduct().getId() + " P" + sd.getPeriod().getIndex());
            }
        }

        // 库存规格
        for (InventorySpec spec : data.getInventorySpecs()) {
            if (!productIds.contains(spec.getProduct().getId())) {
                errors.add("库存规格引用不存在的产品: " + spec.getProduct().getId());
            }
            if (!spIds.contains(spec.getStockingPoint().getId())) {
                errors.add("库存规格引用不存在的库存点: " + spec.getStockingPoint().getId());
            }
        }

        // 供应规格
        for (SupplySpec spec : data.getSupplySpecs()) {
            for (Operation op : spec.getOperations()) {
                if (!opIds.contains(op.getId())) {
                    errors.add("供应规格 " + spec.getName() + " 引用不存在的工序: " + op.getId());
                }
            }
        }

        // 初始库存
        for (InitialInventory inv : data.getInitialInventories()) {
            if (!productIds.contains(inv.getProduct().getId())) {
                errors.add("初始库存引用不存在的产品: " + inv.getProduct().getId());
            }
            if (!spIds.contains(inv.getStockingPoint().getId())) {
                errors.add("初始库存引用不存在的库存点: " + inv.getStockingPoint().getId());
            }
            if (inv.getQuantity() < 0) {
                errors.add("初始库存不能为负: " + inv.getProduct().getId()
                        + "@" + inv.getStockingPoint().getId());
            }
        }

        // 在途供应
        for (InTransitSupply its : data.getInTransitSupplies()) {
            if (!productIds.contains(its.getProduct().getId())) {
                errors.add("在途供应引用不存在的产品: " + its.getProduct().getId());
            }
            if (!spIds.contains(its.getStockingPoint().getId())) {
                errors.add("在途供应引用不存在的库存点: " + its.getStockingPoint().getId());
            }
        }
    }

    // ==================== 6. 提前期可行性 ====================

    private void checkLeadTimeFeasibility() {
        int numPeriods = data.getPeriods().size();
        for (Operation op : data.getOperations()) {
            int leadTimeDays = op.getLeadTimeDays();
            if (leadTimeDays < 0) {
                errors.add("工序 " + op.getId() + " leadTimeDays 不能为负: " + leadTimeDays);
            }
            // 检查是否有 startDate 做日期偏移
            Period firstPeriod = data.getPeriods().get(0);
            if (firstPeriod.getStartDate() != null && leadTimeDays > 0) {
                Period srcPeriod = data.getPeriodOffsetByDays(firstPeriod, leadTimeDays);
                if (srcPeriod == null) {
                    warnings.add("工序 " + op.getId() + " leadTimeDays=" + leadTimeDays
                            + " 天, 但第一个周期前无对应生产周期, 第1周期该产品无到货");
                }
            }
        }
    }

    // ==================== 7. 原材料供应 ====================

    private void checkRawMaterialSupply() {
        // 收集所有产品
        Set<String> productIds = new HashSet<>();
        for (Product p : data.getProducts()) productIds.add(p.getId());

        // 有生产工序的产品
        Set<String> productsWithOperation = new HashSet<>();
        for (Operation op : data.getOperations()) {
            for (OperationOutput oo : op.getOutputs()) {
                productsWithOperation.add(oo.getProductId());
            }
        }
        // 被 BOM 消耗的产品
        Set<String> productsConsumed = new HashSet<>();
        for (OperationInput input : data.getOperationInputs()) {
            productsConsumed.add(input.getInputProduct().getId());
        }

        // 有初始库存的产品
        Set<String> productsWithInitInv = new HashSet<>();
        for (InitialInventory inv : data.getInitialInventories()) {
            if (inv.getQuantity() > 0) {
                productsWithInitInv.add(inv.getProduct().getId());
            }
        }

        // 有在途供应的产品
        Set<String> productsWithInTransit = new HashSet<>();
        for (InTransitSupply its : data.getInTransitSupplies()) {
            if (its.getQuantity() > 0) {
                productsWithInTransit.add(its.getProduct().getId());
            }
        }

        // 被消耗但没有生产 = 纯原材料, 检查是否有初始库存或在途
        for (String prodId : productsConsumed) {
            if (!productsWithOperation.contains(prodId)) {
                // 是纯原材料
                if (!productsWithInitInv.contains(prodId) && !productsWithInTransit.contains(prodId)) {
                    warnings.add("原材料 " + prodId + " 被 BOM 消耗但没有初始库存和在途供应, "
                            + "第1周期可能无法满足消耗需求");
                }
            }
        }

        // 有销售需求的产品
        Set<String> productsWithSalesDemand = new HashSet<>();
        for (SalesDemand sd : data.getSalesDemands()) {
            productsWithSalesDemand.add(sd.getProduct().getId());
        }
        for (String prodId : productsWithSalesDemand) {
            if (!productsWithOperation.contains(prodId)) {
                errors.add("产品 " + prodId + " 有销售需求但没有生产工序");
            }
        }
    }

    // ==================== 8. 孤立产品 ====================

    private void checkOrphanProducts() {
        // 有销售需求的产品
        Set<String> productsWithDemand = new HashSet<>();
        for (SalesDemand sd : data.getSalesDemands()) {
            productsWithDemand.add(sd.getProduct().getId());
        }

        // 有生产工序的产品
        Set<String> productsWithOperation = new HashSet<>();
        for (Operation op : data.getOperations()) {
            for (OperationOutput oo : op.getOutputs()) {
                productsWithOperation.add(oo.getProductId());
            }
        }

        // 在 BOM 中作为输入的产品
        Set<String> productsInBom = new HashSet<>();
        for (OperationInput input : data.getOperationInputs()) {
            productsInBom.add(input.getInputProduct().getId());
        }

        for (Product prod : data.getProducts()) {
            String pid = prod.getId();
            boolean hasDemand = productsWithDemand.contains(pid);
            boolean hasOperation = productsWithOperation.contains(pid);
            boolean inBom = productsInBom.contains(pid);

            // 既无需求、无生产、也不在 BOM 中 → 完全孤立
            if (!hasDemand && !hasOperation && !inBom) {
                warnings.add("产品 " + pid + " 是孤立产品: 无销售需求, 无生产工序, 无 BOM 角色");
            }
        }
    }

    // ==================== 访问检查结果 ====================

    public List<String> getErrors() { return Collections.unmodifiableList(errors); }
    public List<String> getWarnings() { return Collections.unmodifiableList(warnings); }
    public boolean hasErrors() { return !errors.isEmpty(); }
    public boolean hasWarnings() { return !warnings.isEmpty(); }
}