package com.huimv.employment.service.draft;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.huimv.employment.common.exception.BizException;
import com.huimv.employment.common.exception.ErrorCode;
import com.huimv.employment.dao.entity.FeDraftWorkItem;
import com.huimv.employment.dao.entity.FeEmploymentDraft;
import com.huimv.employment.dao.entity.FeServicePlan;
import com.huimv.employment.dao.entity.FeServicePlanFeeRule;
import com.huimv.employment.dao.mapper.FeServicePlanFeeRuleMapper;
import com.huimv.employment.dao.mapper.FeServicePlanMapper;
import com.huimv.employment.service.draft.dto.McpDraftCalculateResponse;
import com.huimv.employment.service.draft.dto.McpDraftGroupBreakdownItem;
import com.huimv.employment.service.draft.dto.McpDraftSchemeCostResponse;
import com.huimv.employment.service.draft.dto.McpDraftSchemeResponse;
import com.huimv.employment.service.draft.dto.McpDraftSchemeWorkerIncomeResponse;
import com.huimv.employment.service.draft.dto.McpDraftSettlementCycleBreakdown;
import com.huimv.employment.service.draft.dto.McpDraftSummaryGroupResponse;
import com.huimv.employment.service.draft.dto.McpDraftSummaryResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 草稿成本测算引擎（v4 §6.4.4）。
 * <p>从 {@code fe_service_plan} + {@code fe_service_plan_fee_rule} 读取方案与费率。</p>
 * <p>结算方式语义：
 * <ul>
 *   <li>{@code daily}：unit_amount=日薪，work_days=天数，人期=人天</li>
 *   <li>{@code weekly}：unit_amount=周薪，work_days=周数，人期=人周</li>
 *   <li>{@code monthly}：unit_amount=月薪，work_days=月数，人期=人月</li>
 * </ul>
 * 劳务费 = unit_amount × worker_count × work_days（或显式 total_amount）。
 * 企业总支出 = 各费用分项之和；人均成本/员工到手按「人期」折算。</p>
 */
@Component
public class DraftCostCalculationEngine {

    private static final String CALC_VERSION = "v2";
    private static final String STATUS_ACTIVE = "active";
    private static final String PLAN_CURRENT = "current";

    private static final String FEE_LABOR = "labor_fee";
    private static final String FEE_SERVICE = "service_fee";
    private static final String FEE_INSURANCE = "insurance";
    private static final String FEE_TAX = "tax_withhold";

    private static final String CALC_RATE = "rate";
    private static final String CALC_FORMULA = "formula";
    private static final String CALC_FIXED = "fixed";

    private static final String MODE_DAILY = "daily";
    private static final String MODE_WEEKLY = "weekly";
    private static final String MODE_MONTHLY = "monthly";

    /** 固定额按「每人每结算单位」计价（日/周/月） */
    private static final String EXPR_PER_PERSON_PERIOD = "per_person_period";

    /** 个人侧预扣（税费从员工到手扣除）；其余 tax_mode 视为企业侧代征/全额缴纳 */
    private static final String TAX_MODE_LABOR_REMUNERATION = "labor_remuneration";

    private static final Pattern FORMULA_MUL_DIV = Pattern.compile(
            "(?i)^(gross|labor|company_pay)\\s*\\*\\s*(\\d+(?:\\.\\d+)?)\\s*/\\s*(\\d+(?:\\.\\d+)?)$");
    private static final Pattern FORMULA_MUL = Pattern.compile(
            "(?i)^(gross|labor|company_pay)\\s*\\*\\s*(\\d+(?:\\.\\d+)?)$");

    private final FeServicePlanMapper feServicePlanMapper;
    private final FeServicePlanFeeRuleMapper feServicePlanFeeRuleMapper;
    private final ObjectMapper objectMapper;

    public DraftCostCalculationEngine(FeServicePlanMapper feServicePlanMapper,
                                      FeServicePlanFeeRuleMapper feServicePlanFeeRuleMapper,
                                      ObjectMapper objectMapper) {
        this.feServicePlanMapper = feServicePlanMapper;
        this.feServicePlanFeeRuleMapper = feServicePlanFeeRuleMapper;
        this.objectMapper = objectMapper;
    }

    public List<CalculatedScheme> calculate(FeEmploymentDraft draft, List<FeDraftWorkItem> items) {
        LaborContext ctx = resolveLaborContext(items);
        return calculate(ctx);
    }

    public List<CalculatedScheme> calculate(BigDecimal laborTotal, int workerCount) {
        LaborContext ctx = new LaborContext(
                scaleMoney(laborTotal),
                workerCount > 0 ? workerCount : 1,
                BigDecimal.valueOf(workerCount > 0 ? workerCount : 1),
                MODE_DAILY,
                "day");
        return calculate(ctx);
    }

