package com.aps.macroplanner.data;

import java.util.*;
import java.time.LocalDate;

/**
 * 测试数据构建器 — 构建一个 3 级 BOM 的供应链优化实例
 *
 * <h3>BOM 结构</h3>
 * <pre>
 * Level 0 (成品):
 *   P1 ──消耗──→ S1×2.0 + R1×3.0
 *   P2 ──消耗──→ S1×1.0
 *
 * Level 1 (半成品):
 *   S1 ──消耗──→ R2×2.0     (被 P1×2.0 + P2×1.0 消耗)
 *
 * Level 2 (原材料):
 *   R1 ──无消耗──            (被 P1×3.0 消耗)
 *   R2 ──无消耗──            (被 S1×2.0 消耗)
 * </pre>
 *
 * <h3>物料流</h3>
 * <pre>
 *   OP_R1 → R1@SP_R1 ──────────────────→ OP_P1 消耗 ×3.0
 *   OP_R2 → R2@SP_R2 ──→ OP_S1 消耗 ×2.0
 *   OP_S1 → S1@SP_S1 ──→ OP_P1 消耗 ×2.0 + OP_P2 消耗 ×1.0
 *   OP_P1 → P1@SP_P1  ──→ 外部销售
 *   OP_P2 → P2@SP_P2  ──→ 外部销售
 * </pre>
 */
public class TestDataBuilder {

    // ==================== 基础数据 ====================
    /** 周期列表 (子类可访问) */
    protected final List<Period> periods = new ArrayList<>();
    /** 产品列表 (子类可访问) */
    protected final List<Product> products = new ArrayList<>();
    /** 库存点列表 (子类可访问) */
    protected final List<StockingPoint> stockingPoints = new ArrayList<>();
    /** 产品→库存点映射表 (一个产品可在多个库存点存储) */
    protected final List<ProductSpMapping> productSpMappings = new ArrayList<>();
    /** 操作列表 (子类可访问) */
    protected final List<Operation> operations = new ArrayList<>();
    /** 操作输入/BOM列表 (子类可访问) */
    protected final List<OperationInput> operationInputs = new ArrayList<>();
    /** 工艺路线列表 (子类可访问) — 用于工序间产量一致性约束 */
    protected final List<Routing> routings = new ArrayList<>();
    /** 单元周期列表 (子类可访问) */
    protected final List<UnitPeriod> unitPeriods = new ArrayList<>();
    /** 销售需求列表 (子类可访问) */
    protected final List<SalesDemand> salesDemands = new ArrayList<>();
    /** 库存规格列表 (子类可访问) */
    protected final List<InventorySpec> inventorySpecs = new ArrayList<>();
    /** 供应规格列表 (子类可访问) */
    protected final List<SupplySpec> supplySpecs = new ArrayList<>();
    /** 在途供应列表 (供应商已发货, 将在未来周期到货的固定量) */
    protected final List<InTransitSupply> inTransitSupplies = new ArrayList<>();
    /** KPI 权重 (子类可访问) */
    protected KPIWeights kpiWeights;

    /** 初始库存: key = productId_spId, value = 初始库存量 (子类可访问) */
    /** 初始库存列表 (子类可访问) */
    protected final List<InitialInventory> initialInventories = new ArrayList<>();

    /** 公开构造器 — 自动调用 build() 初始化默认测试数据 */
    public TestDataBuilder() {
        this(false);
    }

    /**
     * 受保护构造器 — 供子类使用。
     * @param skipAutoBuild 如果为 true, 不自动调用 build(), 由子类负责初始化
     */
    protected TestDataBuilder(boolean skipAutoBuild) {
        if (!skipAutoBuild) {
            build();
        }
    }

