package jnpf.attendance.schedule;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

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

/**
 * 四、专岗/通岗：本岗位专岗优先；不足时用通岗；通岗同时在多岗位候选池时优先补<strong>缺口更大</strong>的岗位（文档「马六」示例）。
 */
public final class CrossPostGeneralStaffAllocator {

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

    private CrossPostGeneralStaffAllocator() {}

    /**
     * @param demandByPost        各岗位本时段仍缺人数（≥0）
     * @param specialistIdsByPost 专岗（本岗位绑定工作站）
     * @param generalIdsByPost    通岗
     * @param random              用于均衡打散；可为 null
     * @return 每岗位本轮分配到的员工 id（顺序无业务含义，每人至多出现一次）
     */
    public static Map<String, List<String>> allocateForOneTimeWindow(
            Map<String, Integer> demandByPost,
            Map<String, Set<String>> specialistIdsByPost,
            Map<String, Set<String>> generalIdsByPost,
            Random random) {
        if (demandByPost == null || demandByPost.isEmpty()) {
            return Collections.emptyMap();
        }
        Random rnd = random == null ? new Random() : random;
        SchedulingForTestCheckLog.info(
                LOG,
                SchedulingForTestCheckLog.TEST_ISSUE_3_STAFF_ASSIGNMENT,
                "cross_post_allocate_input",
                "algo=CrossPostGeneralStaffAllocator step=0_demand_and_pools | demandByPost="
                        + demandByPost
                        + " | specialistPools="
                        + stringifyIdSets(specialistIdsByPost)
                        + " | generalPools="
                        + stringifyIdSets(generalIdsByPost));

        Map<String, Integer> remaining = new LinkedHashMap<>();
        for (Map.Entry<String, Integer> e : demandByPost.entrySet()) {
            if (e.getKey() == null || e.getValue() == null) {
                continue;
            }
            remaining.put(e.getKey(), Math.max(0, e.getValue()));
        }

        Map<String, List<String>> assigned = new LinkedHashMap<>();
        for (String p : remaining.keySet()) {
            assigned.put(p, new ArrayList<>());
        }

        Set<String> used = new HashSet<>();
        Set<String> specialistAnywhere = specialistIdsAnywhere(specialistIdsByPost);

        // 1) 专岗：「桥接」人员（本岗专岗且在其他岗通岗池）优先排在本岗，避免先被其他岗通岗占走（文档马六→服务员）
        for (String post : remaining.keySet()) {
            List<String> specs =
                    shuffledBridgeFirst(
                            safeGet(specialistIdsByPost, post),
                            post,
                            generalIdsByPost,
                            rnd);
            int need = remaining.getOrDefault(post, 0);
            for (String s : specs) {
                if (need <= 0 || used.contains(s)) {
                    continue;
                }
                assigned.get(post).add(s);
                used.add(s);
                need--;
            }
            remaining.put(post, need);
        }
        SchedulingForTestCheckLog.info(
                LOG,
                SchedulingForTestCheckLog.TEST_ISSUE_3_STAFF_ASSIGNMENT,
                "cross_post_after_phase1_specialists",
                "algo=CrossPostGeneralStaffAllocator step=1_bridge_specialists_first | remainingDemand="
                        + remaining
                        + " | partialAssigned="
                        + assignedSnapshot(assigned));

        // 2) 同时出现在 ≥2 个仍有缺口岗位的「人员池」（专岗 ∪ 通岗）→ 优先补缺口大者（马六类场景）
        while (true) {
            List<String> postsWithNeed = new ArrayList<>();
            for (Map.Entry<String, Integer> e : remaining.entrySet()) {
                if (e.getValue() != null && e.getValue() > 0) {
                    postsWithNeed.add(e.getKey());
                }
            }
            if (postsWithNeed.isEmpty()) {
                break;
            }
            Set<String> sharedAcrossPosts =
                    findSharedAcrossPosts(specialistIdsByPost, generalIdsByPost, postsWithNeed, used);
            if (sharedAcrossPosts.isEmpty()) {
                break;
            }
            List<String> sharedList = new ArrayList<>(sharedAcrossPosts);
            Collections.shuffle(sharedList, rnd);
            String g = sharedList.get(0);
            String bestPost =
                    postsWithNeed.stream()
                            .max(
                                    Comparator.<String>comparingInt(
                                                    p -> remaining.getOrDefault(p, 0))
                                            .thenComparing(Comparator.naturalOrder()))
                            .orElse(null);
            if (bestPost == null) {
                break;
            }
            assigned.get(bestPost).add(g);
            used.add(g);
            remaining.put(bestPost, remaining.getOrDefault(bestPost, 0) - 1);
        }
        SchedulingForTestCheckLog.info(
                LOG,
                SchedulingForTestCheckLog.TEST_ISSUE_3_STAFF_ASSIGNMENT,
                "cross_post_after_phase2_shared_general",
                "algo=CrossPostGeneralStaffAllocator step=2_multi_post_shared_pool_max_gap_first | remainingDemand="
                        + remaining
                        + " | assignedSoFar="
                        + assignedSnapshot(assigned));

        // 3) 剩余通岗（只服务单岗位或共享已耗尽）
        for (String post : new ArrayList<>(remaining.keySet())) {
            int need = remaining.getOrDefault(post, 0);
            if (need <= 0) {
                continue;
            }
            List<String> gens = shuffledCopy(safeGet(generalIdsByPost, post), rnd);
            for (String g : gens) {
                if (need <= 0 || used.contains(g)) {
                    continue;
                }
                // 专岗身份不跨岗「做通岗」：避免把马六从服务员专岗挪去收银通岗
                if (specialistAnywhere.contains(g) && !safeGet(specialistIdsByPost, post).contains(g)) {
                    continue;
                }
                assigned.get(post).add(g);
                used.add(g);
                need--;
            }
            remaining.put(post, need);
        }

        SchedulingForTestCheckLog.info(
                LOG,
                SchedulingForTestCheckLog.TEST_ISSUE_3_STAFF_ASSIGNMENT,
                "cross_post_after_phase3_residual_generals",
                "algo=CrossPostGeneralStaffAllocator step=3_residual_generals | remainingDemand="
                        + remaining
                        + " | assigned="
                        + assignedSnapshot(assigned));

        Map<String, List<String>> out = new LinkedHashMap<>();
        for (Map.Entry<String, List<String>> e : assigned.entrySet()) {
            out.put(e.getKey(), Collections.unmodifiableList(e.getValue()));
        }
        return Collections.unmodifiableMap(out);
    }