    private List<CalculatedScheme> calculate(LaborContext ctx) {
        if (ctx.laborTotal == null || ctx.laborTotal.compareTo(BigDecimal.ZERO) <= 0) {
            throw new BizException(ErrorCode.BAD_REQUEST, "草稿劳务费总额为空，无法测算（请补全 worker_groups.total_amount）");
        }

        List<FeServicePlan> plans = listActiveComparisonPlans();
        if (plans.isEmpty()) {
            throw new BizException(ErrorCode.BAD_REQUEST, "未配置可用用工方案，请先初始化 fe_service_plan");
        }

        Map<Long, List<FeServicePlanFeeRule>> rulesByPlan = loadRulesByPlanIds(plans);
        List<CalculatedScheme> result = new ArrayList<>(plans.size());
        boolean recommendedAssigned = false;
        for (FeServicePlan plan : plans) {
            boolean recommended = !recommendedAssigned;
            recommendedAssigned = true;
            result.add(buildScheme(plan, rulesByPlan.getOrDefault(plan.getId(), Collections.emptyList()),
                    ctx, recommended));
        }
        return result;
    }

    public McpDraftSummaryResponse buildDraftSummary(FeEmploymentDraft draft, List<FeDraftWorkItem> items) {
        LaborContext ctx = resolveLaborContext(items);
        McpDraftSummaryResponse summary = new McpDraftSummaryResponse();
        summary.setWorkerSource(draft != null ? draft.getWorkerSource() : null);
        summary.setTotalWorkerCount(ctx.workerCount);
        summary.setTotalAmount(formatMoney(ctx.laborTotal));

        List<McpDraftSummaryGroupResponse> groups = new ArrayList<>();
        if (items != null) {
            for (FeDraftWorkItem item : items) {
                McpDraftSummaryGroupResponse g = new McpDraftSummaryGroupResponse();
                g.setGroupId(item.getGroupNo());
                g.setJobType(item.getJobType());
                if (StringUtils.hasText(item.getWorkLocation())) {
                    g.setWorkLocation(item.getWorkLocation().trim());
                }
                g.setWorkerCount(item.getWorkerCount());
                g.setWorkDays(item.getWorkDays());
                g.setUnitAmount(item.getUnitAmount());
                g.setAmount(formatMoney(resolveItemLaborAmount(item)));
                g.setSettlementCycle(normalizeSettlementMode(item.getSettlementCycle()));
                groups.add(g);
            }
        }
        summary.setGroups(groups);
        return summary;
    }

    /** 方案编码是否为库中 active 对比方案（非 current）。 */
    public boolean isAllowedSchemeCode(String schemeCode) {
        if (!StringUtils.hasText(schemeCode)) {
            return false;
        }
        String code = schemeCode.trim();
        return listActiveComparisonPlans().stream()
                .anyMatch(p -> code.equalsIgnoreCase(p.getPlanCode()));
    }

    public McpDraftCalculateResponse toApiResponse(List<CalculatedScheme> schemes) {
        return toApiResponse(schemes, null);
    }

    public McpDraftCalculateResponse toApiResponse(List<CalculatedScheme> schemes, List<FeDraftWorkItem> items) {
        McpDraftCalculateResponse response = new McpDraftCalculateResponse();
        for (CalculatedScheme scheme : schemes) {
            response.getSchemes().add(toSchemeResponse(scheme, items));
        }
        return response;
    }

    public String calcVersion() {
        return CALC_VERSION;
    }

    private LaborContext resolveLaborContext(List<FeDraftWorkItem> items) {
        if (items == null || items.isEmpty()) {
            throw new BizException(ErrorCode.BAD_REQUEST, "草稿无用工明细，无法测算");
        }
        BigDecimal laborTotal = BigDecimal.ZERO;
        BigDecimal periodUnits = BigDecimal.ZERO;
        int workerCount = 0;
        boolean hasAmount = false;
        String settlementMode = null;

        for (FeDraftWorkItem item : items) {
            BigDecimal labor = resolveItemLaborAmount(item);
            if (labor.compareTo(BigDecimal.ZERO) > 0) {
                laborTotal = laborTotal.add(labor);
                hasAmount = true;
            }
            if (item.getWorkerCount() != null && item.getWorkerCount() > 0) {
                workerCount += item.getWorkerCount();
            }
            BigDecimal units = resolveItemPeriodUnits(item);
            if (units.compareTo(BigDecimal.ZERO) > 0) {
                periodUnits = periodUnits.add(units);
            }
            if (settlementMode == null) {
                String mode = normalizeSettlementMode(item.getSettlementCycle());
                if (mode != null) {
                    settlementMode = mode;
                }
            }
        }
        if (!hasAmount || laborTotal.compareTo(BigDecimal.ZERO) <= 0) {
            throw new BizException(ErrorCode.BAD_REQUEST, "草稿劳务费总额为空，无法测算（需各组 total_amount）");
        }
        if (workerCount <= 0) {
            workerCount = 1;
        }
        if (periodUnits.compareTo(BigDecimal.ZERO) <= 0) {
            periodUnits = BigDecimal.valueOf(workerCount);
        }
        if (settlementMode == null) {
            settlementMode = MODE_DAILY;
        }
        return new LaborContext(
                scaleMoney(laborTotal),
                workerCount,
                periodUnits,
                settlementMode,
                toPeriodUnit(settlementMode));
    }

