package com.huimv.employment.service.settlement;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.huimv.employment.common.exception.BizException;
import com.huimv.employment.common.exception.ErrorCode;
import com.huimv.employment.dao.entity.FeDraftCostSnapshot;
import com.huimv.employment.dao.entity.FeEmploymentDraft;
import com.huimv.employment.dao.entity.FeEmploymentOrder;
import com.huimv.employment.dao.entity.FeEnterprise;
import com.huimv.employment.dao.entity.FeOrderWorkItem;
import com.huimv.employment.dao.entity.FePaymentRecord;
import com.huimv.employment.dao.entity.FeServicePlan;
import com.huimv.employment.dao.entity.FeSettlementFee;
import com.huimv.employment.dao.entity.FeSettlementItem;
import com.huimv.employment.dao.entity.FeSettlementOrder;
import com.huimv.employment.dao.entity.FeWorkerRegistration;
import com.huimv.employment.dao.mapper.FeDraftCostSnapshotMapper;
import com.huimv.employment.dao.mapper.FeEmploymentDraftMapper;
import com.huimv.employment.dao.mapper.FeEmploymentOrderMapper;
import com.huimv.employment.dao.mapper.FePaymentRecordMapper;
import com.huimv.employment.dao.mapper.FeServicePlanMapper;
import com.huimv.employment.dao.mapper.FeSettlementFeeMapper;
import com.huimv.employment.dao.mapper.FeSettlementItemMapper;
import com.huimv.employment.dao.mapper.FeSettlementOrderMapper;
import com.huimv.employment.dao.mapper.FeWorkerRegistrationMapper;
import com.huimv.employment.service.enterprise.EnterpriseService;
import com.huimv.employment.service.notification.NotificationService;
import com.huimv.employment.service.order.OrderWorkItemService;
import com.huimv.employment.service.order.dto.OrderCompleteRequest;
import com.huimv.employment.service.order.dto.OrderCompleteResponse;
import com.huimv.employment.service.settlement.dto.OrderStartSettlementRequest;
import com.huimv.employment.service.settlement.dto.SettlementConfirmPayRequest;
import com.huimv.employment.service.settlement.dto.SettlementFeeResponse;
import com.huimv.employment.service.settlement.dto.SettlementItemResponse;
import com.huimv.employment.service.settlement.dto.SettlementSummaryResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 结算服务（方案 A：企业主动发起）。
 * <p>
 * 流程：{@code task_publish} → 发起结算 → {@code settlement_pending} →
 * 确认并支付（Mock）→ {@code settlement_paid} → 完税/开票办结 → {@code completed}。
 * </p>
 * <p>MVP 无考勤、无真实支付/税票通道：明细按订单工种 unit_amount×work_days 计算，支付与办结均为 Mock。</p>
 */
@Service
public class SettlementService {

    private static final Logger log = LoggerFactory.getLogger(SettlementService.class);

    /** 订单步骤：任务已发布（发起结算的前置步骤） */
    private static final String STEP_TASK_PUBLISH = "task_publish";
    /** 订单步骤：待结算（发起结算成功后的目标步骤） */
    private static final String STEP_SETTLEMENT_PENDING = "settlement_pending";
    /** 订单步骤：已支付（历史兼容） */
    private static final String STEP_SETTLEMENT_PAID = "settlement_paid";
    /** 订单步骤：开票归档（确认支付后的目标步骤；结算打款节点 done） */
    private static final String STEP_INVOICE_ARCHIVE = "invoice_archive";
    /** 订单步骤：已完成（完税/开票办结后） */
    private static final String STEP_COMPLETED = "completed";
    /** 登记状态：已签完合同，可纳入结算 */
    private static final String REG_COMPLETED = "completed";
    /** 结算单状态：待企业确认 */
    private static final String STATUS_PENDING = "pending";
    /** 结算单状态：已确认待支付 */
    private static final String STATUS_CONFIRMED = "confirmed";
    /** 结算单状态：支付中 */
    private static final String STATUS_PAYING = "paying";
    /** 结算单状态：已支付 */
    private static final String STATUS_PAID = "paid";
    /** 结算单状态：已取消（不视为有效结算单） */
    private static final String STATUS_CANCELLED = "cancelled";
    /** 明细状态：已支付 */
    private static final String ITEM_STATUS_PAID = "paid";
    /** 支付状态：成功 */
    private static final String PAY_STATUS_SUCCESS = "success";
    /** 订单业务状态：结算中 */
    private static final String ORDER_STATUS_SETTLING = "settling";
    /** 订单业务状态：已支付 */
    private static final String ORDER_STATUS_PAID = "paid";
    /** 订单业务状态：已完成 */
    private static final String ORDER_STATUS_COMPLETED = "completed";
    private static final String CHANNEL_WECHAT = "wechat";
    private static final String CHANNEL_ALIPAY = "alipay";
    private static final String CHANNEL_BANK = "bank";
    private static final DateTimeFormatter SETTLEMENT_NO_DAY = DateTimeFormatter.ofPattern("yyyyMMdd");

    private final FeSettlementOrderMapper feSettlementOrderMapper;
    private final FeSettlementItemMapper feSettlementItemMapper;
    private final FeSettlementFeeMapper feSettlementFeeMapper;
    private final FePaymentRecordMapper fePaymentRecordMapper;
    private final FeEmploymentOrderMapper feEmploymentOrderMapper;
    private final FeEmploymentDraftMapper feEmploymentDraftMapper;
    private final FeWorkerRegistrationMapper feWorkerRegistrationMapper;
    private final FeDraftCostSnapshotMapper feDraftCostSnapshotMapper;
    private final FeServicePlanMapper feServicePlanMapper;
    private final EnterpriseService enterpriseService;
    private final NotificationService notificationService;
    private final OrderWorkItemService orderWorkItemService;
    private final OrderInvoiceService orderInvoiceService;

