package com.huimv.employment.service.draft;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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.FeConversation;
import com.huimv.employment.dao.entity.FeDraftCostSnapshot;
import com.huimv.employment.dao.entity.FeDraftField;
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.FeRegistrationBatch;
import com.huimv.employment.dao.mapper.FeDraftCostSnapshotMapper;
import com.huimv.employment.dao.mapper.FeDraftFieldMapper;
import com.huimv.employment.dao.mapper.FeEmploymentDraftMapper;
import com.huimv.employment.dao.mapper.FeEmploymentOrderMapper;
import com.huimv.employment.service.conversation.ConversationService;
import com.huimv.employment.service.registration.RegistrationBatchService;
import com.huimv.employment.service.draft.dto.McpDraftCalculateResponse;
import com.huimv.employment.service.draft.dto.McpDraftGenerateRequest;
import com.huimv.employment.service.draft.dto.McpDraftGenerateResponse;
import com.huimv.employment.service.draft.dto.McpDraftInfoResponse;
import com.huimv.employment.service.draft.dto.McpDraftSubmitRequest;
import com.huimv.employment.service.draft.dto.McpDraftSubmitResponse;
import com.huimv.employment.service.draft.dto.McpDraftUpdateRequest;
import com.huimv.employment.service.draft.dto.McpDraftUpdateResponse;
import com.huimv.employment.service.enterprise.EnterpriseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

/**
 * MCP 草稿接口业务（对齐交互规则 v2 §6.3）。
 * <p>通过 session_id 关联会话；update/info/calculate/submit 须传入 draft_id，并校验草稿归属当前会话。</p>
 */
@Service
public class McpDraftService {

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

    private static final String DB_STATUS_PENDING = "pending";
    private static final String DB_STATUS_CONFIRMED = "confirmed";
    private static final String ORDER_STATUS_CONFIRMED = "confirmed";
    private static final String STEP_PLAN_CONFIRMED = "plan_confirmed";
    private static final String SUBMIT_SUCCESS = "success";
    private static final String MCP_STATUS_INCOMPLETE = "incomplete";
    private static final String MCP_STATUS_READY = "ready";
    private static final String MCP_STATUS_SUBMITTED = "submitted";
    private static final String MCP_UPDATE_SUCCESS = "success";
    private static final String MCP_UPDATE_FAIL = "fail";
    private static final String MCP_UPDATE_BAN = "ban";

    private static final String SOURCE_AI_INFERRED = "ai_inferred";
    private static final String SOURCE_ENTERPRISE_PROFILE = "enterprise_profile";
    private static final String WORK_LOCATION_SOURCE_ENTERPRISE = "enterprise_registered_address";