    private static BigDecimal resolveItemPeriodUnits(FeDraftWorkItem item) {
        if (item == null) {
            return BigDecimal.ZERO;
        }
        int workers = item.getWorkerCount() != null && item.getWorkerCount() > 0 ? item.getWorkerCount() : 0;
        if (workers <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal periods = item.getWorkDays();
        if (periods == null || periods.compareTo(BigDecimal.ZERO) <= 0) {
            periods = BigDecimal.ONE;
        }
        return BigDecimal.valueOf(workers).multiply(periods);
    }

    private static String normalizeSettlementMode(String raw) {
        if (!StringUtils.hasText(raw)) {
            return null;
        }
        String mode = raw.trim().toLowerCase(Locale.ROOT);
        if (MODE_DAILY.equals(mode) || MODE_WEEKLY.equals(mode) || MODE_MONTHLY.equals(mode)) {
            return mode;
        }
        return null;
    }

    private static String toPeriodUnit(String settlementMode) {
        if (MODE_WEEKLY.equals(settlementMode)) {
            return "week";
        }
        if (MODE_MONTHLY.equals(settlementMode)) {
            return "month";
        }
        return "day";
    }

    private List<FeServicePlan> listActiveComparisonPlans() {
        LocalDate today = LocalDate.now();
        List<FeServicePlan> plans = feServicePlanMapper.selectList(new LambdaQueryWrapper<FeServicePlan>()
                .eq(FeServicePlan::getStatus, STATUS_ACTIVE)
                .ne(FeServicePlan::getPlanCode, PLAN_CURRENT)
                .orderByAsc(FeServicePlan::getSortOrder)
                .orderByAsc(FeServicePlan::getId));
        List<FeServicePlan> effective = new ArrayList<>();
        for (FeServicePlan plan : plans) {
            if (plan.getEffectiveFrom() != null && today.isBefore(plan.getEffectiveFrom())) {
                continue;
            }
            if (plan.getEffectiveTo() != null && today.isAfter(plan.getEffectiveTo())) {
                continue;
            }
            effective.add(plan);
        }
        return effective;
    }

    private Map<Long, List<FeServicePlanFeeRule>> loadRulesByPlanIds(List<FeServicePlan> plans) {
        List<Long> planIds = new ArrayList<>(plans.size());
        for (FeServicePlan plan : plans) {
            planIds.add(plan.getId());
        }
        List<FeServicePlanFeeRule> rules = feServicePlanFeeRuleMapper.selectList(
                new LambdaQueryWrapper<FeServicePlanFeeRule>()
                        .in(FeServicePlanFeeRule::getPlanId, planIds)
                        .orderByAsc(FeServicePlanFeeRule::getSortOrder));
        Map<Long, List<FeServicePlanFeeRule>> map = new HashMap<>();
        for (FeServicePlanFeeRule rule : rules) {
            map.computeIfAbsent(rule.getPlanId(), k -> new ArrayList<>()).add(rule);
        }
        return map;
    }

    private CalculatedScheme buildScheme(FeServicePlan plan,
                                         List<FeServicePlanFeeRule> rules,
                                         LaborContext ctx,
                                         boolean recommended) {
        BigDecimal labor = ctx.laborTotal;
        List<FeServicePlanFeeRule> orderedRules = rules == null ? Collections.emptyList() : rules;

        Map<String, FeeLine> feeLines = new LinkedHashMap<>();
        if (orderedRules.isEmpty()) {
            feeLines.put(FEE_LABOR, new FeeLine(FEE_LABOR, "劳务费", labor, null));
            if (plan.getServiceFeeRate() != null) {
                BigDecimal service = scaleMoney(labor.multiply(plan.getServiceFeeRate()));
                feeLines.put(FEE_SERVICE, new FeeLine(FEE_SERVICE, "平台服务费", service, plan.getServiceFeeRate()));
            }
        } else {
            boolean hasLaborRule = false;
            for (FeServicePlanFeeRule rule : orderedRules) {
                if (FEE_LABOR.equalsIgnoreCase(rule.getFeeCode())) {
                    hasLaborRule = true;
                    break;
                }
            }
            if (!hasLaborRule) {
                feeLines.put(FEE_LABOR, new FeeLine(FEE_LABOR, "劳务费", labor, null));
            }
            for (FeServicePlanFeeRule rule : orderedRules) {
                String code = rule.getFeeCode() == null ? "" : rule.getFeeCode().trim().toLowerCase(Locale.ROOT);
                BigDecimal amount;
                BigDecimal rateShown = null;
                if (FEE_LABOR.equals(code)) {
                    amount = labor;
                } else {
                    amount = scaleMoney(calcFeeAmount(labor, rule, plan, ctx.periodUnits));
                    if (CALC_RATE.equalsIgnoreCase(trim(rule.getCalcType()))) {
                        rateShown = resolveRate(rule, plan);
                    } else if (isPerPersonPeriod(rule)) {
                        rateShown = rule.getDefaultRate();
                    }
                }
                String name = StringUtils.hasText(rule.getFeeName()) ? rule.getFeeName() : code;
                feeLines.put(code, new FeeLine(code, name, amount, rateShown));
            }
        }

        BigDecimal companyPay = amountOf(feeLines, FEE_LABOR, labor);
        BigDecimal serviceFee = amountOf(feeLines, FEE_SERVICE, null);
        BigDecimal insuranceFee = amountOf(feeLines, FEE_INSURANCE, null);
        BigDecimal taxFee = amountOf(feeLines, FEE_TAX, null);

        boolean taxOnWorker = TAX_MODE_LABOR_REMUNERATION.equalsIgnoreCase(trim(plan.getTaxMode()));
        BigDecimal workerTax = taxOnWorker && taxFee != null ? taxFee : BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        BigDecimal companyTax = !taxOnWorker && taxFee != null ? taxFee : null;

        // 企业总支出：汇总分项；个人预扣个税不计入企业支出
        BigDecimal totalCost = BigDecimal.ZERO;
        for (FeeLine line : feeLines.values()) {
            if (line.amount == null) {
                continue;
            }
            if (taxOnWorker && FEE_TAX.equalsIgnoreCase(line.feeCode)) {
                continue;
            }
            totalCost = totalCost.add(line.amount);
        }
        totalCost = scaleMoney(totalCost);

        BigDecimal net = scaleMoney(companyPay.subtract(workerTax));
        if (net.compareTo(BigDecimal.ZERO) < 0) {
            net = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }

        BigDecimal avgCostPerUnit = divideByUnits(totalCost, ctx.periodUnits);
        BigDecimal unitNet = divideByUnits(net, ctx.periodUnits);

        return new CalculatedScheme(
                plan.getId(),
                plan.getPlanCode(),
                plan.getPlanName(),
                parseTags(plan.getComplianceTags()),
                recommended,
                companyPay,
                serviceFee,
                insuranceFee,
                companyTax != null ? companyTax : BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP),
                totalCost,
                companyPay,
                workerTax,
                net,
                avgCostPerUnit,
                unitNet,
                ctx.periodUnit,
                ctx.settlementMode,
                ctx.periodUnits,
                ctx.workerCount,
                new ArrayList<>(feeLines.values()));
    }