    /** 初始化默认测试数据 (3级BOM, 5产品, 5操作, 4周期) */
    protected void build() {
        // === 周期定义 (每天一个周期, 带日期) ===
        LocalDate baseDate = LocalDate.of(2026, 1, 5);
        Period p1 = new Period(0, "P1", baseDate);                // 2026-01-05
        Period p2 = new Period(1, "P2", baseDate.plusDays(1));    // 2026-01-06
        Period p3 = new Period(2, "P3", baseDate.plusDays(2));    // 2026-01-07
        periods.addAll(Arrays.asList(p1, p2, p3));

        // === 产品定义 (5个: 2成品 + 1半成品 + 2原材料) ===
        Product prodP1 = new Product("P1", "Product-1-Finished");
        Product prodP2 = new Product("P2", "Product-2-Finished");
        Product prodS1 = new Product("S1", "SemiProduct-1");
        Product prodR1 = new Product("R1", "RawMaterial-1");
        Product prodR2 = new Product("R2", "RawMaterial-2");
        products.addAll(Arrays.asList(prodP1, prodP2, prodS1, prodR1, prodR2));

        // === 库存点定义 (每个产品一个库存点) ===
        // === 库存点定义 (3个: 成品库, 半成品库, 原材料库) ===
        StockingPoint spFG = new StockingPoint("WH_FG", "成品库");
        StockingPoint spSemi = new StockingPoint("WH_Semi", "半成品库");
        StockingPoint spRM = new StockingPoint("WH_RM", "原材料库");
        stockingPoints.addAll(Arrays.asList(spFG, spSemi, spRM));

        // === 构建产品→库存点映射表 ===

        productSpMappings.add(new ProductSpMapping(prodP1, spFG));
        productSpMappings.add(new ProductSpMapping(prodP2, spFG));
        productSpMappings.add(new ProductSpMapping(prodS1, spSemi));
        productSpMappings.add(new ProductSpMapping(prodR1, spRM));
        productSpMappings.add(new ProductSpMapping(prodR2, spRM));

        // === 操作定义 (7个操作, 分属 7 个生产单元) ===
        // 同一操作可在多个 Unit 执行, 体现多供应商/多产线场景
        // 成品 P1: 可在 L1(快线 0.1h/件) 或 L3(慢线 0.125h/件) 生产
        Operation opP1 = new Operation("OP_P1", "Make-P1",
                Arrays.asList(
                        new UnitOperation("L1", 0.1, false, 100.0, 1.0),
                        new UnitOperation("L3", 0.125, false, 100.0, 1.0)),
                new OperationOutput(prodP1, spFG), 1.0);
        // 成品 P2: 可在 L1(0.125h/件) 或 L3(0.167h/件) 生产
        Operation opP2 = new Operation("OP_P2", "Make-P2",
                Arrays.asList(
                        new UnitOperation("L1", 0.125, false, 80.0, 1.0),
                        new UnitOperation("L3", 0.167, false, 80.0, 1.0)),
                new OperationOutput(prodP2, spFG), 1.0);
        // 半成品 S1: 只在 L2 生产
        Operation opS1 = new Operation("OP_S1", "Make-S1",
                Arrays.asList(
                        new UnitOperation("L2", 0.0333, false, 120.0, 1.0)),
                new OperationOutput(prodS1, spSemi), 1.0);
        // 供应商 R1(主): SuA, 0.12h/件, 批次200, leadTime=1天
        Operation opR1 = new Operation("OP_R1", "Procure-R1",
                Arrays.asList(
                        new UnitOperation("SuA", 0.12, true, 200.0, 1.0)),
                new OperationOutput(prodR1, spRM), 1.0, 1);
        // 供应商 R1(备): SuB, 0.24h/件, 批次150, leadTime=2天
        Operation opR1Alt = new Operation("OP_R1_Alt", "Procure-R1-Alt",
                Arrays.asList(
                        new UnitOperation("SuB", 0.24, true, 150.0, 1.0)),
                new OperationOutput(prodR1, spRM), 1.0, 2);
        // 供应商 R2(主): Su1, 0.16h/件, 批次200, leadTime=1天
        Operation opR2 = new Operation("OP_R2", "Procure-R2",
                Arrays.asList(
                        new UnitOperation("Su1", 0.16, true, 200.0, 1.0)),
                new OperationOutput(prodR2, spRM), 1.0, 1);
        // 供应商 R2(备): Su2, 0.30h/件, 批次150, leadTime=3天
        Operation opR2Alt = new Operation("OP_R2_Alt", "Procure-R2-Alt",
                Arrays.asList(
                        new UnitOperation("Su2", 0.3, true, 150.0, 1.0)),
                new OperationOutput(prodR2, spRM), 1.0, 3);
        operations.addAll(Arrays.asList(opP1, opP2, opS1, opR1, opR1Alt, opR2, opR2Alt));

        // === BOM / 操作输入物料定义 ===
        // P1 消耗: S1×2.0 + R1×3.0
        operationInputs.add(new OperationInput(opP1, prodS1, spSemi, 2.0));
        operationInputs.add(new OperationInput(opP1, prodR1, spRM, 3.0));
        // P2 消耗: S1×1.0
        operationInputs.add(new OperationInput(opP2, prodS1, spSemi, 1.0));
        // S1 消耗: R2×2.0
        operationInputs.add(new OperationInput(opS1, prodR2, spRM, 2.0));

        // === 单元周期定义 (7 个生产单元, 不同可用工时) ===
        // L1: 成品线, 最大可用工时 10h/周期
        for (Period p : periods) {
            unitPeriods.add(new UnitPeriod("L1", p, 0.0, 10.0, false));
        }
        for (Period p : periods) {
            unitPeriods.add(new UnitPeriod("L2", p, 0.0, 10.0, false));
        }
        // Unit_Semi: 半成品线, 最大产能 10h/周期
        for (Period p : periods) {
            unitPeriods.add(new UnitPeriod("L3", p, 0.0, 10.0, false));
        }
        // 原材料供应商: 无限产能 (只要有需求就能供应)
        for (Period p : periods) {
            unitPeriods.add(UnitPeriod.unlimited("SuA", p));
        }
        for (Period p : periods) {
            unitPeriods.add(UnitPeriod.unlimited("SuB", p));
        }
        for (Period p : periods) {
            unitPeriods.add(UnitPeriod.unlimited("Su1", p));
        }
        for (Period p : periods) {
            unitPeriods.add(UnitPeriod.unlimited("Su2", p));
        }

        // === 初始库存 ===
        initialInventories.add(new InitialInventory(prodP1, spFG, 20.0));
        initialInventories.add(new InitialInventory(prodP2, spFG, 10));
        initialInventories.add(new InitialInventory(prodS1, spSemi, 50));
        initialInventories.add(new InitialInventory(prodR1, spRM, 200));
        initialInventories.add(new InitialInventory(prodR2, spRM, 100));

        // === 在途供应 (原材料供应商已发货, 将在未来日期到货) ===
        // R1: 主供应商在 2026-01-07 到货 100 件
        // inTransitSupplies.add(new InTransitSupply(prodR1, spR, baseDate.plusDays(2), 100.0));
        // R1: 备用供应商在 2026-01-08 到货 50 件
        // inTransitSupplies.add(new InTransitSupply(prodR1, spR, baseDate.plusDays(3), 50.0));
        // R2: 主供应商在 2026-01-06 到货 80 件
        // inTransitSupplies.add(new InTransitSupply(prodR2, spR, baseDate.plusDays(1), 80.0));
        // R2: 备用供应商在 2026-01-07 到货 60 件
        //  inTransitSupplies.add(new InTransitSupply(prodR2, spR, baseDate.plusDays(2), 60.0));

        // === 销售需求 (每个产品每周期都有外部需求) ===
        // P1: 成品需求 50/周期
        salesDemands.add(new SalesDemand(prodP1, spFG, p1, 50.0, 1.0));
        salesDemands.add(new SalesDemand(prodP1, spFG, p2, 60.0, 1.0));
        salesDemands.add(new SalesDemand(prodP1, spFG, p3, 40.0, 1.0));

        salesDemands.add(new SalesDemand(prodP2, spFG, p1, 30.0, 1.0));
        salesDemands.add(new SalesDemand(prodP2, spFG, p2, 40.0, 1.0));
        salesDemands.add(new SalesDemand(prodP2, spFG, p3, 50.0, 1.0));


        // 注意: 原材料 R1, R2 不设外部销售需求, 仅用于内部 BOM 消耗

        // === 库存规格 (每个产品/库存点/周期) ===
        // P1: target 100, min 20, max 200
        for (Period p : periods) {
            inventorySpecs.add(new InventorySpec(prodP1, spFG, p,
                    20, 5, 150, true, true, true));
        }
        // P2: target 80, min 15, max 150
        for (Period p : periods) {
            inventorySpecs.add(new InventorySpec(prodP2, spFG, p,
                    15, 3, 120.0, true, true, true));
        }
        // S1: target 60, min 10, max 120
        for (Period p : periods) {
            inventorySpecs.add(new InventorySpec(prodS1, spSemi, p,
                    30.0, 10.0, 200.0, true, true, true));
        }
        // R1: target 80.0, min 500.0, max 500.0
        for (Period p : periods) {
            inventorySpecs.add(new InventorySpec(prodR1, spRM, p,
                    80.0, 50.0, 500.0, true, true, true));
        }
        // R2: target 40.0, min 20.0, max 400.0
        for (Period p : periods) {
            inventorySpecs.add(new InventorySpec(prodR2, spRM, p,
                    40.0, 20.0, 400.0, true, true, true));
        }

        // === 供应规格 (每个产品一个供应规格, 可关联多个操作/多产线) ===
        supplySpecs.add(new SupplySpec("Supply-P1", 200.0, 20.0, 400.0,
                false, Arrays.asList(opP1)));
        supplySpecs.add(new SupplySpec("Supply-P2", 160.0, 10.0, 300.0,
                false, Arrays.asList(opP2)));
        // S1: 主产线 + 备用产线都生产 S1
        supplySpecs.add(new SupplySpec("Supply-S1", 240.0, 30.0, 400.0,
                false, Arrays.asList(opS1)));
        // R1: 主供应商 + 备用供应商
        supplySpecs.add(new SupplySpec("Supply-R1", 100.0, 50.0, 200.0,
                false, Arrays.asList(opR1)));

        // R1: 备用供应商
        supplySpecs.add(new SupplySpec("Supply-R1-Alt", 70.0, 30.0, 100.0,
                false, Arrays.asList( opR1Alt)));
        // R2: 主供应商 + 备用供应商
        supplySpecs.add(new SupplySpec("Supply-R2", 80.0, 60.0, 150.0,
                false, Arrays.asList(opR2)));

        supplySpecs.add(new SupplySpec("Supply-R2-Alt", 40.0, 20.0, 80.0,
                false, Arrays.asList( opR2Alt)));

        // === KPI 权重 ===
        kpiWeights = new KPIWeights(
                100.0,   // fulfillmentWeight
                10.0,    // lotSizeWeight
                5.0,     // maxInventoryLevelWeight
                5.0,     // minInventoryLevelWeight
                8.0,     // targetInventoryLevelWeight
                20.0,    // unitCapacityWeight
                8.0,     // supplyTargetWeight
                5.0,     // minSupplyWeight
                5.0,     // maxSupplyWeight
                1.0,     // salesDemandPriorityWeight
                20.0,    // postponementPenaltyWeight
                5.0      // processMaxQuantityWeight
        );
    }