    public SettlementService(FeSettlementOrderMapper feSettlementOrderMapper,
                             FeSettlementItemMapper feSettlementItemMapper,
                             FeSettlementFeeMapper feSettlementFeeMapper,
                             FePaymentRecordMapper fePaymentRecordMapper,
                             FeEmploymentOrderMapper feEmploymentOrderMapper,
                             FeEmploymentDraftMapper feEmploymentDraftMapper,
                             FeWorkerRegistrationMapper feWorkerRegistrationMapper,
                             FeDraftCostSnapshotMapper feDraftCostSnapshotMapper,
                             FeServicePlanMapper feServicePlanMapper,
                             EnterpriseService enterpriseService,
                             NotificationService notificationService,
                             OrderWorkItemService orderWorkItemService,
                             OrderInvoiceService orderInvoiceService) {
        this.feSettlementOrderMapper = feSettlementOrderMapper;
        this.feSettlementItemMapper = feSettlementItemMapper;
        this.feSettlementFeeMapper = feSettlementFeeMapper;
        this.fePaymentRecordMapper = fePaymentRecordMapper;
        this.feEmploymentOrderMapper = feEmploymentOrderMapper;
        this.feEmploymentDraftMapper = feEmploymentDraftMapper;
        this.feWorkerRegistrationMapper = feWorkerRegistrationMapper;
        this.feDraftCostSnapshotMapper = feDraftCostSnapshotMapper;
        this.feServicePlanMapper = feServicePlanMapper;
        this.enterpriseService = enterpriseService;
        this.notificationService = notificationService;
        this.orderWorkItemService = orderWorkItemService;
        this.orderInvoiceService = orderInvoiceService;
    }

    /**
     * 查询订单当前有效结算单（非 cancelled）。
     */
    public SettlementSummaryResponse getActiveSettlement(Long userId, Long orderId) {
        FeEmploymentOrder order = requireOwnedOrder(userId, orderId);
        FeSettlementOrder settlement = findActiveByOrderId(orderId);
        if (settlement == null) {
            throw new BizException(ErrorCode.NOT_FOUND, "尚未发起结算");
        }
        return toSummary(order, settlement, false, "结算单详情");
    }