    private static BigDecimal amountOf(Map<String, FeeLine> feeLines, String code, BigDecimal fallback) {
        FeeLine line = feeLines.get(code);
        if (line != null && line.amount != null) {
            return line.amount;
        }
        return fallback;
    }

    private BigDecimal calcFeeAmount(BigDecimal base,
                                     FeServicePlanFeeRule rule,
                                     FeServicePlan plan,
                                     BigDecimal periodUnits) {
        String calcType = trim(rule.getCalcType()).toLowerCase(Locale.ROOT);
        if (CALC_RATE.equals(calcType)) {
            BigDecimal rate = resolveRate(rule, plan);
            if (rate == null) {
                return BigDecimal.ZERO;
            }
            return base.multiply(rate);
        }
        if (CALC_FIXED.equals(calcType)) {
            BigDecimal fixed = rule.getDefaultRate() != null ? rule.getDefaultRate() : BigDecimal.ZERO;
            if (isPerPersonPeriod(rule)) {
                return fixed.multiply(periodUnits != null ? periodUnits : BigDecimal.ONE);
            }
            return fixed;
        }
        if (CALC_FORMULA.equals(calcType)) {
            return evalFormula(base, rule.getCalcExpression(), plan);
        }
        return BigDecimal.ZERO;
    }

    private static boolean isPerPersonPeriod(FeServicePlanFeeRule rule) {
        if (rule == null) {
            return false;
        }
        String expr = trim(rule.getCalcExpression()).toLowerCase(Locale.ROOT);
        return EXPR_PER_PERSON_PERIOD.equals(expr) || "period_unit".equals(expr);
    }