    private static Map<String, List<String>> assignedSnapshot(Map<String, List<String>> assigned) {
        Map<String, List<String>> snap = new LinkedHashMap<>();
        if (assigned == null) {
            return snap;
        }
        for (Map.Entry<String, List<String>> e : assigned.entrySet()) {
            snap.put(e.getKey(), e.getValue() == null ? Collections.emptyList() : new ArrayList<>(e.getValue()));
        }
        return snap;
    }

    private static String stringifyIdSets(Map<String, Set<String>> byPost) {
        if (byPost == null || byPost.isEmpty()) {
            return "{}";
        }
        return byPost.toString();
    }

    private static Set<String> findSharedAcrossPosts(
            Map<String, Set<String>> specialistIdsByPost,
            Map<String, Set<String>> generalIdsByPost,
            List<String> postsWithNeed,
            Set<String> used) {
        Map<String, Integer> freq = new HashMap<>();
        for (String p : postsWithNeed) {
            Set<String> pool = new LinkedHashSet<>(safeGet(specialistIdsByPost, p));
            pool.addAll(safeGet(generalIdsByPost, p));
            for (String g : pool) {
                if (used.contains(g)) {
                    continue;
                }
                freq.merge(g, 1, Integer::sum);
            }
        }
        Set<String> shared = new LinkedHashSet<>();
        for (Map.Entry<String, Integer> e : freq.entrySet()) {
            if (e.getValue() != null && e.getValue() >= 2) {
                shared.add(e.getKey());
            }
        }
        return shared;
    }

    private static Set<String> safeGet(Map<String, Set<String>> map, String key) {
        if (map == null || key == null) {
            return Collections.emptySet();
        }
        Set<String> s = map.get(key);
        return s == null ? Collections.emptySet() : s;
    }

    private static List<String> shuffledCopy(Set<String> ids, Random rnd) {
        List<String> list = new ArrayList<>(ids);
        Collections.shuffle(list, rnd);
        return list;
    }

    private static Set<String> specialistIdsAnywhere(Map<String, Set<String>> specialistIdsByPost) {
        Set<String> out = new HashSet<>();
        if (specialistIdsByPost == null) {
            return out;
        }
        for (Set<String> s : specialistIdsByPost.values()) {
            if (s != null) {
                out.addAll(s);
            }
        }
        return out;
    }

    /**
     * 在本岗专岗列表中，把「同时出现在其他岗通岗池」的人排在前面（同层内再打散）。
     */
    private static List<String> shuffledBridgeFirst(
            Set<String> specialistIds,
            String post,
            Map<String, Set<String>> generalIdsByPost,
            Random rnd) {
        List<String> bridges = new ArrayList<>();
        List<String> others = new ArrayList<>();
        for (String id : specialistIds) {
            if (id == null) {
                continue;
            }
            if (isBridgeSpecialist(id, post, generalIdsByPost)) {
                bridges.add(id);
            } else {
                others.add(id);
            }
        }
        Collections.shuffle(bridges, rnd);
        Collections.shuffle(others, rnd);
        List<String> out = new ArrayList<>(bridges.size() + others.size());
        out.addAll(bridges);
        out.addAll(others);
        return out;
    }

    private static boolean isBridgeSpecialist(
            String employeeId, String homePost, Map<String, Set<String>> generalIdsByPost) {
        if (generalIdsByPost == null || homePost == null) {
            return false;
        }
        for (Map.Entry<String, Set<String>> e : generalIdsByPost.entrySet()) {
            String other = e.getKey();
            if (other == null || other.equals(homePost)) {
                continue;
            }
            Set<String> gen = e.getValue();
            if (gen != null && gen.contains(employeeId)) {
                return true;
            }
        }
        return false;
    }
}