    private static final Set<String> ALLOWED_SOURCE_TYPES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
            "intent", "clone", "button")));
    private static final Set<String> ALLOWED_SETTLEMENT_MODES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
            "daily", "piece", "monthly")));

    private final FeEmploymentDraftMapper feEmploymentDraftMapper;
    private final FeDraftFieldMapper feDraftFieldMapper;
    private final FeDraftCostSnapshotMapper feDraftCostSnapshotMapper;
    private final FeEmploymentOrderMapper feEmploymentOrderMapper;
    private final ConversationService conversationService;
    private final EnterpriseService enterpriseService;
    private final DraftCostCalculationEngine draftCostCalculationEngine;
    private final RegistrationBatchService registrationBatchService;
    private final ObjectMapper objectMapper;

    public McpDraftService(FeEmploymentDraftMapper feEmploymentDraftMapper,
                           FeDraftFieldMapper feDraftFieldMapper,
                           FeDraftCostSnapshotMapper feDraftCostSnapshotMapper,
                           FeEmploymentOrderMapper feEmploymentOrderMapper,
                           ConversationService conversationService,
                           EnterpriseService enterpriseService,
                           DraftCostCalculationEngine draftCostCalculationEngine,
                           RegistrationBatchService registrationBatchService,
                           ObjectMapper objectMapper) {
        this.feEmploymentDraftMapper = feEmploymentDraftMapper;
        this.feDraftFieldMapper = feDraftFieldMapper;
        this.feDraftCostSnapshotMapper = feDraftCostSnapshotMapper;
        this.feEmploymentOrderMapper = feEmploymentOrderMapper;
        this.conversationService = conversationService;
        this.enterpriseService = enterpriseService;
        this.draftCostCalculationEngine = draftCostCalculationEngine;
        this.registrationBatchService = registrationBatchService;
        this.objectMapper = objectMapper;
    }

    @Transactional(rollbackFor = Exception.class)
    public McpDraftGenerateResponse generate(String sessionId, McpDraftGenerateRequest request) {
        validateGenerateRequest(request);
        FeConversation conversation = conversationService.requireBySessionId(sessionId);
        FeEmploymentDraft existing = findPendingDraft(conversation.getId());
        if (existing != null) {
            return toGenerateResponse(existing);
        }
        if ("clone".equalsIgnoreCase(request.getSourceType().trim())) {
            throw new BizException(ErrorCode.BAD_REQUEST, "请使用 POST /api/mcp/orders/clone 克隆历史订单");
        }

        FeEnterprise enterprise = enterpriseService.requireById(conversation.getEnterpriseId());
        LocalDateTime now = LocalDateTime.now();
        FeEmploymentDraft draft = buildInitialDraft(conversation, enterprise, request, now);
        try {
            feEmploymentDraftMapper.insert(draft);
        } catch (DuplicateKeyException ex) {
            throw new BizException(ErrorCode.BAD_REQUEST, "草稿编号冲突，请重试");
        }
        prefillFromEnterprise(draft, enterprise, now);
        recalculateMissingFields(draft, now);
        return toGenerateResponse(feEmploymentDraftMapper.selectById(draft.getId()));
    }

    @Transactional(rollbackFor = Exception.class)
    public McpDraftUpdateResponse update(String sessionId, McpDraftUpdateRequest request) {
        FeConversation conversation = conversationService.requireBySessionId(sessionId);
        FeEmploymentDraft draft = requireDraftInConversation(conversation.getId(), request.getDraftId());
        if (!isDbEditable(draft)) {
            return banResponse(draft.getId());
        }
        try {
            LocalDateTime now = LocalDateTime.now();
            Set<String> updatedKeys = applyMcpFields(draft, request.getFields());
            draft.setUpdateTime(now);
            feEmploymentDraftMapper.updateById(draft);
            syncFieldMetadata(draft.getId(), updatedKeys, draft, now, SOURCE_AI_INFERRED);
            recalculateMissingFields(draft, now);
            return successResponse(draft.getId());
        } catch (BizException ex) {
            log.debug("MCP 草稿更新失败 sessionId={} draftId={}: {}",
                    sessionId, request.getDraftId(), ex.getMessage());
            McpDraftUpdateResponse response = new McpDraftUpdateResponse();
            response.setDraftId(request.getDraftId());
            response.setStatus(MCP_UPDATE_FAIL);
            return response;
        }
    }

    public McpDraftInfoResponse getInfo(String sessionId, Long draftId) {
        FeConversation conversation = conversationService.requireBySessionId(sessionId);
        FeEmploymentDraft draft = requireDraftInConversation(conversation.getId(), draftId);
        return toInfoResponse(draft);
    }

    @Transactional(rollbackFor = Exception.class)
    public McpDraftCalculateResponse calculate(String sessionId, Long draftId) {
        FeConversation conversation = conversationService.requireBySessionId(sessionId);
        FeEmploymentDraft draft = requireReadyDraft(conversation.getId(), draftId);
        List<DraftCostCalculationEngine.CalculatedScheme> schemes = draftCostCalculationEngine.calculate(draft);
        persistCostSnapshots(draft, schemes);
        updateDraftEstimates(draft, schemes, LocalDateTime.now());
        McpDraftCalculateResponse response = draftCostCalculationEngine.toApiResponse(schemes);
        response.setDraftId(draft.getId());
        return response;
    }

    @Transactional(rollbackFor = Exception.class)
    public McpDraftSubmitResponse submit(String sessionId, McpDraftSubmitRequest request) {
        FeConversation conversation = conversationService.requireBySessionId(sessionId);
        FeEmploymentDraft draft = requireReadyDraft(conversation.getId(), request.getDraftId());
        String schemeCode = request.getSelectedSchemeCode().trim().toUpperCase();
        if (!draftCostCalculationEngine.isAllowedSchemeCode(schemeCode)) {
            throw new BizException(ErrorCode.BAD_REQUEST, "不支持的方案编码：" + request.getSelectedSchemeCode());
        }
        FeDraftCostSnapshot snapshot = findLatestSnapshot(draft.getId(), schemeCode);
        if (snapshot == null) {
            throw new BizException(ErrorCode.BAD_REQUEST, "请先调用 calculate 获取方案后再提交");
        }

        LocalDateTime now = LocalDateTime.now();
        FeEmploymentOrder order = buildOrderFromDraft(draft, snapshot, now);
        order.setOrderNo("tmp_" + UUID.randomUUID().toString().replace("-", "").substring(0, 20));
        try {
            feEmploymentOrderMapper.insert(order);
        } catch (DuplicateKeyException ex) {
            throw new BizException(ErrorCode.BAD_REQUEST, "订单编号冲突，请重试");
        }
        order.setOrderNo(generateOrderNo(order.getId()));
        feEmploymentOrderMapper.updateById(order);

        FeEmploymentDraft draftUpdate = new FeEmploymentDraft();
        draftUpdate.setId(draft.getId());
        draftUpdate.setStatus(DB_STATUS_CONFIRMED);
        draftUpdate.setConfirmedAt(now);
        draftUpdate.setSelectedPlanId(snapshot.getPlanId());
        draftUpdate.setEstimatedTotal(snapshot.getEmployerTotalOutflow());
        draftUpdate.setEstimatedPerCapita(snapshot.getEmployerAvgCostPerWorker());
        draftUpdate.setUpdateTime(now);
        feEmploymentDraftMapper.updateById(draftUpdate);

        FeRegistrationBatch batch = registrationBatchService.createForOrder(order, draft, now);

        McpDraftSubmitResponse response = new McpDraftSubmitResponse();
        response.setDraftId(draft.getId());
        response.setStatus(SUBMIT_SUCCESS);
        response.setOrderId(order.getOrderNo());
        response.setBatchId(batch.getBatchNo());
        return response;
    }

    private FeEmploymentDraft requireDraftInConversation(Long conversationId, Long draftId) {
        if (draftId == null) {
            throw new BizException(ErrorCode.BAD_REQUEST, "draft_id 不能为空");
        }
        FeEmploymentDraft draft = feEmploymentDraftMapper.selectById(draftId);
        if (draft == null || !conversationId.equals(draft.getConversationId())) {
            throw new BizException(ErrorCode.NOT_FOUND, "草稿不存在或不属于当前会话");
        }
        return draft;
    }

    private FeEmploymentDraft requirePendingDraft(Long conversationId, Long draftId) {
        FeEmploymentDraft draft = requireDraftInConversation(conversationId, draftId);
        if (!DB_STATUS_PENDING.equals(draft.getStatus())) {
            throw new BizException(ErrorCode.BAD_REQUEST, "草稿已提交，不可继续操作");
        }
        return draft;
    }

    private FeEmploymentDraft requireReadyDraft(Long conversationId, Long draftId) {
        FeEmploymentDraft draft = requirePendingDraft(conversationId, draftId);
        List<String> missing = parseMissingFields(draft.getMissingFields());
        if (!missing.isEmpty()) {
            throw new BizException(ErrorCode.BAD_REQUEST,
                    "草稿信息不完整，请先补全字段：" + String.join(", ", missing));
        }
        return draft;
    }

    private void persistCostSnapshots(FeEmploymentDraft draft,
                                      List<DraftCostCalculationEngine.CalculatedScheme> schemes) {
        feDraftCostSnapshotMapper.delete(new LambdaQueryWrapper<FeDraftCostSnapshot>()
                .eq(FeDraftCostSnapshot::getDraftId, draft.getId()));
        LocalDateTime now = LocalDateTime.now();
        for (DraftCostCalculationEngine.CalculatedScheme scheme : schemes) {
            FeDraftCostSnapshot snapshot = new FeDraftCostSnapshot();
            snapshot.setDraftId(draft.getId());
            snapshot.setPlanId(scheme.getPlanId());
            snapshot.setPlanCode(scheme.getCode());
            snapshot.setPlanName(scheme.getTitle());
            snapshot.setEmployerTotalOutflow(scheme.getEmployerTotalOutflow());
            snapshot.setEmployerAvgCostPerWorker(scheme.getEmployerAvgCostPerWorker());
            snapshot.setWorkerTakeHomePerWorker(scheme.getWorkerTakeHomePerWorker());
            snapshot.setIsRecommended(scheme.isRecommended());
            snapshot.setComplianceTags(writeJsonArray(scheme.getTags()));
            snapshot.setCalcVersion(draftCostCalculationEngine.calcVersion());
            snapshot.setCreateTime(now);
            feDraftCostSnapshotMapper.insert(snapshot);
        }
    }

    private void updateDraftEstimates(FeEmploymentDraft draft,
                                      List<DraftCostCalculationEngine.CalculatedScheme> schemes,
                                      LocalDateTime now) {
        DraftCostCalculationEngine.CalculatedScheme recommended = schemes.stream()
                .filter(DraftCostCalculationEngine.CalculatedScheme::isRecommended)
                .findFirst()
                .orElse(schemes.get(0));
        // 仅刷新人均预估；保留 estimated_total 作为劳务费基数（total_amount），避免二次测算被污染
        FeEmploymentDraft update = new FeEmploymentDraft();
        update.setId(draft.getId());
        update.setEstimatedPerCapita(recommended.getEmployerAvgCostPerWorker());
        update.setUpdateTime(now);
        feEmploymentDraftMapper.updateById(update);
    }

    private FeDraftCostSnapshot findLatestSnapshot(Long draftId, String schemeCode) {
        return feDraftCostSnapshotMapper.selectOne(new LambdaQueryWrapper<FeDraftCostSnapshot>()
                .eq(FeDraftCostSnapshot::getDraftId, draftId)
                .eq(FeDraftCostSnapshot::getPlanCode, schemeCode)
                .orderByDesc(FeDraftCostSnapshot::getCreateTime)
                .last("LIMIT 1"));
    }

    private FeEmploymentOrder buildOrderFromDraft(FeEmploymentDraft draft,
                                                  FeDraftCostSnapshot snapshot,
                                                  LocalDateTime now) {
        FeEmploymentOrder order = new FeEmploymentOrder();
        order.setEnterpriseId(draft.getEnterpriseId());
        order.setUserId(draft.getUserId());
        order.setDraftId(draft.getId());
        order.setSelectedPlanId(snapshot.getPlanId());
        order.setTitle(StringUtils.hasText(draft.getTitle()) ? draft.getTitle() : "用工订单");
        order.setWorkerCount(draft.getWorkerCount());
        order.setWorkDays(draft.getWorkDays());
        order.setWorkType(draft.getWorkType());
        order.setWorkLocation(draft.getWorkLocation());
        order.setWorkStartDate(draft.getWorkStartDate());
        order.setWorkEndDate(draft.getWorkEndDate());
        order.setOrderStatus(ORDER_STATUS_CONFIRMED);
        order.setCurrentStepCode(STEP_PLAN_CONFIRMED);
        order.setRegisteredCount(0);
        order.setPendingCount(draft.getWorkerCount());
        order.setAbnormalCount(0);
        order.setTotalOutflow(snapshot.getEmployerTotalOutflow());
        order.setCreateTime(now);
        order.setUpdateTime(now);
        order.setDelFlag(0);
        return order;
    }

    private static String generateOrderNo(Long orderId) {
        return String.format("ord_%d", orderId);
    }

    private String writeJsonArray(List<String> values) {
        try {
            return objectMapper.writeValueAsString(values);
        } catch (Exception ex) {
            return "[]";
        }
    }

    private void validateGenerateRequest(McpDraftGenerateRequest request) {
        if (request == null || !StringUtils.hasText(request.getSourceType())) {
            throw new BizException(ErrorCode.BAD_REQUEST, "source_type 不能为空");
        }
        String sourceType = request.getSourceType().trim().toLowerCase();
        if (!ALLOWED_SOURCE_TYPES.contains(sourceType)) {
            throw new BizException(ErrorCode.BAD_REQUEST, "不支持的 source_type：" + request.getSourceType());
        }
    }

    private FeEmploymentDraft buildInitialDraft(FeConversation conversation,
                                              FeEnterprise enterprise,
                                              McpDraftGenerateRequest request,
                                              LocalDateTime now) {
        FeEmploymentDraft draft = new FeEmploymentDraft();
        draft.setDraftNo(UUID.randomUUID().toString().replace("-", ""));
        draft.setEnterpriseId(enterprise.getId());
        draft.setUserId(conversation.getUserId());
        draft.setConversationId(conversation.getId());
        draft.setScenario(resolveScenario(request));
        if (StringUtils.hasText(conversation.getTitle())) {
            String title = conversation.getTitle().trim();
            draft.setTitle(title.length() <= 200 ? title : title.substring(0, 200));
        }
        draft.setStatus(DB_STATUS_PENDING);
        draft.setInsuranceRequired(Boolean.TRUE);
        draft.setCheckInRequired(Boolean.FALSE);
        draft.setQrValidDays(7);
        draft.setMissingFields("[]");
        draft.setRiskPrompts("[]");
        draft.setCreateTime(now);
        draft.setUpdateTime(now);
        return draft;
    }

    private String resolveScenario(McpDraftGenerateRequest request) {
        if ("clone".equalsIgnoreCase(request.getSourceType())) {
            return "existing_worker_regularization";
        }
        return "new_demand";
    }

    private void prefillFromEnterprise(FeEmploymentDraft draft, FeEnterprise enterprise, LocalDateTime now) {
        if (!StringUtils.hasText(enterprise.getRegisteredAddress())) {
            return;
        }
        draft.setWorkLocation(enterprise.getRegisteredAddress().trim());
        draft.setWorkLocationSource(WORK_LOCATION_SOURCE_ENTERPRISE);
        feEmploymentDraftMapper.updateById(draft);
        upsertField(draft.getId(), "work_location", "工作地点",
                draft.getWorkLocation(), SOURCE_ENTERPRISE_PROFILE, true, now);
    }

    private FeEmploymentDraft findPendingDraft(Long conversationId) {
        return feEmploymentDraftMapper.selectOne(new LambdaQueryWrapper<FeEmploymentDraft>()
                .eq(FeEmploymentDraft::getConversationId, conversationId)
                .eq(FeEmploymentDraft::getStatus, DB_STATUS_PENDING)
                .orderByDesc(FeEmploymentDraft::getCreateTime)
                .last("LIMIT 1"));
    }

    private Set<String> applyMcpFields(FeEmploymentDraft draft, Map<String, Object> fields) {
        if (fields == null || fields.isEmpty()) {
            throw new BizException(ErrorCode.BAD_REQUEST, "fields 不能为空");
        }
        Set<String> updatedKeys = new HashSet<>();
        for (Map.Entry<String, Object> entry : fields.entrySet()) {
            if (entry.getKey() == null) {
                continue;
            }
            String key = entry.getKey().trim();
            applySingleField(draft, key, entry.getValue());
            updatedKeys.add(key);
        }
        validateDraftDates(draft);
        return updatedKeys;
    }

    private void applySingleField(FeEmploymentDraft draft, String key, Object value) {
        switch (key) {
            case "worker_count":
                draft.setWorkerCount(toInteger(value, key));
                break;
            case "work_days":
                draft.setWorkDays(toInteger(value, key));
                break;
            case "total_amount":
                draft.setEstimatedTotal(toDecimal(value, key));
                break;
            case "daily_wage":
                draft.setDailyWage(toDecimal(value, key));
                break;
            case "work_type":
                draft.setWorkType(toStringValue(value));
                break;
            case "work_content":
                draft.setWorkContent(toStringValue(value));
                break;
            case "work_location":
                draft.setWorkLocation(toStringValue(value));
                draft.setWorkLocationSource("user_input");
                break;
            case "settlement_mode":
                String mode = toStringValue(value);
                if (StringUtils.hasText(mode) && !ALLOWED_SETTLEMENT_MODES.contains(mode)) {
                    throw new BizException(ErrorCode.BAD_REQUEST, "不支持的结算方式：" + mode);
                }
                draft.setSettlementMode(mode);
                break;
            case "job_requirements":
                draft.setJobRequirements(toStringValue(value));
                break;
            case "work_start_date":
                draft.setWorkStartDate(toLocalDate(value, key));
                break;
            case "work_end_date":
                draft.setWorkEndDate(toLocalDate(value, key));
                break;
            case "title":
                draft.setTitle(toStringValue(value));
                break;
            default:
                throw new BizException(ErrorCode.BAD_REQUEST, "不支持的字段：" + key);
        }
    }

    private void validateDraftDates(FeEmploymentDraft draft) {
        if (draft.getWorkStartDate() != null && draft.getWorkEndDate() != null
                && draft.getWorkEndDate().isBefore(draft.getWorkStartDate())) {
            throw new BizException(ErrorCode.BAD_REQUEST, "结束日期不能早于开工日期");
        }
    }

    private void recalculateMissingFields(FeEmploymentDraft draft, LocalDateTime now) {
        List<String> missing = new ArrayList<>();
        for (String key : DraftSchemaRegistry.requiredFieldKeys()) {
            if (isFieldEmpty(draft, key)) {
                missing.add(key);
            }
        }
        try {
            FeEmploymentDraft update = new FeEmploymentDraft();
            update.setId(draft.getId());
            update.setMissingFields(objectMapper.writeValueAsString(missing));
            update.setUpdateTime(now);
            feEmploymentDraftMapper.updateById(update);
            draft.setMissingFields(update.getMissingFields());
        } catch (Exception ex) {
            throw new BizException(ErrorCode.INTERNAL_ERROR);
        }
    }

    private boolean isFieldEmpty(FeEmploymentDraft draft, String key) {
        switch (key) {
            case "worker_count":
                return draft.getWorkerCount() == null || draft.getWorkerCount() <= 0;
            case "work_days":
                return draft.getWorkDays() == null || draft.getWorkDays() <= 0;
            case "total_amount":
                return draft.getEstimatedTotal() == null || draft.getEstimatedTotal().compareTo(BigDecimal.ZERO) <= 0;
            case "work_type":
                return !StringUtils.hasText(draft.getWorkType());
            case "work_location":
                return !StringUtils.hasText(draft.getWorkLocation());
            case "settlement_mode":
                return !StringUtils.hasText(draft.getSettlementMode());
            default:
                return true;
        }
    }

    private McpDraftGenerateResponse toGenerateResponse(FeEmploymentDraft draft) {
        List<String> missing = parseMissingFields(draft.getMissingFields());
        McpDraftGenerateResponse response = new McpDraftGenerateResponse();
        response.setDraftId(draft.getId());
        response.setSchema(DraftSchemaRegistry.buildSchema());
        response.setMissingFields(missing);
        response.setStatus(missing.isEmpty() ? MCP_STATUS_READY : MCP_STATUS_INCOMPLETE);
        return response;
    }

    private McpDraftInfoResponse toInfoResponse(FeEmploymentDraft draft) {
        List<String> missing = parseMissingFields(draft.getMissingFields());
        boolean editable = isDbEditable(draft);
        McpDraftInfoResponse response = new McpDraftInfoResponse();
        response.setDraftId(draft.getId());
        response.setData(toDataMap(draft));
        response.setMissingFields(missing);
        response.setEditable(editable);
        if (!DB_STATUS_PENDING.equals(draft.getStatus())) {
            response.setStatus(MCP_STATUS_SUBMITTED);
        } else {
            response.setStatus(missing.isEmpty() ? MCP_STATUS_READY : MCP_STATUS_INCOMPLETE);
        }
        return response;
    }

    private Map<String, Object> toDataMap(FeEmploymentDraft draft) {
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("draft_id", draft.getId());
        data.put("draft_no", draft.getDraftNo());
        data.put("user_id", draft.getUserId());
        data.put("scenario", draft.getScenario());
        data.put("title", draft.getTitle());
        data.put("worker_count", draft.getWorkerCount());
        data.put("work_days", draft.getWorkDays());
        data.put("work_type", draft.getWorkType());
        data.put("work_content", draft.getWorkContent());
        data.put("work_location", draft.getWorkLocation());
        data.put("work_location_source", draft.getWorkLocationSource());
        data.put("settlement_mode", draft.getSettlementMode());
        data.put("daily_wage", draft.getDailyWage());
        data.put("total_amount", draft.getEstimatedTotal());
        data.put("estimated_per_capita", draft.getEstimatedPerCapita());
        data.put("job_requirements", draft.getJobRequirements());
        data.put("work_start_date", draft.getWorkStartDate());
        data.put("work_end_date", draft.getWorkEndDate());
        data.put("insurance_required", draft.getInsuranceRequired());
        data.put("insurance_level", draft.getInsuranceLevel());
        data.put("check_in_required", draft.getCheckInRequired());
        data.put("qr_valid_days", draft.getQrValidDays());
        return data;
    }

    private boolean isDbEditable(FeEmploymentDraft draft) {
        return DB_STATUS_PENDING.equals(draft.getStatus());
    }

    private McpDraftUpdateResponse successResponse(Long draftId) {
        McpDraftUpdateResponse response = new McpDraftUpdateResponse();
        response.setDraftId(draftId);
        response.setStatus(MCP_UPDATE_SUCCESS);
        return response;
    }

    private McpDraftUpdateResponse banResponse(Long draftId) {
        McpDraftUpdateResponse response = new McpDraftUpdateResponse();
        response.setDraftId(draftId);
        response.setStatus(MCP_UPDATE_BAN);
        return response;
    }

    private void syncFieldMetadata(Long draftId,
                                   Set<String> updatedFieldKeys,
                                   FeEmploymentDraft draft,
                                   LocalDateTime now,
                                   String source) {
        Map<String, String> labels = DraftSchemaRegistry.fieldLabels();
        for (String fieldKey : updatedFieldKeys) {
            upsertField(draftId, fieldKey, labels.get(fieldKey), resolveFieldText(draft, fieldKey), source, false, now);
        }
    }

    private void upsertField(Long draftId,
                             String fieldKey,
                             String fieldLabel,
                             String fieldValue,
                             String source,
                             boolean isDefault,
                             LocalDateTime now) {
        FeDraftField existing = feDraftFieldMapper.selectOne(new LambdaQueryWrapper<FeDraftField>()
                .eq(FeDraftField::getDraftId, draftId)
                .eq(FeDraftField::getFieldKey, fieldKey)
                .last("LIMIT 1"));
        if (existing != null) {
            FeDraftField update = new FeDraftField();
            update.setId(existing.getId());
            update.setFieldValue(fieldValue);
            update.setSource(source);
            update.setIsDefault(isDefault);
            update.setUpdateTime(now);
            feDraftFieldMapper.updateById(update);
            return;
        }
        FeDraftField created = new FeDraftField();
        created.setDraftId(draftId);
        created.setFieldKey(fieldKey);
        created.setFieldLabel(fieldLabel);
        created.setFieldValue(fieldValue);
        created.setSource(source);
        created.setEditable(true);
        created.setIsDefault(isDefault);
        created.setCreateTime(now);
        created.setUpdateTime(now);
        feDraftFieldMapper.insert(created);
    }

    private String resolveFieldText(FeEmploymentDraft draft, String fieldKey) {
        switch (fieldKey) {
            case "worker_count":
                return draft.getWorkerCount() != null ? String.valueOf(draft.getWorkerCount()) : null;
            case "work_days":
                return draft.getWorkDays() != null ? String.valueOf(draft.getWorkDays()) : null;
            case "total_amount":
                return draft.getEstimatedTotal() != null ? draft.getEstimatedTotal().toPlainString() : null;
            case "daily_wage":
                return draft.getDailyWage() != null ? draft.getDailyWage().toPlainString() : null;
            case "work_type":
                return draft.getWorkType();
            case "work_content":
                return draft.getWorkContent();
            case "work_location":
                return draft.getWorkLocation();
            case "settlement_mode":
                return draft.getSettlementMode();
            case "job_requirements":
                return draft.getJobRequirements();
            default:
                return null;
        }
    }

    private List<String> parseMissingFields(String json) {
        if (!StringUtils.hasText(json)) {
            return Collections.emptyList();
        }
        try {
            List<String> parsed = objectMapper.readValue(json.trim(),
                    objectMapper.getTypeFactory().constructCollectionType(List.class, String.class));
            return parsed != null ? parsed : Collections.emptyList();
        } catch (Exception ex) {
            return Collections.emptyList();
        }
    }

    private static Integer toInteger(Object value, String key) {
        if (value == null) {
            return null;
        }
        if (value instanceof Number) {
            return ((Number) value).intValue();
        }
        try {
            return Integer.parseInt(String.valueOf(value).trim());
        } catch (NumberFormatException ex) {
            throw new BizException(ErrorCode.BAD_REQUEST, "字段 " + key + " 须为整数");
        }
    }

    private static BigDecimal toDecimal(Object value, String key) {
        if (value == null) {
            return null;
        }
        if (value instanceof BigDecimal) {
            return (BigDecimal) value;
        }
        if (value instanceof Number) {
            return BigDecimal.valueOf(((Number) value).doubleValue());
        }
        try {
            return new BigDecimal(String.valueOf(value).trim());
        } catch (NumberFormatException ex) {
            throw new BizException(ErrorCode.BAD_REQUEST, "字段 " + key + " 须为数值");
        }
    }

    private static LocalDate toLocalDate(Object value, String key) {
        if (value == null) {
            return null;
        }
        if (value instanceof LocalDate) {
            return (LocalDate) value;
        }
        try {
            return LocalDate.parse(String.valueOf(value).trim());
        } catch (Exception ex) {
            throw new BizException(ErrorCode.BAD_REQUEST, "字段 " + key + " 须为日期 yyyy-MM-dd");
        }
    }

    private static String toStringValue(Object value) {
        if (value == null) {
            return null;
        }
        String text = String.valueOf(value).trim();
        return text.isEmpty() ? null : text;
    }
}