    private static BigDecimal resolveRate(FeServicePlanFeeRule rule, FeServicePlan plan) {
        if (rule.getDefaultRate() != null) {
            return rule.getDefaultRate();
        }
        if (plan.getServiceFeeRate() != null && FEE_SERVICE.equalsIgnoreCase(trim(rule.getFeeCode()))) {
            return plan.getServiceFeeRate();
        }
        return parsePlainRate(rule.getCalcExpression());
    }

    private static BigDecimal evalFormula(BigDecimal base, String expression, FeServicePlan plan) {
        if (!StringUtils.hasText(expression)) {
            return BigDecimal.ZERO;
        }
        String expr = expression.trim().replace(" ", "");
        if ("gross".equalsIgnoreCase(expr) || "labor".equalsIgnoreCase(expr)
                || "company_pay".equalsIgnoreCase(expr)) {
            return base;
        }
        if (expr.toLowerCase(Locale.ROOT).contains("service_fee_rate") && plan.getServiceFeeRate() != null) {
            return base.multiply(plan.getServiceFeeRate());
        }
        Matcher mulDiv = FORMULA_MUL_DIV.matcher(expr);
        if (mulDiv.matches()) {
            return base.multiply(new BigDecimal(mulDiv.group(2)))
                    .divide(new BigDecimal(mulDiv.group(3)), 8, RoundingMode.HALF_UP);
        }
        Matcher mul = FORMULA_MUL.matcher(expr);
        if (mul.matches()) {
            return base.multiply(new BigDecimal(mul.group(2)));
        }
        BigDecimal plain = parsePlainRate(expression);
        return plain != null ? base.multiply(plain) : BigDecimal.ZERO;
    }

    private static BigDecimal parsePlainRate(String text) {
        if (!StringUtils.hasText(text)) {
            return null;
        }
        try {
            return new BigDecimal(text.trim());
        } catch (NumberFormatException ex) {
            return null;
        }
    }

    private List<String> parseTags(String complianceTagsJson) {
        if (!StringUtils.hasText(complianceTagsJson)) {
            return Collections.emptyList();
        }
        try {
            List<String> tags = objectMapper.readValue(complianceTagsJson, new TypeReference<List<String>>() {
            });
            return tags != null ? tags : Collections.emptyList();
        } catch (Exception ex) {
            return Collections.emptyList();
        }
    }

    private McpDraftSchemeResponse toSchemeResponse(CalculatedScheme scheme, List<FeDraftWorkItem> items) {
        McpDraftSchemeResponse response = new McpDraftSchemeResponse();
        response.setCode(scheme.getCode());
        response.setTitle(scheme.getTitle());
        response.setTags(scheme.getTags());
        response.setRecommended(scheme.isRecommended());

        McpDraftSchemeCostResponse cost = new McpDraftSchemeCostResponse();
        cost.setTotalCost(formatMoney(scheme.getTotalCost()));
        cost.setLaborFee(formatMoney(scheme.getCompanyPay()));
        if (scheme.getServiceFee() != null) {
            cost.setServiceFee(formatMoney(scheme.getServiceFee()));
        }
        if (scheme.getInsuranceFee() != null) {
            cost.setInsuranceFee(formatMoney(scheme.getInsuranceFee()));
        }
        if (scheme.getTaxFee() != null && scheme.getTaxFee().compareTo(BigDecimal.ZERO) > 0) {
            cost.setTaxFee(formatMoney(scheme.getTaxFee()));
        }
        response.setCost(cost);

        McpDraftSchemeWorkerIncomeResponse income = new McpDraftSchemeWorkerIncomeResponse();
        income.setTax(formatMoney(scheme.getWorkerTax()));
        income.setNet(formatMoney(scheme.getNet()));
        response.setWorkerIncome(income);
        response.setBySettlementCycle(buildSettlementCycleBreakdown(scheme, items));
        response.setGroupBreakdown(buildGroupBreakdown(scheme, items));
        return response;
    }