    // ==================== Getters ====================

    public List<Period> getPeriods() { return periods; }
    public List<Product> getProducts() { return products; }
    public List<StockingPoint> getStockingPoints() { return stockingPoints; }
    /**
     * 获取指定产品的所有库存点。
     * 支持一个产品存放在多个库存点的场景。
     * @return 库存点列表, 不存在则返回空列表
     */
    public List<StockingPoint> getStockingPointsForProduct(String productId) {
        List<StockingPoint> result = new ArrayList<>();
        for (ProductSpMapping m : productSpMappings) {
            if (m.getProduct().getId().equals(productId)) {
                result.add(m.getStockingPoint());
            }
        }
        return result;
    }
    public List<Operation> getOperations() { return operations; }
    public List<OperationInput> getOperationInputs() { return operationInputs; }
    public List<Routing> getRoutings() { return routings; }
    public List<UnitPeriod> getUnitPeriods() { return unitPeriods; }
    public List<SalesDemand> getSalesDemands() { return salesDemands; }
    public List<InventorySpec> getInventorySpecs() { return inventorySpecs; }
    public List<SupplySpec> getSupplySpecs() { return supplySpecs; }
    public List<InTransitSupply> getInTransitSupplies() { return inTransitSupplies; }

    public List<InitialInventory> getInitialInventories() { return initialInventories; }

