package jnpf.attendance.service.impl.preschedule;

import jnpf.attendance.schedule.ShiftPlanAssignmentResult;
import jnpf.attendance.schedule.ShiftPlanAssignmentResult.IncompleteScheduleReason;
import jnpf.util.StringUtil;

import java.util.EnumMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * 将 {@link ShiftPlanAssignmentResult} 中排班/人员未满说明转为预排班主表简短 {@code msg}，
 * 按自动排班链路环节归类，便于前端展示「在哪一步出了问题」。
 */
public final class PreScheduleIncompleteMsgBuilder {

    private static final Map<PipelineStage, Set<String>> STAGE_REASON_CODES = buildStageReasonCodes();

    private PreScheduleIncompleteMsgBuilder() {}

    /**
     * 排班完整且无说明时返回 {@code null}；否则返回按排班环节归类的概略原因。
     */
    public static String build(ShiftPlanAssignmentResult result) {
        if (result == null) {
            return "排班未成功：引擎未返回结果，请稍后重试";
        }

        int picked = result.countAssignedStaff();
        int need = result.countTotalNeed();
        boolean hasReasons = result.hasIncompleteScheduleExplanations();
        boolean emptyRoster = result.getByShift().isEmpty();

        if (!hasReasons && !emptyRoster && (need <= 0 || picked >= need)) {
            return null;
        }

        Map<PipelineStage, Set<String>> stageCodes = groupReasonCodesByStage(result.getIncompleteScheduleReasons());
        String stepHints = formatStageHints(stageCodes);
        String example = pickOneReasonExample(result.getIncompleteScheduleReasons());

        if (emptyRoster && need <= 0) {
            return appendExample(
                    prefixOutcome("未生成班次计划", stepHints, "请检查历史排班数据、营业额与考勤组班次配置"),
                    example);
        }

        if (need > 0 && picked < need) {
            return appendExample(
                    prefixOutcome(
                            String.format("人员未排满（%d/%d）", picked, need),
                            stepHints,
                            "请检查工作站人员绑定与排班规则配置"),
                    example);
        }

        if (StringUtil.isNotBlank(stepHints)) {
            return appendExample(prefixOutcome("排班不完整", stepHints, null), example);
        }

        return emptyRoster ? "未生成班次计划：请检查历史排班数据与考勤组班次配置" : null;
    }

    private static String appendExample(String msg, String example) {
        if (StringUtil.isBlank(msg) || StringUtil.isBlank(example)) {
            return msg;
        }
        return msg + "。示例：" + example;
    }

    /**
     * 从结果中取一条最早环节的具体说明，便于用户对照真实数据排查。
     */
    private static String pickOneReasonExample(List<IncompleteScheduleReason> reasons) {
        if (reasons == null || reasons.isEmpty()) {
            return "";
        }
        IncompleteScheduleReason best = null;
        int bestOrder = Integer.MAX_VALUE;
        for (IncompleteScheduleReason reason : reasons) {
            if (reason == null || StringUtil.isBlank(reason.getMessage())) {
                continue;
            }
            PipelineStage stage = resolveStage(reason.getReasonCode());
            int order = stage == null ? Integer.MAX_VALUE - 1 : stage.order;
            if (order < bestOrder) {
                bestOrder = order;
                best = reason;
            }
        }
        if (best == null) {
            for (IncompleteScheduleReason reason : reasons) {
                if (reason != null && StringUtil.isNotBlank(reason.getMessage())) {
                    best = reason;
                    break;
                }
            }
        }
        return best == null ? "" : formatReasonExample(best);
    }

    private static String formatReasonExample(IncompleteScheduleReason reason) {
        StringBuilder sb = new StringBuilder();
        if (reason.getScheduleDay() != null) {
            sb.append(reason.getScheduleDay());
        }
        if (StringUtil.isNotBlank(reason.getTimeRangeText())) {
            if (sb.length() > 0) {
                sb.append(' ');
            }
            sb.append(reason.getTimeRangeText().trim());
        }
        if (StringUtil.isNotBlank(reason.getPostId())) {
            if (sb.length() > 0) {
                sb.append(' ');
            }
            sb.append("岗位").append(reason.getPostId().trim());
        }
        if (StringUtil.isNotBlank(reason.getMessage())) {
            if (sb.length() > 0) {
                sb.append('，');
            }
            sb.append(reason.getMessage().trim());
        }
        return sb.toString();
    }

    private static String prefixOutcome(String headline, String stepHints, String fallback) {
        StringBuilder sb = new StringBuilder(headline);
        if (StringUtil.isNotBlank(stepHints)) {
            sb.append("。问题环节：").append(stepHints);
        } else if (StringUtil.isNotBlank(fallback)) {
            sb.append("，").append(fallback);
        }
        return sb.toString();
    }

    private static Map<PipelineStage, Set<String>> groupReasonCodesByStage(
            List<IncompleteScheduleReason> reasons) {
        Map<PipelineStage, Set<String>> grouped = new EnumMap<>(PipelineStage.class);
        if (reasons == null) {
            return grouped;
        }
        for (IncompleteScheduleReason reason : reasons) {
            if (reason == null || StringUtil.isBlank(reason.getReasonCode())) {
                continue;
            }
            PipelineStage stage = resolveStage(reason.getReasonCode().trim());
            if (stage == null) {
                continue;
            }
            grouped.computeIfAbsent(stage, k -> new LinkedHashSet<>()).add(reason.getReasonCode().trim());
        }
        return grouped;
    }