    /**
     * 按 settlement_cycle 聚合：展示该结算方式下的人均成本、到手、税前与分项费用。
     */
    private List<McpDraftSettlementCycleBreakdown> buildSettlementCycleBreakdown(
            CalculatedScheme scheme, List<FeDraftWorkItem> items) {
        List<McpDraftSettlementCycleBreakdown> result = new ArrayList<>();
        if (items == null || items.isEmpty()) {
            return result;
        }

        Map<String, CycleBucket> buckets = new LinkedHashMap<>();
        // 固定顺序：日 → 周 → 月 → 其他
        buckets.put(MODE_DAILY, new CycleBucket());
        buckets.put(MODE_WEEKLY, new CycleBucket());
        buckets.put(MODE_MONTHLY, new CycleBucket());

        BigDecimal laborTotal = BigDecimal.ZERO;
        BigDecimal periodUnitsTotal = BigDecimal.ZERO;
        for (FeDraftWorkItem item : items) {
            String mode = normalizeSettlementMode(item.getSettlementCycle());
            if (mode == null) {
                mode = MODE_DAILY;
            }
            CycleBucket bucket = buckets.computeIfAbsent(mode, key -> new CycleBucket());
            BigDecimal labor = resolveItemLaborAmount(item);
            BigDecimal units = resolveItemPeriodUnits(item);
            bucket.labor = bucket.labor.add(labor);
            bucket.periodUnits = bucket.periodUnits.add(units);
            if (item.getWorkerCount() != null && item.getWorkerCount() > 0) {
                bucket.workerCount += item.getWorkerCount();
            }
            laborTotal = laborTotal.add(labor);
            periodUnitsTotal = periodUnitsTotal.add(units);
        }
        if (laborTotal.compareTo(BigDecimal.ZERO) <= 0) {
            return result;
        }

        BigDecimal serviceTotal = nullToZero(scheme.getServiceFee());
        BigDecimal insuranceTotal = nullToZero(scheme.getInsuranceFee());
        BigDecimal taxDisplayTotal = scheme.getTaxFee() != null && scheme.getTaxFee().compareTo(BigDecimal.ZERO) > 0
                ? scheme.getTaxFee()
                : nullToZero(scheme.getWorkerTax());
        BigDecimal netTotal = nullToZero(scheme.getNet());

        List<String> activeModes = new ArrayList<>();
        for (Map.Entry<String, CycleBucket> e : buckets.entrySet()) {
            if (e.getValue().labor.compareTo(BigDecimal.ZERO) > 0
                    || e.getValue().periodUnits.compareTo(BigDecimal.ZERO) > 0) {
                activeModes.add(e.getKey());
            }
        }

        BigDecimal allocatedService = BigDecimal.ZERO;
        BigDecimal allocatedInsurance = BigDecimal.ZERO;
        BigDecimal allocatedTax = BigDecimal.ZERO;
        BigDecimal allocatedNet = BigDecimal.ZERO;

        for (int i = 0; i < activeModes.size(); i++) {
            String mode = activeModes.get(i);
            CycleBucket bucket = buckets.get(mode);
            boolean last = i == activeModes.size() - 1;

            BigDecimal laborShare = bucket.labor;
            BigDecimal laborRatio = laborShare.divide(laborTotal, 8, RoundingMode.HALF_UP);
            BigDecimal unitsRatio = periodUnitsTotal.compareTo(BigDecimal.ZERO) > 0
                    ? bucket.periodUnits.divide(periodUnitsTotal, 8, RoundingMode.HALF_UP)
                    : laborRatio;

            BigDecimal laborFee = scaleMoney(laborShare);
            BigDecimal serviceFee;
            BigDecimal insuranceFee;
            BigDecimal taxFee;
            BigDecimal net;
            if (last) {
                serviceFee = scaleMoney(serviceTotal.subtract(allocatedService));
                insuranceFee = scaleMoney(insuranceTotal.subtract(allocatedInsurance));
                taxFee = scaleMoney(taxDisplayTotal.subtract(allocatedTax));
                net = scaleMoney(netTotal.subtract(allocatedNet));
            } else {
                serviceFee = scaleMoney(serviceTotal.multiply(laborRatio));
                insuranceFee = scaleMoney(insuranceTotal.multiply(unitsRatio));
                taxFee = scaleMoney(taxDisplayTotal.multiply(laborRatio));
                net = scaleMoney(netTotal.multiply(laborRatio));
                allocatedService = allocatedService.add(serviceFee);
                allocatedInsurance = allocatedInsurance.add(insuranceFee);
                allocatedTax = allocatedTax.add(taxFee);
                allocatedNet = allocatedNet.add(net);
            }

            // 企业侧税费才计入该结算方式总成本；个人预扣仅展示在 tax_fee
            BigDecimal companySideTax = scheme.getTaxFee() != null
                    && scheme.getTaxFee().compareTo(BigDecimal.ZERO) > 0
                    ? taxFee : BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
            BigDecimal totalCost = scaleMoney(laborFee.add(serviceFee).add(insuranceFee).add(companySideTax));

            McpDraftSettlementCycleBreakdown row = new McpDraftSettlementCycleBreakdown();
            row.setSettlementCycle(mode);
            row.setWorkerCount(bucket.workerCount);
            row.setPeriodUnits(bucket.periodUnits.stripTrailingZeros().toPlainString());
            row.setLaborFee(formatMoney(laborFee));
            if (serviceFee.compareTo(BigDecimal.ZERO) > 0) {
                row.setServiceFee(formatMoney(serviceFee));
            }
            if (insuranceFee.compareTo(BigDecimal.ZERO) > 0) {
                row.setInsuranceFee(formatMoney(insuranceFee));
            }
            if (taxFee.compareTo(BigDecimal.ZERO) > 0) {
                row.setTaxFee(formatMoney(taxFee));
            }
            row.setTotalCost(formatMoney(totalCost));
            row.setNet(formatMoney(net));
            row.setAvgCost(formatMoney(divideByUnits(totalCost, bucket.periodUnits)));
            row.setUnitGross(formatMoney(divideByUnits(laborFee, bucket.periodUnits)));
            row.setUnitNet(formatMoney(divideByUnits(net, bucket.periodUnits)));
            result.add(row);
        }
        return result;
    }