    public KPIWeights getKpiWeights() { return kpiWeights; }

    /**
     * 获取初始库存量
     * @param productId 产品ID
     * @param spId 库存点ID
     * @return 初始库存量，不存在则返回 0
     */
    public double getInitialInventory(String productId, String spId) {
        for (InitialInventory inv : initialInventories) {
            if (inv.getProduct().getId().equals(productId)
                    && inv.getStockingPoint().getId().equals(spId)) {
                return inv.getQuantity();
            }
        }
        return 0.0;
    }

    /**
     * 获取指定产品/库存点/周期的销售需求
     */
    public List<SalesDemand> getSalesDemandsFor(Product product, StockingPoint sp, Period period) {
        List<SalesDemand> result = new ArrayList<>();
        for (SalesDemand sd : salesDemands) {
            if (sd.getProduct().getId().equals(product.getId())
                    && sd.getStockingPoint().getId().equals(sp.getId())
                    && sd.getPeriod().getIndex() == period.getIndex()) {
                result.add(sd);
            }
        }
        return result;
    }

    /**
     * 获取指定产品/库存点/周期的库存规格
     */
    public InventorySpec getInventorySpecFor(Product product, StockingPoint sp, Period period) {
        for (InventorySpec spec : inventorySpecs) {
            if (spec.getProduct().getId().equals(product.getId())
                    && spec.getStockingPoint().getId().equals(sp.getId())
                    && spec.getPeriod().getIndex() == period.getIndex()) {
                return spec;
            }
        }
        return null;
    }