    private static String formatStageHints(Map<PipelineStage, Set<String>> stageCodes) {
        if (stageCodes == null || stageCodes.isEmpty()) {
            return "";
        }
        TreeMap<Integer, String> ordered = new TreeMap<>();
        for (Map.Entry<PipelineStage, Set<String>> entry : stageCodes.entrySet()) {
            PipelineStage stage = entry.getKey();
            String hint = summarizeStage(stage, entry.getValue());
            if (StringUtil.isNotBlank(hint)) {
                ordered.put(stage.order, stage.label + "：" + hint);
            }
        }
        if (ordered.isEmpty()) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        int index = 1;
        for (String part : ordered.values()) {
            if (index > 1) {
                sb.append("；");
            }
            sb.append(index++).append('.').append(part);
        }
        return sb.toString();
    }

    private static String summarizeStage(PipelineStage stage, Set<String> codes) {
        if (codes == null || codes.isEmpty()) {
            return "";
        }
        LinkedHashSet<String> hints = new LinkedHashSet<>();
        for (String code : codes) {
            String hint = mapReasonCodeToStageHint(stage, code);
            if (StringUtil.isNotBlank(hint)) {
                hints.add(hint);
            }
        }
        return String.join("、", hints);
    }

    private static PipelineStage resolveStage(String reasonCode) {
        for (Map.Entry<PipelineStage, Set<String>> e : STAGE_REASON_CODES.entrySet()) {
            if (e.getValue().contains(reasonCode)) {
                return e.getKey();
            }
        }
        return null;
    }

    private static String mapReasonCodeToStageHint(PipelineStage stage, String reasonCode) {
        switch (reasonCode) {
            case ShiftPlanAssignmentResult.INCOMPLETE_PATTERN_HISTORY_INSUFFICIENT:
                return "历史手动排班不足，无法推算排班规律";
            case ShiftPlanAssignmentResult.INCOMPLETE_SIMILAR_HISTORY_NO_SHIFT_DATA:
                return "相似历史日缺少可参考的班次数据";
            case ShiftPlanAssignmentResult.INCOMPLETE_HISTORICAL_POST_MISSING_MASTER:
                return "历史排班引用的岗位已失效或已删除";
            case ShiftPlanAssignmentResult.INCOMPLETE_ATTEND_SHIFT_BLOCKS_DROPPED:
                return "班段时间与考勤组班次配置不一致，部分班段被移除";
            case ShiftPlanAssignmentResult.INCOMPLETE_NO_CANDIDATE:
                return "过滤后在组/划线资格下无可排人员";
            case ShiftPlanAssignmentResult.INCOMPLETE_WORKSTATION_POOL_EMPTY:
                return "候选人不落在工作站岗位绑定名单内";
            case ShiftPlanAssignmentResult.INCOMPLETE_FIXED_MUST_BLOCK:
                return "固定班「必须」规则拦截（工时/休息/连续上班等）";
            case ShiftPlanAssignmentResult.INCOMPLETE_RULES_OR_CONFLICT:
                return "排班硬约束、时段重叠或同人占用导致未满";
            default:
                return stage == null ? "" : "存在未识别的排班异常";
        }
    }

    private static Map<PipelineStage, Set<String>> buildStageReasonCodes() {
        Map<PipelineStage, Set<String>> map = new EnumMap<>(PipelineStage.class);
        map.put(
                PipelineStage.SIMILAR_DAYS,
                setOf(
                        ShiftPlanAssignmentResult.INCOMPLETE_PATTERN_HISTORY_INSUFFICIENT,
                        ShiftPlanAssignmentResult.INCOMPLETE_SIMILAR_HISTORY_NO_SHIFT_DATA));
        map.put(
                PipelineStage.POST_CONFIG,
                setOf(ShiftPlanAssignmentResult.INCOMPLETE_HISTORICAL_POST_MISSING_MASTER));
        map.put(
                PipelineStage.ATTEND_SHIFT_MATCH,
                setOf(ShiftPlanAssignmentResult.INCOMPLETE_ATTEND_SHIFT_BLOCKS_DROPPED));
        map.put(
                PipelineStage.STAFF_ASSIGN,
                setOf(
                        ShiftPlanAssignmentResult.INCOMPLETE_NO_CANDIDATE,
                        ShiftPlanAssignmentResult.INCOMPLETE_WORKSTATION_POOL_EMPTY,
                        ShiftPlanAssignmentResult.INCOMPLETE_FIXED_MUST_BLOCK,
                        ShiftPlanAssignmentResult.INCOMPLETE_RULES_OR_CONFLICT));
        return map;
    }

    @SafeVarargs
    private static <T> Set<T> setOf(T... items) {
        LinkedHashSet<T> set = new LinkedHashSet<>();
        if (items != null) {
            for (T item : items) {
                set.add(item);
            }
        }
        return set;
    }

    /** 自动排班主链路环节（与 {@link jnpf.attendance.schedule.AutoSchedulePipelineLog} 阶段对应）。 */
    private enum PipelineStage {
        SIMILAR_DAYS(1, "相似日匹配"),
        POST_CONFIG(2, "岗位配置"),
        ATTEND_SHIFT_MATCH(3, "考勤班次匹配"),
        STAFF_ASSIGN(4, "人员分配");

        private final int order;
        private final String label;

        PipelineStage(int order, String label) {
            this.order = order;
            this.label = label;
        }
    }
}