    /**
     * 按各组劳务费占比分摊 total_cost / net。
     */
    private List<McpDraftGroupBreakdownItem> buildGroupBreakdown(CalculatedScheme scheme,
                                                                List<FeDraftWorkItem> items) {
        List<McpDraftGroupBreakdownItem> breakdown = new ArrayList<>();
        if (items == null || items.isEmpty()) {
            return breakdown;
        }
        List<BigDecimal> amounts = new ArrayList<>(items.size());
        BigDecimal laborTotal = BigDecimal.ZERO;
        for (FeDraftWorkItem item : items) {
            BigDecimal amount = resolveItemLaborAmount(item);
            amounts.add(amount);
            laborTotal = laborTotal.add(amount);
        }
        if (laborTotal.compareTo(BigDecimal.ZERO) <= 0) {
            return breakdown;
        }

        BigDecimal totalCost = scheme.getTotalCost() != null ? scheme.getTotalCost() : BigDecimal.ZERO;
        BigDecimal netTotal = scheme.getNet() != null ? scheme.getNet() : BigDecimal.ZERO;
        BigDecimal allocatedCost = BigDecimal.ZERO;
        BigDecimal allocatedNet = BigDecimal.ZERO;

        for (int i = 0; i < items.size(); i++) {
            FeDraftWorkItem item = items.get(i);
            McpDraftGroupBreakdownItem row = new McpDraftGroupBreakdownItem();
            row.setJobType(item.getJobType());
            row.setCount(item.getWorkerCount() != null ? item.getWorkerCount() : 0);
            row.setSettlementCycle(normalizeSettlementMode(item.getSettlementCycle()));

            BigDecimal share = amounts.get(i);
            BigDecimal costShare;
            BigDecimal netShare;
            if (i == items.size() - 1) {
                costShare = scaleMoney(totalCost.subtract(allocatedCost));
                netShare = scaleMoney(netTotal.subtract(allocatedNet));
            } else {
                costShare = scaleMoney(totalCost.multiply(share).divide(laborTotal, 8, RoundingMode.HALF_UP));
                netShare = scaleMoney(netTotal.multiply(share).divide(laborTotal, 8, RoundingMode.HALF_UP));
                allocatedCost = allocatedCost.add(costShare);
                allocatedNet = allocatedNet.add(netShare);
            }
            row.setCost(formatMoney(costShare));
            row.setNetIncome(formatMoney(netShare));
            breakdown.add(row);
        }
        return breakdown;
    }

    private static BigDecimal nullToZero(BigDecimal value) {
        return value != null ? value : BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
    }

    private static final class CycleBucket {
        private BigDecimal labor = BigDecimal.ZERO;
        private BigDecimal periodUnits = BigDecimal.ZERO;
        private int workerCount;
    }

    private static BigDecimal resolveItemLaborAmount(FeDraftWorkItem item) {
        if (item == null) {
            return BigDecimal.ZERO;
        }
        if (item.getTotalAmount() != null && item.getTotalAmount().compareTo(BigDecimal.ZERO) > 0) {
            return item.getTotalAmount();
        }
        if (item.getUnitAmount() != null
                && item.getWorkerCount() != null && item.getWorkerCount() > 0
                && item.getWorkDays() != null && item.getWorkDays().compareTo(BigDecimal.ZERO) > 0) {
            return item.getUnitAmount()
                    .multiply(BigDecimal.valueOf(item.getWorkerCount()))
                    .multiply(item.getWorkDays());
        }
        return BigDecimal.ZERO;
    }