    /**
     * 获取指定单元/周期的 UnitPeriod
     */
    public UnitPeriod getUnitPeriod(String unitId, Period period) {
        for (UnitPeriod up : unitPeriods) {
            if (up.getUnitId().equals(unitId) && up.getPeriod().getIndex() == period.getIndex()) {
                return up;
            }
        }
        return null;
    }

    /**
     * 获取上一个周期
     */
    public Period getPreviousPeriod(Period period) {
        if (period.getIndex() == 0) return null;
        return periods.get(period.getIndex() - 1);
    }

    /**
     * 根据日期查找包含该日期的周期。
     * 遍历所有周期, 返回 startDate ≤ date < startDate+durationInDays 的周期。
     * @return 包含该日期的周期, 若所有周期都无 startDate 或找不到则返回 null
     */
    public Period getPeriodByDate(LocalDate date) {
        if (date == null) return null;
        for (Period p : periods) {
            if (p.contains(date)) return p;
        }
        return null;
    }

    /**
     * 根据参考周期和偏移天数查找目标周期。
     * 用于 leadTime 计算: 参
     * 考周期的 startDate - daysBack 对应哪个周期？
     * @param reference 参考周期
     * @param daysBack 向前偏移天数 (≥0)
     * @return 包含 reference.startDate - daysBack 的周期, 无对应周期返回 null
     */
    public Period getPeriodOffsetByDays(Period reference, int daysBack) {
        if (reference == null || reference.getStartDate() == null) return null;
        LocalDate targetDate = reference.getStartDate().minusDays(daysBack);
        return getPeriodByDate(targetDate);
    }
}