    /**
     * 企业发起结算：生成结算单，订单 {@code task_publish → settlement_pending}。
     * <ul>
     *   <li>幂等：已有非 cancelled 结算单则直接返回，并校正订单步骤</li>
     *   <li>计划结束日未到：须显式传 {@code allow_before_end_date=true} 才允许提前结算</li>
     *   <li>结算对象：本单 {@code reg_status=completed} 的工人</li>
     * </ul>
     */
    @Transactional(rollbackFor = Exception.class)
    public SettlementSummaryResponse startSettlement(Long userId, Long orderId, OrderStartSettlementRequest request) {
        FeEmploymentOrder order = requireOwnedOrder(userId, orderId);

        // 幂等：重复发起不新建结算单；仍确保有「结算待确认」待办
        FeSettlementOrder existing = findActiveByOrderId(orderId);
        if (existing != null) {
            ensureOrderAtSettlementPending(order, LocalDateTime.now());
            Long recipient = order.getUserId() != null ? order.getUserId() : userId;
            notificationService.pushSettlementPendingConfirm(order, existing, recipient);
            return toSummary(order, existing, false, "结算单已存在");
        }

        String step = order.getCurrentStepCode();
        if (STEP_SETTLEMENT_PENDING.equals(step) || isStepAfter(step, STEP_SETTLEMENT_PENDING)) {
            // 步骤已前进但缺结算单，属于数据不一致
            throw new BizException(ErrorCode.BAD_REQUEST, "订单已进入结算或后续阶段，但未找到有效结算单，请联系管理员");
        }
        if (!STEP_TASK_PUBLISH.equals(step)) {
            throw new BizException(ErrorCode.BAD_REQUEST,
                    "仅任务发布后可发起结算，当前步骤：" + (step == null ? "未知" : step));
        }

        // 未到计划结束日默认拦截，避免误提前结算
        boolean beforeEnd = isBeforeWorkEndDate(order);
        boolean allowBefore = request != null && request.isAllowBeforeEndDate();
        if (beforeEnd && !allowBefore) {
            LocalDate endDate = orderWorkItemService.maxWorkEndDate(
                    orderWorkItemService.listByOrderId(order.getId()));
            throw new BizException(ErrorCode.BAD_REQUEST,
                    "计划结束日（" + endDate + "）尚未到期。"
                            + "确认提前结算请传 allow_before_end_date=true");
        }

        List<FeWorkerRegistration> workers = listCompletableWorkers(orderId);
        if (workers.isEmpty()) {
            throw new BizException(ErrorCode.BAD_REQUEST, "无可结算工人（须登记已完成签署）");
        }

        List<FeOrderWorkItem> orderItems = orderWorkItemService.listByOrderId(order.getId());
        if (orderItems == null || orderItems.isEmpty()) {
            throw new BizException(ErrorCode.BAD_REQUEST, "订单无用工明细，无法按工种结算");
        }
        AmountBreakdown amounts = calculateAmountsByJobType(order, workers, orderItems);
        LocalDateTime now = LocalDateTime.now();
        // 多批次时取首个可结算工人的批次作为主批次引用
        Long batchId = workers.get(0).getBatchId();

        // 1) 结算主单
        FeSettlementOrder settlement = new FeSettlementOrder();
        settlement.setSettlementNo(nextSettlementNo());
        settlement.setOrderId(orderId);
        settlement.setBatchId(batchId);
        settlement.setEnterpriseId(order.getEnterpriseId());
        settlement.setWorkerCount(workers.size());
        settlement.setGrossAmount(amounts.gross);
        settlement.setDeductionAmount(amounts.deduction);
        settlement.setNetAmount(amounts.net);
        settlement.setServiceFeeAmount(amounts.serviceFee);
        settlement.setInvoiceAmount(amounts.invoice);
        settlement.setTotalOutflow(amounts.totalOutflow);
        settlement.setSettlementStatus(STATUS_PENDING);
        settlement.setComplianceTags("[\"合同已签署\",\"待企业确认结算\"]");
        settlement.setComplianceRemark("按订单工种 unit_amount×work_days 生成明细，待企业确认后支付");
        settlement.setCreateTime(now);
        settlement.setUpdateTime(now);
        settlement.setDelFlag(0);
        feSettlementOrderMapper.insert(settlement);

        // 2) 按人明细：匹配登记工种 → 订单用工明细计算应发
        for (WorkerLine line : amounts.lines) {
            FeSettlementItem item = new FeSettlementItem();
            item.setSettlementId(settlement.getId());
            item.setRegistrationId(line.registration.getId());
            item.setWorkerId(line.registration.getWorkerId());
            item.setWorkerNameMask(maskName(line.registration.getRealName()));
            item.setWorkDays(line.workDays);
            item.setGrossAmount(line.gross);
            item.setTaxAmount(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
            item.setOtherDeduction(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
            item.setNetAmount(line.net);
            item.setItemStatus(STATUS_PENDING);
            item.setCreateTime(now);
            item.setUpdateTime(now);
            feSettlementItemMapper.insert(item);
        }

        // 3) 订单级费用分项（供前端资金总览）
        insertFee(settlement.getId(), "labor_fee", "劳务费合计", amounts.gross, null, 1);
        if (amounts.serviceFee.compareTo(BigDecimal.ZERO) > 0) {
            insertFee(settlement.getId(), "service_fee", "平台服务费", amounts.serviceFee, amounts.serviceFeeRate, 2);
        }
        if (amounts.otherFee.compareTo(BigDecimal.ZERO) > 0) {
            insertFee(settlement.getId(), "other_fee", "其他费用", amounts.otherFee, null, 3);
        }

        // 4) 推进订单节点
        FeEmploymentOrder update = new FeEmploymentOrder();
        update.setId(orderId);
        update.setCurrentStepCode(STEP_SETTLEMENT_PENDING);
        update.setOrderStatus(ORDER_STATUS_SETTLING);
        update.setUpdateTime(now);
        feEmploymentOrderMapper.updateById(update);
        order.setCurrentStepCode(STEP_SETTLEMENT_PENDING);
        order.setOrderStatus(ORDER_STATUS_SETTLING);

        // 5) 消息与待办中心：结算待确认
        Long recipient = order.getUserId() != null ? order.getUserId() : userId;
        notificationService.pushSettlementPendingConfirm(order, settlement, recipient);

        log.info("发起结算成功 orderId={} settlementId={} settlementNo={} workers={}",
                orderId, settlement.getId(), settlement.getSettlementNo(), workers.size());

        String msg = beforeEnd ? "已提前发起结算，结算单已生成" : "结算单已生成，进入待结算";
        SettlementSummaryResponse response = toSummary(order, settlement, true, msg);
        response.setBeforeEndDate(beforeEnd);
        return response;
    }

    /**
     * 企业确认结算并支付（MVP 一次完成）。
     * <ul>
     *   <li>结算单 {@code pending → paid}，明细同步 paid</li>
     *   <li>写入 {@code fe_payment_record}（Mock 即时 success）</li>
     *   <li>订单 {@code settlement_pending → invoice_archive}（结算打款 done，进入开票归档）</li>
     *   <li>幂等：已支付则校正到开票归档并返回</li>
     * </ul>
     */
    @Transactional(rollbackFor = Exception.class)
    public SettlementSummaryResponse confirmAndPay(Long userId, Long orderId, SettlementConfirmPayRequest request) {
        FeEmploymentOrder order = requireOwnedOrder(userId, orderId);
        FeSettlementOrder settlement = findActiveByOrderId(orderId);
        if (settlement == null) {
            throw new BizException(ErrorCode.BAD_REQUEST, "尚未发起结算，请先发布任务生成结算单");
        }

        // 幂等：已支付
        if (STATUS_PAID.equals(settlement.getSettlementStatus())) {
            ensureOrderAtInvoiceArchive(order, LocalDateTime.now());
            return toSummary(order, settlement, false, "结算已支付，已进入开票归档");
        }

        // 确认支付前须任务发布完成（步骤已过 task_publish，且工种起止日已写入）
        assertTaskPublished(order);

        String step = order.getCurrentStepCode();
        if (STEP_INVOICE_ARCHIVE.equals(step) || STEP_SETTLEMENT_PAID.equals(step)
                || STEP_COMPLETED.equals(step)
                || isStepAfter(step, STEP_SETTLEMENT_PENDING)) {
            if (STATUS_PAID.equals(settlement.getSettlementStatus())
                    || findSuccessPayment(settlement.getId()) != null) {
                ensureOrderAtInvoiceArchive(order, LocalDateTime.now());
                return toSummary(order, settlement, false, "订单已进入开票归档阶段");
            }
        }
        if (!STEP_SETTLEMENT_PENDING.equals(step) && !"settlement".equals(step)) {
            throw new BizException(ErrorCode.BAD_REQUEST,
                    "仅待结算阶段可确认支付，当前步骤：" + (step == null ? "未知" : step));
        }
        if (!STATUS_PENDING.equals(settlement.getSettlementStatus())
                && !STATUS_CONFIRMED.equals(settlement.getSettlementStatus())
                && !STATUS_PAYING.equals(settlement.getSettlementStatus())) {
            throw new BizException(ErrorCode.BAD_REQUEST,
                    "当前结算单状态不可支付：" + settlement.getSettlementStatus());
        }

        String channel = resolvePaymentChannel(request != null ? request.getPaymentChannel() : null);
        LocalDateTime now = LocalDateTime.now();
        BigDecimal payAmount = settlement.getTotalOutflow() != null
                ? settlement.getTotalOutflow()
                : (settlement.getNetAmount() != null ? settlement.getNetAmount() : BigDecimal.ZERO);

        // 1) 结算单确认并置为已支付（MVP 跳过真实渠道回调）
        FeSettlementOrder settlementUpdate = new FeSettlementOrder();
        settlementUpdate.setId(settlement.getId());
        settlementUpdate.setSettlementStatus(STATUS_PAID);
        settlementUpdate.setConfirmedAt(settlement.getConfirmedAt() != null ? settlement.getConfirmedAt() : now);
        settlementUpdate.setConfirmedBy(userId);
        settlementUpdate.setPaidAt(now);
        settlementUpdate.setComplianceTags("[\"合同已签署\",\"结算已确认\",\"支付已完成\"]");
        settlementUpdate.setComplianceRemark("MVP Mock 支付成功，后续可对接真实支付通道");
        settlementUpdate.setUpdateTime(now);
        feSettlementOrderMapper.updateById(settlementUpdate);
        settlement.setSettlementStatus(STATUS_PAID);
        settlement.setConfirmedAt(settlementUpdate.getConfirmedAt());
        settlement.setConfirmedBy(userId);
        settlement.setPaidAt(now);

        // 2) 明细全部标记已支付
        List<FeSettlementItem> items = feSettlementItemMapper.selectList(
                new LambdaQueryWrapper<FeSettlementItem>()
                        .eq(FeSettlementItem::getSettlementId, settlement.getId()));
        if (items != null) {
            for (FeSettlementItem item : items) {
                FeSettlementItem itemUpdate = new FeSettlementItem();
                itemUpdate.setId(item.getId());
                itemUpdate.setItemStatus(ITEM_STATUS_PAID);
                itemUpdate.setUpdateTime(now);
                feSettlementItemMapper.updateById(itemUpdate);
            }
        }

        // 3) 支付记录（Mock 即时成功；已有成功记录则复用）
        FePaymentRecord payment = findSuccessPayment(settlement.getId());
        if (payment == null) {
            payment = new FePaymentRecord();
            payment.setPaymentNo(nextPaymentNo());
            payment.setSettlementId(settlement.getId());
            payment.setEnterpriseId(order.getEnterpriseId());
            payment.setPaymentChannel(channel);
            payment.setPaymentAmount(payAmount);
            payment.setPaymentStatus(PAY_STATUS_SUCCESS);
            payment.setExternalTradeNo("MOCK-" + payment.getPaymentNo());
            payment.setPayerUserId(userId);
            payment.setPaidAt(now);
            payment.setCreateTime(now);
            payment.setUpdateTime(now);
            fePaymentRecordMapper.insert(payment);
        }

        // 4) 结算打款完成 → 开票归档节点
        FeEmploymentOrder orderUpdate = new FeEmploymentOrder();
        orderUpdate.setId(order.getId());
        orderUpdate.setCurrentStepCode(STEP_INVOICE_ARCHIVE);
        orderUpdate.setOrderStatus(ORDER_STATUS_PAID);
        orderUpdate.setUpdateTime(now);
        feEmploymentOrderMapper.updateById(orderUpdate);
        order.setCurrentStepCode(STEP_INVOICE_ARCHIVE);
        order.setOrderStatus(ORDER_STATUS_PAID);

        // 5) 关闭「结算待确认」待办
        notificationService.markSettlementTodoHandled(settlement.getId());

        log.info("结算确认支付成功，进入开票归档 orderId={} settlementId={} paymentNo={} channel={} amount={}",
                orderId, settlement.getId(), payment.getPaymentNo(), channel, payAmount);
        return toSummary(order, settlement, false, "结算已确认并支付成功，已进入开票归档");
    }

    /**
     * 企业完税/开票办结：订单 {@code invoice_archive → completed}。
     * <ul>
     *   <li>前置：订单已在开票归档（或历史 settlement_paid），且已确认结算并支付（settlement=paid）</li>
     *   <li>须完税且已开票（MVP 默认均为 true；显式传 false 会拒绝办结）</li>
     *   <li>结算单合规标签追加完税/开票留痕；发票号、完税凭证号可选写入备注</li>
     *   <li>幂等：已 completed 直接返回</li>
     * </ul>
     */
    @Transactional(rollbackFor = Exception.class)
    public OrderCompleteResponse completeOrder(Long userId, Long orderId, OrderCompleteRequest request) {
        FeEmploymentOrder order = requireOwnedOrder(userId, orderId);
        LocalDateTime now = LocalDateTime.now();

        if (STEP_COMPLETED.equals(order.getCurrentStepCode())) {
            return toCompleteResponse(order, findActiveByOrderId(orderId), request, now, "流程已结束");
        }

        String step = order.getCurrentStepCode();
        if (!STEP_INVOICE_ARCHIVE.equals(step) && !STEP_SETTLEMENT_PAID.equals(step)) {
            throw new BizException(ErrorCode.BAD_REQUEST,
                    "仅开票归档阶段可办结，当前步骤：" + (step == null ? "未知" : step));
        }

        // 开票归档前须已确认结算并支付
        FeSettlementOrder settlement = assertSettlementConfirmed(orderId);

        OrderCompleteRequest req = request != null ? request : new OrderCompleteRequest();
        if (!req.isTaxClearedOrDefault()) {
            throw new BizException(ErrorCode.BAD_REQUEST, "须确认已完税（tax_cleared=true）后才能办结");
        }
        if (!req.isInvoiceIssuedOrDefault()) {
            throw new BizException(ErrorCode.BAD_REQUEST, "须确认已开票（invoice_issued=true）后才能办结");
        }

        // 开票信息落库 fe_order_invoice
        orderInvoiceService.saveOnArchive(userId, order, settlement, req, now);

        // 结算单合规留痕
        StringBuilder remark = new StringBuilder("开票归档办结：完税已确认、发票已开具");
        if (StringUtils.hasText(req.getInvoiceNo())) {
            remark.append("；发票号=").append(req.getInvoiceNo().trim());
        }
        if (StringUtils.hasText(req.getTaxVoucherNo())) {
            remark.append("；完税凭证=").append(req.getTaxVoucherNo().trim());
        }
        if (StringUtils.hasText(req.getCompanyName())) {
            remark.append("；抬头=").append(req.getCompanyName().trim());
        }
        if (StringUtils.hasText(req.getRemark())) {
            remark.append("；").append(req.getRemark().trim());
        }

        FeSettlementOrder settlementUpdate = new FeSettlementOrder();
        settlementUpdate.setId(settlement.getId());
        settlementUpdate.setComplianceTags(
                "[\"合同已签署\",\"结算已确认\",\"支付已完成\",\"完税已申报\",\"发票已开具\",\"流程已结束\"]");
        settlementUpdate.setComplianceRemark(remark.toString());
        settlementUpdate.setUpdateTime(now);
        feSettlementOrderMapper.updateById(settlementUpdate);
        settlement.setComplianceTags(settlementUpdate.getComplianceTags());
        settlement.setComplianceRemark(settlementUpdate.getComplianceRemark());

        FeEmploymentOrder orderUpdate = new FeEmploymentOrder();
        orderUpdate.setId(order.getId());
        orderUpdate.setCurrentStepCode(STEP_COMPLETED);
        orderUpdate.setOrderStatus(ORDER_STATUS_COMPLETED);
        orderUpdate.setUpdateTime(now);
        feEmploymentOrderMapper.updateById(orderUpdate);
        order.setCurrentStepCode(STEP_COMPLETED);
        order.setOrderStatus(ORDER_STATUS_COMPLETED);

        log.info("开票归档完成，流程结束 orderId={} settlementId={} invoiceNo={} taxVoucherNo={}",
                orderId, settlement.getId(), req.getInvoiceNo(), req.getTaxVoucherNo());
        return toCompleteResponse(order, settlement, req, now, "开票归档完成，开票信息已保存，流程已结束");
    }

    private OrderCompleteResponse toCompleteResponse(FeEmploymentOrder order,
                                                     FeSettlementOrder settlement,
                                                     OrderCompleteRequest request,
                                                     LocalDateTime completedAt,
                                                     String message) {
        OrderCompleteRequest req = request != null ? request : new OrderCompleteRequest();
        OrderCompleteResponse response = new OrderCompleteResponse();
        response.setOrderId(order.getId());
        response.setOrderNo(order.getOrderNo());
        response.setCurrentStepCode(order.getCurrentStepCode());
        response.setOrderStatus(order.getOrderStatus());
        if (settlement != null) {
            response.setSettlementId(settlement.getId());
            response.setSettlementNo(settlement.getSettlementNo());
            response.setInvoiceAmount(settlement.getInvoiceAmount());
        }
        response.setTaxCleared(req.isTaxClearedOrDefault());
        response.setInvoiceIssued(req.isInvoiceIssuedOrDefault());
        response.setInvoiceNo(StringUtils.hasText(req.getInvoiceNo()) ? req.getInvoiceNo().trim() : null);
        response.setTaxVoucherNo(StringUtils.hasText(req.getTaxVoucherNo()) ? req.getTaxVoucherNo().trim() : null);
        response.setMessage(message);
        response.setCompletedAt(completedAt);
        return response;
    }

    /**
     * 幂等路径：结算单已存在但订单步骤未跟上时，校正到 settlement_pending。
     */
    private void ensureOrderAtSettlementPending(FeEmploymentOrder order, LocalDateTime now) {
        if (STEP_SETTLEMENT_PENDING.equals(order.getCurrentStepCode())
                || isStepAfter(order.getCurrentStepCode(), STEP_SETTLEMENT_PENDING)) {
            return;
        }
        FeEmploymentOrder update = new FeEmploymentOrder();
        update.setId(order.getId());
        update.setCurrentStepCode(STEP_SETTLEMENT_PENDING);
        update.setOrderStatus(ORDER_STATUS_SETTLING);
        update.setUpdateTime(now);
        feEmploymentOrderMapper.updateById(update);
        order.setCurrentStepCode(STEP_SETTLEMENT_PENDING);
        order.setOrderStatus(ORDER_STATUS_SETTLING);
    }

    /** 幂等路径：已支付但订单步骤未跟上时，校正到 invoice_archive。 */
    private void ensureOrderAtInvoiceArchive(FeEmploymentOrder order, LocalDateTime now) {
        if (STEP_INVOICE_ARCHIVE.equals(order.getCurrentStepCode())
                || STEP_COMPLETED.equals(order.getCurrentStepCode())
                || isStepAfter(order.getCurrentStepCode(), STEP_INVOICE_ARCHIVE)) {
            return;
        }
        FeEmploymentOrder update = new FeEmploymentOrder();
        update.setId(order.getId());
        update.setCurrentStepCode(STEP_INVOICE_ARCHIVE);
        update.setOrderStatus(ORDER_STATUS_PAID);
        update.setUpdateTime(now);
        feEmploymentOrderMapper.updateById(update);
        order.setCurrentStepCode(STEP_INVOICE_ARCHIVE);
        order.setOrderStatus(ORDER_STATUS_PAID);
    }

    /** 本单最新一条非 cancelled 结算单。 */
    private FeSettlementOrder findActiveByOrderId(Long orderId) {
        return feSettlementOrderMapper.selectOne(new LambdaQueryWrapper<FeSettlementOrder>()
                .eq(FeSettlementOrder::getOrderId, orderId)
                .ne(FeSettlementOrder::getSettlementStatus, STATUS_CANCELLED)
                .orderByDesc(FeSettlementOrder::getId)
                .last("LIMIT 1"));
    }

    /** 可结算工人：登记已 completed（签完合同）。不含待审/驳回。 */
    private List<FeWorkerRegistration> listCompletableWorkers(Long orderId) {
        List<FeWorkerRegistration> list = feWorkerRegistrationMapper.selectList(
                new LambdaQueryWrapper<FeWorkerRegistration>()
                        .eq(FeWorkerRegistration::getOrderId, orderId)
                        .eq(FeWorkerRegistration::getRegStatus, REG_COMPLETED)
                        .orderByAsc(FeWorkerRegistration::getId));
        return list != null ? list : new ArrayList<>();
    }

    /**
     * 按工人确认工种匹配订单用工明细计算结算金额：
     * <ol>
     *   <li>每人应发 = 对应工种 {@code unit_amount × work_days}</li>
     *   <li>应发总额 = 各人应发合计</li>
     *   <li>服务费 = 方案费率 × 应发总额（若有）</li>
     *   <li>企业总支出：优先按工种明细 total_amount 折算；否则 应发+服务费</li>
     * </ol>
     */
    private AmountBreakdown calculateAmountsByJobType(FeEmploymentOrder order,
                                                      List<FeWorkerRegistration> workers,
                                                      List<FeOrderWorkItem> orderItems) {
        Map<String, FeOrderWorkItem> jobTypeMap = new HashMap<>();
        for (FeOrderWorkItem wi : orderItems) {
            if (wi != null && StringUtils.hasText(wi.getJobType())) {
                jobTypeMap.putIfAbsent(wi.getJobType().trim(), wi);
            }
        }
        FeOrderWorkItem fallback = orderWorkItemService.firstItem(orderItems);

        List<WorkerLine> lines = new ArrayList<>();
        BigDecimal gross = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        for (FeWorkerRegistration reg : workers) {
            FeOrderWorkItem wi = resolveWorkItemForRegistration(reg, jobTypeMap, fallback);
            BigDecimal workDays = wi.getWorkDays() != null && wi.getWorkDays().compareTo(BigDecimal.ZERO) > 0
                    ? wi.getWorkDays() : BigDecimal.ONE;
            BigDecimal unitAmount = wi.getUnitAmount() != null ? wi.getUnitAmount() : BigDecimal.ZERO;
            BigDecimal perGross = unitAmount.multiply(workDays).setScale(2, RoundingMode.HALF_UP);
            // 若明细已有 total_amount 且配置了人数，优先用「组内人均」作为该工种单价口径兜底为 0 时的回退
            if (perGross.compareTo(BigDecimal.ZERO) <= 0
                    && wi.getTotalAmount() != null
                    && wi.getWorkerCount() != null
                    && wi.getWorkerCount() > 0) {
                perGross = wi.getTotalAmount()
                        .divide(BigDecimal.valueOf(wi.getWorkerCount()), 2, RoundingMode.HALF_UP);
            }

            WorkerLine line = new WorkerLine();
            line.registration = reg;
            line.jobType = wi.getJobType();
            line.workDays = workDays;
            line.unitAmount = unitAmount;
            line.gross = perGross;
            line.net = perGross; // MVP：实发=应发
            lines.add(line);
            gross = gross.add(perGross);
        }

        BigDecimal serviceFeeRate = null;
        BigDecimal serviceFee = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        if (order.getSelectedPlanId() != null) {
            FeServicePlan plan = feServicePlanMapper.selectById(order.getSelectedPlanId());
            if (plan != null && plan.getServiceFeeRate() != null
                    && plan.getServiceFeeRate().compareTo(BigDecimal.ZERO) > 0) {
                serviceFeeRate = plan.getServiceFeeRate();
                serviceFee = gross.multiply(serviceFeeRate).setScale(2, RoundingMode.HALF_UP);
            }
        }

        int workerCount = workers.size();
        int plannedCount = orderWorkItemService.sumWorkerCount(orderItems);
        BigDecimal otherFee = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        BigDecimal totalOutflow;
        FeDraftCostSnapshot snapshot = findSelectedSnapshot(order);
        BigDecimal plannedTotal = orderWorkItemService.sumTotalAmount(orderItems);
        if (snapshot != null && snapshot.getEmployerTotalOutflow() != null && plannedCount > 0) {
            totalOutflow = snapshot.getEmployerTotalOutflow()
                    .multiply(BigDecimal.valueOf(workerCount))
                    .divide(BigDecimal.valueOf(plannedCount), 2, RoundingMode.HALF_UP);
            BigDecimal impliedOther = totalOutflow.subtract(gross).subtract(serviceFee);
            if (impliedOther.compareTo(BigDecimal.ZERO) > 0) {
                otherFee = impliedOther.setScale(2, RoundingMode.HALF_UP);
            } else {
                totalOutflow = gross.add(serviceFee).setScale(2, RoundingMode.HALF_UP);
            }
        } else if (plannedTotal != null && plannedTotal.compareTo(BigDecimal.ZERO) > 0 && plannedCount > 0) {
            totalOutflow = plannedTotal
                    .multiply(BigDecimal.valueOf(workerCount))
                    .divide(BigDecimal.valueOf(plannedCount), 2, RoundingMode.HALF_UP);
        } else {
            totalOutflow = gross.add(serviceFee).setScale(2, RoundingMode.HALF_UP);
        }

        AmountBreakdown breakdown = new AmountBreakdown();
        breakdown.lines = lines;
        breakdown.gross = gross;
        breakdown.deduction = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        breakdown.net = gross;
        breakdown.serviceFee = serviceFee;
        breakdown.serviceFeeRate = serviceFeeRate;
        breakdown.otherFee = otherFee;
        breakdown.invoice = totalOutflow;
        breakdown.totalOutflow = totalOutflow;
        return breakdown;
    }

    /** 按登记 confirmed_work_type 精确匹配订单工种；无工种时回退首条明细。 */
    private static FeOrderWorkItem resolveWorkItemForRegistration(FeWorkerRegistration reg,
                                                                  Map<String, FeOrderWorkItem> jobTypeMap,
                                                                  FeOrderWorkItem fallback) {
        if (reg != null && StringUtils.hasText(reg.getConfirmedWorkType())) {
            FeOrderWorkItem matched = jobTypeMap.get(reg.getConfirmedWorkType().trim());
            if (matched != null) {
                return matched;
            }
            throw new BizException(ErrorCode.BAD_REQUEST,
                    "工人「" + (reg.getRealName() != null ? reg.getRealName() : reg.getId())
                            + "」确认工种「" + reg.getConfirmedWorkType().trim()
                            + "」不在订单用工明细中，无法结算");
        }
        if (fallback == null) {
            throw new BizException(ErrorCode.BAD_REQUEST, "订单无用工明细，无法结算");
        }
        return fallback;
    }

    /** 取订单所选方案对应的草稿成本快照。 */
    private FeDraftCostSnapshot findSelectedSnapshot(FeEmploymentOrder order) {
        if (order.getDraftId() == null || order.getSelectedPlanId() == null) {
            return null;
        }
        return feDraftCostSnapshotMapper.selectOne(new LambdaQueryWrapper<FeDraftCostSnapshot>()
                .eq(FeDraftCostSnapshot::getDraftId, order.getDraftId())
                .eq(FeDraftCostSnapshot::getPlanId, order.getSelectedPlanId())
                .orderByDesc(FeDraftCostSnapshot::getId)
                .last("LIMIT 1"));
    }

    /** 写入一条结算费用分项。 */
    private void insertFee(Long settlementId, String code, String name, BigDecimal amount,
                           BigDecimal rate, int sort) {
        FeSettlementFee fee = new FeSettlementFee();
        fee.setSettlementId(settlementId);
        fee.setFeeCode(code);
        fee.setFeeName(name);
        fee.setAmount(amount);
        fee.setRate(rate);
        fee.setSortOrder(sort);
        feSettlementFeeMapper.insert(fee);
    }

    /** 组装结算摘要（含明细与费用分项）。 */
    private SettlementSummaryResponse toSummary(FeEmploymentOrder order,
                                                FeSettlementOrder settlement,
                                                boolean created,
                                                String message) {
        SettlementSummaryResponse response = new SettlementSummaryResponse();
        response.setSettlementId(settlement.getId());
        response.setSettlementNo(settlement.getSettlementNo());
        response.setOrderId(order.getId());
        response.setOrderNo(order.getOrderNo());
        response.setCurrentStepCode(order.getCurrentStepCode());
        response.setSettlementStatus(settlement.getSettlementStatus());
        response.setWorkerCount(settlement.getWorkerCount());
        response.setPlannedCount(orderWorkItemService.sumWorkerCountByOrderId(order.getId()));
        response.setWorkEndDate(orderWorkItemService.maxWorkEndDate(
                orderWorkItemService.listByOrderId(order.getId())));
        response.setGrossAmount(settlement.getGrossAmount());
        response.setDeductionAmount(settlement.getDeductionAmount());
        response.setNetAmount(settlement.getNetAmount());
        response.setServiceFeeAmount(settlement.getServiceFeeAmount());
        response.setInvoiceAmount(settlement.getInvoiceAmount());
        response.setTotalOutflow(settlement.getTotalOutflow());
        response.setBeforeEndDate(isBeforeWorkEndDate(order));
        response.setCreated(created);
        response.setMessage(message);
        response.setCreateTime(settlement.getCreateTime());
        response.setConfirmedAt(settlement.getConfirmedAt());
        response.setPaidAt(settlement.getPaidAt());

        FePaymentRecord payment = findLatestPayment(settlement.getId());
        if (payment != null) {
            response.setPaymentId(payment.getId());
            response.setPaymentNo(payment.getPaymentNo());
            response.setPaymentChannel(payment.getPaymentChannel());
            response.setPaymentStatus(payment.getPaymentStatus());
            response.setPaymentAmount(payment.getPaymentAmount());
            response.setExternalTradeNo(payment.getExternalTradeNo());
        }

        List<FeSettlementItem> items = feSettlementItemMapper.selectList(
                new LambdaQueryWrapper<FeSettlementItem>()
                        .eq(FeSettlementItem::getSettlementId, settlement.getId())
                        .orderByAsc(FeSettlementItem::getId));
        List<SettlementItemResponse> itemResponses = new ArrayList<>();
        if (items != null) {
            for (FeSettlementItem item : items) {
                SettlementItemResponse ir = new SettlementItemResponse();
                ir.setItemId(item.getId());
                ir.setRegistrationId(item.getRegistrationId());
                ir.setWorkerId(item.getWorkerId());
                ir.setWorkerNameMask(item.getWorkerNameMask());
                ir.setWorkDays(item.getWorkDays());
                ir.setGrossAmount(item.getGrossAmount());
                ir.setTaxAmount(item.getTaxAmount());
                ir.setOtherDeduction(item.getOtherDeduction());
                ir.setNetAmount(item.getNetAmount());
                ir.setItemStatus(item.getItemStatus());
                itemResponses.add(ir);
            }
        }
        response.setItems(itemResponses);

        List<FeSettlementFee> fees = feSettlementFeeMapper.selectList(
                new LambdaQueryWrapper<FeSettlementFee>()
                        .eq(FeSettlementFee::getSettlementId, settlement.getId())
                        .orderByAsc(FeSettlementFee::getSortOrder));
        List<SettlementFeeResponse> feeResponses = new ArrayList<>();
        if (fees != null) {
            for (FeSettlementFee fee : fees) {
                SettlementFeeResponse fr = new SettlementFeeResponse();
                fr.setFeeCode(fee.getFeeCode());
                fr.setFeeName(fee.getFeeName());
                fr.setAmount(fee.getAmount());
                fr.setRate(fee.getRate());
                fr.setSortOrder(fee.getSortOrder());
                feeResponses.add(fr);
            }
        }
        response.setFees(feeResponses);
        return response;
    }

    /** 校验企业身份且订单归属当前企业。 */
    private FeEmploymentOrder requireOwnedOrder(Long userId, Long orderId) {
        enterpriseService.requireEnterpriseUser(userId);
        FeEnterprise enterprise = enterpriseService.findEnterpriseByUserId(userId);
        if (enterprise == null) {
            throw new BizException(ErrorCode.BAD_REQUEST, "请先完成企业登记");
        }
        if (orderId == null) {
            throw new BizException(ErrorCode.BAD_REQUEST, "订单 ID 无效");
        }
        FeEmploymentOrder order = feEmploymentOrderMapper.selectById(orderId);
        if (order == null) {
            throw new BizException(ErrorCode.NOT_FOUND, "用工订单不存在");
        }
        if (order.getEnterpriseId() == null || !order.getEnterpriseId().equals(enterprise.getId())) {
            throw new BizException(ErrorCode.FORBIDDEN, "无权操作该订单");
        }
        return order;
    }

    /** 当前日期是否早于订单计划结束日（结束日当天视为可正常结算）。 */
    private boolean isBeforeWorkEndDate(FeEmploymentOrder order) {
        LocalDate end = orderWorkItemService.maxWorkEndDate(orderWorkItemService.listByOrderId(order.getId()));
        return end != null && LocalDate.now().isBefore(end);
    }

    /** 姓名脱敏：张三→张*，欧阳娜娜→欧*娜。 */
    private static String maskName(String realName) {
        if (!StringUtils.hasText(realName)) {
            return "*";
        }
        String name = realName.trim();
        if (name.length() == 1) {
            return name;
        }
        if (name.length() == 2) {
            return name.charAt(0) + "*";
        }
        return name.charAt(0) + "*" + name.charAt(name.length() - 1);
    }

    /** 结算单号：STL + yyyyMMdd + 4 位随机数。 */
    private String nextSettlementNo() {
        String day = LocalDateTime.now().format(SETTLEMENT_NO_DAY);
        int rand = ThreadLocalRandom.current().nextInt(1000, 10000);
        return "STL" + day + rand;
    }

    /** 支付单号：PAY + yyyyMMdd + 4 位随机数。 */
    private String nextPaymentNo() {
        String day = LocalDateTime.now().format(SETTLEMENT_NO_DAY);
        int rand = ThreadLocalRandom.current().nextInt(1000, 10000);
        return "PAY" + day + rand;
    }

    /** 解析并校验支付渠道，非法则默认 wechat。 */
    private static String resolvePaymentChannel(String raw) {
        if (!StringUtils.hasText(raw)) {
            return CHANNEL_WECHAT;
        }
        String channel = raw.trim().toLowerCase();
        if (CHANNEL_WECHAT.equals(channel) || CHANNEL_ALIPAY.equals(channel) || CHANNEL_BANK.equals(channel)) {
            return channel;
        }
        throw new BizException(ErrorCode.BAD_REQUEST, "不支持的支付渠道：" + raw + "，可选 wechat/alipay/bank");
    }

    private FePaymentRecord findSuccessPayment(Long settlementId) {
        return fePaymentRecordMapper.selectOne(new LambdaQueryWrapper<FePaymentRecord>()
                .eq(FePaymentRecord::getSettlementId, settlementId)
                .eq(FePaymentRecord::getPaymentStatus, PAY_STATUS_SUCCESS)
                .orderByDesc(FePaymentRecord::getId)
                .last("LIMIT 1"));
    }

    private FePaymentRecord findLatestPayment(Long settlementId) {
        return fePaymentRecordMapper.selectOne(new LambdaQueryWrapper<FePaymentRecord>()
                .eq(FePaymentRecord::getSettlementId, settlementId)
                .orderByDesc(FePaymentRecord::getId)
                .last("LIMIT 1"));
    }

    /**
     * 开票归档前校验本单已确认结算并支付：
     * 须存在有效结算单、状态为 paid，且已有成功支付记录（或已写入 confirmed_at）。
     */
    private FeSettlementOrder assertSettlementConfirmed(Long orderId) {
        FeSettlementOrder settlement = findActiveByOrderId(orderId);
        if (settlement == null) {
            throw new BizException(ErrorCode.BAD_REQUEST, "尚未确认结算，无法开票归档（无有效结算单）");
        }
        if (!STATUS_PAID.equals(settlement.getSettlementStatus())) {
            throw new BizException(ErrorCode.BAD_REQUEST,
                    "尚未确认结算，无法开票归档（当前结算状态："
                            + (settlement.getSettlementStatus() == null
                            ? "未知" : settlement.getSettlementStatus()) + "）");
        }
        if (settlement.getConfirmedAt() == null && findSuccessPayment(settlement.getId()) == null) {
            throw new BizException(ErrorCode.BAD_REQUEST,
                    "尚未确认结算，无法开票归档（缺少确认时间或成功支付记录）");
        }
        return settlement;
    }

    /**
     * 确认支付前校验任务已发布完成：
     * <ul>
     *   <li>订单步骤已到达或超过 {@code task_publish}</li>
     *   <li>全部用工明细已写入 work_start_date / work_end_date</li>
     * </ul>
     */
    private void assertTaskPublished(FeEmploymentOrder order) {
        String step = order.getCurrentStepCode();
        boolean published = STEP_TASK_PUBLISH.equals(step)
                || STEP_SETTLEMENT_PENDING.equals(step)
                || "settlement".equals(step)
                || isStepAfter(step, STEP_TASK_PUBLISH);
        if (!published) {
            throw new BizException(ErrorCode.BAD_REQUEST,
                    "任务尚未发布完成，无法确认结算并支付（当前步骤："
                            + (step == null ? "未知" : step) + "）");
        }
        List<FeOrderWorkItem> items = orderWorkItemService.listByOrderId(order.getId());
        if (items == null || items.isEmpty()) {
            throw new BizException(ErrorCode.BAD_REQUEST, "任务尚未发布完成：订单无用工明细");
        }
        for (FeOrderWorkItem item : items) {
            if (item.getWorkStartDate() == null || item.getWorkEndDate() == null) {
                String label = StringUtils.hasText(item.getJobType())
                        ? item.getJobType()
                        : (StringUtils.hasText(item.getGroupNo()) ? item.getGroupNo() : String.valueOf(item.getId()));
                throw new BizException(ErrorCode.BAD_REQUEST,
                        "任务尚未发布完成：工种「" + label + "」缺少起止日，请先调用任务发布接口");
            }
        }
    }

    /** 判断 current 是否在合规流程中严格位于 target 之后。 */
    private static boolean isStepAfter(String current, String target) {
        if (!StringUtils.hasText(current) || !StringUtils.hasText(target)) {
            return false;
        }
        List<String> steps = Arrays.asList(
                "draft", "draft_created", "plan_confirmed", "enterprise_auth",
                "registration", "worker_registration", "worker_recruitment",
                "enterprise_audit", "contract_signing",
                "task_publish", "settlement_pending", "settlement",
                "settlement_paid", "invoice_archive", "completed");
        // settlement / settlement_pending 同位；settlement_paid 与 invoice_archive 均视为支付后
        int curIdx = steps.indexOf(current);
        int targetIdx = steps.indexOf(target);
        return curIdx >= 0 && targetIdx >= 0 && curIdx > targetIdx;
    }

    /** 单人按工种计算的结算行。 */
    private static final class WorkerLine {
        private FeWorkerRegistration registration;
        private String jobType;
        private BigDecimal workDays;
        private BigDecimal unitAmount;
        private BigDecimal gross;
        private BigDecimal net;
    }

    /** 结算金额中间结果。 */
    private static final class AmountBreakdown {
        /** 按人明细行 */
        private List<WorkerLine> lines;
        /** 应发总额 */
        private BigDecimal gross;
        /** 扣款合计 */
        private BigDecimal deduction;
        /** 实发总额 */
        private BigDecimal net;
        /** 平台服务费 */
        private BigDecimal serviceFee;
        /** 服务费率（可空） */
        private BigDecimal serviceFeeRate;
        /** 总支出中除劳务费、服务费外的部分（保险等） */
        private BigDecimal otherFee;
        /** 预计开票额 */
        private BigDecimal invoice;
        /** 企业总支出 */
        private BigDecimal totalOutflow;
    }
}