    private static BigDecimal divideByUnits(BigDecimal amount, BigDecimal units) {
        if (amount == null) {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        if (units == null || units.compareTo(BigDecimal.ZERO) <= 0) {
            return scaleMoney(amount);
        }
        return amount.divide(units, 2, RoundingMode.HALF_UP);
    }

    private static BigDecimal scaleMoney(BigDecimal value) {
        if (value == null) {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        return value.setScale(2, RoundingMode.HALF_UP);
    }

    private static String formatMoney(BigDecimal value) {
        return scaleMoney(value).toPlainString();
    }

    private static String trim(String value) {
        return value == null ? "" : value.trim();
    }

    private static final class LaborContext {
        private final BigDecimal laborTotal;
        private final int workerCount;
        private final BigDecimal periodUnits;
        private final String settlementMode;
        private final String periodUnit;

        private LaborContext(BigDecimal laborTotal,
                             int workerCount,
                             BigDecimal periodUnits,
                             String settlementMode,
                             String periodUnit) {
            this.laborTotal = laborTotal;
            this.workerCount = workerCount;
            this.periodUnits = periodUnits;
            this.settlementMode = settlementMode;
            this.periodUnit = periodUnit;
        }
    }

    static final class FeeLine {
        private final String feeCode;
        private final String feeName;
        private final BigDecimal amount;
        private final BigDecimal rate;

        FeeLine(String feeCode, String feeName, BigDecimal amount, BigDecimal rate) {
            this.feeCode = feeCode;
            this.feeName = feeName;
            this.amount = amount;
            this.rate = rate;
        }
    }

    /**
     * 测算结果内部模型，供快照落库与 API 转换共用。
     */
    public static final class CalculatedScheme {

        private final Long planId;
        private final String code;
        private final String title;
        private final List<String> tags;
        private final boolean recommended;
        private final BigDecimal companyPay;
        private final BigDecimal serviceFee;
        private final BigDecimal insuranceFee;
        private final BigDecimal taxFee;
        private final BigDecimal totalCost;
        private final BigDecimal gross;
        private final BigDecimal workerTax;
        private final BigDecimal net;
        private final BigDecimal avgCostPerUnit;
        private final BigDecimal unitNet;
        private final String periodUnit;
        private final String settlementMode;
        private final BigDecimal periodUnits;
        private final int workerCount;
        private final List<FeeLine> feeLines;

        public CalculatedScheme(Long planId,
                                String code,
                                String title,
                                List<String> tags,
                                boolean recommended,
                                BigDecimal companyPay,
                                BigDecimal serviceFee,
                                BigDecimal insuranceFee,
                                BigDecimal taxFee,
                                BigDecimal totalCost,
                                BigDecimal gross,
                                BigDecimal workerTax,
                                BigDecimal net,
                                BigDecimal avgCostPerUnit,
                                BigDecimal unitNet,
                                String periodUnit,
                                String settlementMode,
                                BigDecimal periodUnits,
                                int workerCount,
                                List<FeeLine> feeLines) {
            this.planId = planId;
            this.code = code;
            this.title = title;
            this.tags = tags;
            this.recommended = recommended;
            this.companyPay = companyPay;
            this.serviceFee = serviceFee;
            this.insuranceFee = insuranceFee;
            this.taxFee = taxFee;
            this.totalCost = totalCost;
            this.gross = gross;
            this.workerTax = workerTax;
            this.net = net;
            this.avgCostPerUnit = avgCostPerUnit;
            this.unitNet = unitNet;
            this.periodUnit = periodUnit;
            this.settlementMode = settlementMode;
            this.periodUnits = periodUnits;
            this.workerCount = workerCount;
            this.feeLines = feeLines != null ? feeLines : Collections.emptyList();
        }

        public Long getPlanId() {
            return planId;
        }

        public String getCode() {
            return code;
        }

        public String getTitle() {
            return title;
        }

        public List<String> getTags() {
            return tags;
        }

        public boolean isRecommended() {
            return recommended;
        }

        public BigDecimal getCompanyPay() {
            return companyPay;
        }

        public BigDecimal getServiceFee() {
            return serviceFee;
        }

        public BigDecimal getInsuranceFee() {
            return insuranceFee;
        }

        public BigDecimal getTaxFee() {
            return taxFee;
        }

        public BigDecimal getTotalCost() {
            return totalCost;
        }

        public BigDecimal getGross() {
            return gross;
        }

        public BigDecimal getTax() {
            return workerTax;
        }

        public BigDecimal getWorkerTax() {
            return workerTax;
        }

        public BigDecimal getNet() {
            return net;
        }

        public BigDecimal getEmployerTotalOutflow() {
            return totalCost;
        }

        /** 快照字段：人均成本按结算单位（人天/人周/人月）。 */
        public BigDecimal getEmployerAvgCostPerWorker() {
            return avgCostPerUnit;
        }

        /** 快照字段：员工到手按结算单位。 */
        public BigDecimal getWorkerTakeHomePerWorker() {
            return unitNet;
        }

        public BigDecimal getAvgCostPerUnit() {
            return avgCostPerUnit;
        }

        public BigDecimal getUnitNet() {
            return unitNet;
        }

        public String getPeriodUnit() {
            return periodUnit;
        }

        public String getSettlementMode() {
            return settlementMode;
        }

        public BigDecimal getPeriodUnits() {
            return periodUnits;
        }

        public int getWorkerCount() {
            return workerCount;
        }

        public List<FeeLine> getFeeLines() {
            return feeLines;
        }
    }
}
