package jnpf.attendance.schedule;

import jnpf.model.attendance.vo.scheduling.FixedSchedulingRuleVo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 第十步核心：<strong>固定排班</strong>规则下的「必须 / 尽量」分级与按序放松。
 * <p>
 * 被 {@link ScheduleStaffAssignmentSteps910#pickForPostDemand} 与
 * {@link ShiftPlanFinalStaffAssigner}（内联等价逻辑）调用。
 * <p>
 * <b>划线排班不使用本类</b>（调用方传 {@code fixedRules=null} 时 {@link #applyFilters} 仅过第九步硬约束）。
 * <p>
 * <b>放松顺序</b>（仅 priority=尽量 的已启用项）：见 {@link FixedSoftRuleKind#relaxOrder()}。
 */
public final class FixedSoftConstraintRelaxationPlanner {

    private static final Logger LOG = LoggerFactory.getLogger(FixedSoftConstraintRelaxationPlanner.class);

    private FixedSoftConstraintRelaxationPlanner() {}

    /**
     * 在固定排班规则下筛选并<strong>最多返回 {@code targetCount} 人</strong>。
     * <p>
     * <b>步骤</b>
     * <ol>
     *   <li>若存在固定规则且候选非空：{@link #filterHardAndMustPriorityRulesOnly} —
     *       无人通过「硬约束 + 全部必须项」则<strong>直接返回空</strong>（本班次不排，放松尽量亦无效）；</li>
     *   <li>循环：{@link #applyFilters} 得到当前放松集合下的合格池；
     *       若 {@code pool.size() >= targetCount} → {@link #shufflePick} 取目标人数；</li>
     *   <li>人数仍不足 → {@link #nextRelaxableKind} 放开下一档「尽量」规则，加入 {@code relaxed} 后重试；</li>
     *   <li>无可再放规则 → 对当前池 {@link #shufflePick} 取 min(targetCount, pool.size())（有多少排多少）。</li>
     * </ol>
     *
     * @param targetCount          目标人数上限
     * @param candidateEmployeeIds 候选员工 ID（顺序会被 shuffle，不保证专岗优先）
     * @param fixedRules           固定排班规则 VO；null 表示仅应用第九步硬约束
     * @param port                 规则判定端口
     * @param context              本段班次上下文（Assigner 场景下为「代表」上下文；每人单独判断见 Assigner 内联逻辑）
     * @param random               抽样打乱用
     */
    public static List<String> pickWithRelaxation(
            int targetCount,
            List<String> candidateEmployeeIds,
            FixedSchedulingRuleVo fixedRules,
            StaffRuleEvaluationPort port,
            StaffAssignmentContext context,
            Random random) {
        Objects.requireNonNull(port, "port");
        Objects.requireNonNull(context, "context");
        if (targetCount <= 0) {
            return Collections.emptyList();
        }
        Random rnd = random == null ? new Random() : random;
        List<String> ids = candidateEmployeeIds == null ? Collections.emptyList() : candidateEmployeeIds;

        // 前置：必须规则下是否至少有一人可选
        if (fixedRules != null && !ids.isEmpty()) {
            List<String> afterMust = filterHardAndMustPriorityRulesOnly(ids, fixedRules, port, context);
            if (afterMust.isEmpty()) {
                return Collections.emptyList();
            }
        }

        Set<FixedSoftRuleKind> relaxed = new LinkedHashSet<>();
        while (true) {
            List<String> pool = applyFilters(ids, fixedRules, port, context, relaxed);
            if (pool.size() >= targetCount) {
                return shufflePick(pool, targetCount, rnd);
            }
            FixedSoftRuleKind next = nextRelaxableKind(fixedRules, relaxed);
            if (next == null) {
                return shufflePick(pool, Math.min(targetCount, pool.size()), rnd);
            }
            relaxed.add(next);
            SchedulingForTestCheckLog.info(
                    LOG,
                    SchedulingForTestCheckLog.TEST_ISSUE_3_STAFF_ASSIGNMENT,
                    "staff_assign_soft_constraint_relax_planner",
                    String.format(
                            "algo=FixedSoftConstraintRelaxationPlanner.pickWithRelaxation | scheduleDay=%s | postId=%s | groupId=%s | slotLabel=%s "
                                    + "| targetCount=%d | poolAfterFilters=%d | newlyRelaxedSoftRule=%s | relaxedAccumulated=%s",
                            context.getScheduleDay(),
                            context.getPostId(),
                            context.getGroupId(),
                            context.getSlotRangeLabel(),
                            targetCount,
                            pool.size(),
                            next.name(),
                            String.valueOf(relaxed)));
        }
    }

    /**
     * 仅保留通过「第九步硬约束 + 全部启用的必须级固定规则」的候选人。
     * <p>
     * 用于判断：若结果为空，则禁止通过放松「尽量」规则增加人选。
     */
    private static List<String> filterHardAndMustPriorityRulesOnly(
            List<String> candidateEmployeeIds,
            FixedSchedulingRuleVo fixedRules,
            StaffRuleEvaluationPort port,
            StaffAssignmentContext context) {
        List<String> out = new ArrayList<>();
        for (String id : candidateEmployeeIds) {
            if (id == null || id.trim().isEmpty()) {
                continue;
            }
            if (!port.passesOperationalHardConstraints(id, context)) {
                continue;
            }
            if (!passesMustPriorityFixedRulesOnly(id, fixedRules, port, context)) {
                continue;
            }
            out.add(id);
        }
        return out;
    }

    /** 该员工是否满足全部「已启用且 priority=必须」的 {@link FixedSoftRuleKind}。 */
    private static boolean passesMustPriorityFixedRulesOnly(
            String employeeId,
            FixedSchedulingRuleVo fixedRules,
            StaffRuleEvaluationPort port,
            StaffAssignmentContext context) {
        if (fixedRules == null) {
            return true;
        }
        for (FixedSoftRuleKind kind : FixedSoftRuleKind.relaxOrder()) {
            if (!kind.isEnabled(fixedRules)) {
                continue;
            }
            if (!kind.isMustPriority(fixedRules)) {
                continue;
            }
            if (kind.violates(employeeId, context, port)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 从合格池中随机抽取至多 {@code n} 人（打乱后取前 n 个）。
     */
    private static List<String> shufflePick(List<String> pool, int n, Random rnd) {
        if (pool.isEmpty() || n <= 0) {
            return Collections.emptyList();
        }
        List<String> copy = new ArrayList<>(pool);
        Collections.shuffle(copy, rnd);
        return Collections.unmodifiableList(new ArrayList<>(copy.subList(0, Math.min(n, copy.size()))));
    }

    /**
     * 在当前放松集合 {@code relaxed} 下，过滤出通过硬约束 + 固定规则（必须 + 未放松的尽量）的候选人。
     */
    private static List<String> applyFilters(
            List<String> candidateEmployeeIds,
            FixedSchedulingRuleVo fixedRules,
            StaffRuleEvaluationPort port,
            StaffAssignmentContext context,
            Set<FixedSoftRuleKind> relaxed) {
        List<String> out = new ArrayList<>();
        for (String id : candidateEmployeeIds) {
            if (id == null || id.trim().isEmpty()) {
                continue;
            }
            if (!port.passesOperationalHardConstraints(id, context)) {
                continue;
            }
            if (!passesFixedSchedulingRules(id, fixedRules, port, context, relaxed)) {
                continue;
            }
            out.add(id);
        }
        return out;
    }

    /**
     * 固定排班规则综合判定：必须项始终检查；尽量项在未列入 {@code relaxed} 时检查。
     */
    private static boolean passesFixedSchedulingRules(
            String employeeId,
            FixedSchedulingRuleVo fixedRules,
            StaffRuleEvaluationPort port,
            StaffAssignmentContext context,
            Set<FixedSoftRuleKind> relaxed) {
        if (fixedRules == null) {
            return true;
        }
        for (FixedSoftRuleKind kind : FixedSoftRuleKind.relaxOrder()) {
            if (!kind.isEnabled(fixedRules)) {
                continue;
            }
            boolean must = kind.isMustPriority(fixedRules);
            if (must) {
                if (kind.violates(employeeId, context, port)) {
                    return false;
                }
                continue;
            }
            if (relaxed.contains(kind)) {
                continue;
            }
            if (kind.violates(employeeId, context, port)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 下一档可放松的「尽量」规则（已启用、非必须、尚未在 {@code relaxed} 中）。
     */
    private static FixedSoftRuleKind nextRelaxableKind(
            FixedSchedulingRuleVo fixedRules, Set<FixedSoftRuleKind> relaxed) {
        if (fixedRules == null) {
            return null;
        }
        for (FixedSoftRuleKind kind : FixedSoftRuleKind.relaxOrder()) {
            if (!kind.isEnabled(fixedRules)) {
                continue;
            }
            if (kind.isMustPriority(fixedRules)) {
                continue;
            }
            if (relaxed.contains(kind)) {
                continue;
            }
            return kind;
        }
        return null;
    }
}
