package jnpf.attendance.schedule;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 根据第六～八步 {@link ScheduleDemandCoverResult}（固定班贪心 picks、划线段）与
 * {@link WorkstationPostStaffBuckets} 生成「日期 + 班次 + 人员」列表，便于测试/报表核对。
 *
 * <p>分配规则（刻意简单、确定性）：按当日时段起点排序后依次落人；专岗优先于通岗；同一用户同一日不允许时段重叠。
 * 若该岗池内无人可用则使用 {@code UNASSIGNED|&lt;postId&gt;}。
 */
public final class FinalScheduleRosterFromCoverBuilder {

    private static final String UNASSIGNED_PREFIX = "UNASSIGNED|";

    private FinalScheduleRosterFromCoverBuilder() {}

    public static List<FinalScheduleRosterLine> build(
            Map<LocalDate, ScheduleDemandCoverResult> coverByDay,
            Map<LocalDate, HalfHourPostDemandMatrix> matrices,
            WorkstationPostStaffBuckets buckets) {
        if (coverByDay == null || coverByDay.isEmpty()) {
            return Collections.emptyList();
        }
        List<LocalDate> days = new ArrayList<>(coverByDay.keySet());
        Collections.sort(days);
        List<FinalScheduleRosterLine> out = new ArrayList<>();
        for (LocalDate day : days) {
            ScheduleDemandCoverResult cov = coverByDay.get(day);
            HalfHourPostDemandMatrix matrix = matrices == null ? null : matrices.get(day);
            if (cov == null || matrix == null) {
                continue;
            }
            out.addAll(buildForOneDay(day, cov, matrix, buckets));
        }
        return Collections.unmodifiableList(out);
    }

    private static List<FinalScheduleRosterLine> buildForOneDay(
            LocalDate day,
            ScheduleDemandCoverResult cov,
            HalfHourPostDemandMatrix matrix,
            WorkstationPostStaffBuckets buckets) {
        List<PendingShift> pending = new ArrayList<>();
        List<String> posts = matrix.getPostIdsOrdered();
        List<String> slots = matrix.getSlotRangeLabelsOrdered();
        for (String postId : posts) {
            FixedShiftCoverOutcome fo = cov.getFixedShiftCoverByPost().get(postId);
            if (fo != null) {
                for (PostFixedShiftCandidate c : fo.getPicksInOrder()) {
                    if (c == null) {
                        continue;
                    }
                    pending.add(
                            new PendingShift(
                                    postId,
                                    FinalScheduleRosterLine.KIND_FIXED,
                                    c.getNormalizedShiftLabel(),
                                    c.getStartMinuteInclusive(),
                                    c.getEndMinuteExclusive()));
                }
            }
            List<LineShiftPick> linePicks = cov.getLineShiftPicksByPost().get(postId);
            if (linePicks != null) {
                for (LineShiftPick lp : linePicks) {
                    if (lp == null) {
                        continue;
                    }
                    int[] range = linePickToMinuteRange(slots, lp);
                    String slotLabel = linePickSlotLabel(slots, lp);
                    pending.add(
                            new PendingShift(
                                    postId,
                                    FinalScheduleRosterLine.KIND_LINE,
                                    slotLabel,
                                    range[0],
                                    range[1]));
                }
            }
        }
        pending.sort(PENDING_ORDER);

        Map<String, List<int[]>> busyByUser = new HashMap<>();
        List<FinalScheduleRosterLine> lines = new ArrayList<>(pending.size());
        for (PendingShift p : pending) {
            String uid = pickEmployee(p.postId, p.start, p.end, buckets, busyByUser);
            lines.add(
                    new FinalScheduleRosterLine(
                            day, p.postId, p.kind, p.shiftLabel, p.start, p.end, uid));
        }
        return lines;
    }

    private static final Comparator<PendingShift> PENDING_ORDER =
            Comparator.comparingInt((PendingShift p) -> p.start)
                    .thenComparingInt((PendingShift p) -> -p.end)
                    .thenComparing(p -> p.kind.equals(FinalScheduleRosterLine.KIND_FIXED) ? 0 : 1)
                    .thenComparing(p -> p.postId);

    private static int[] linePickToMinuteRange(List<String> slots, LineShiftPick lp) {
        int i = lp.getStartSlotIndexInclusive();
        int j = lp.getEndSlotIndexExclusive();
        if (slots == null || slots.isEmpty() || i < 0 || j <= i || i >= slots.size()) {
            return new int[] {0, 0};
        }
        int last = Math.min(j - 1, slots.size() - 1);
        int[] a = HalfHourSlotLabel.startEndExclusiveMinutes(slots.get(i));
        int[] b = HalfHourSlotLabel.startEndExclusiveMinutes(slots.get(last));
        return new int[] {a[0], b[1]};
    }

    private static String linePickSlotLabel(List<String> slots, LineShiftPick lp) {
        int i = lp.getStartSlotIndexInclusive();
        int j = lp.getEndSlotIndexExclusive();
        if (slots == null || slots.isEmpty() || i < 0 || j <= i || i >= slots.size()) {
            return "LINE[" + i + "," + j + ")";
        }
        int last = Math.min(j - 1, slots.size() - 1);
        return slots.get(i) + "~" + slots.get(last);
    }

    private static String pickEmployee(
            String postId,
            int startInclusive,
            int endExclusive,
            WorkstationPostStaffBuckets buckets,
            Map<String, List<int[]>> busyByUser) {
        Objects.requireNonNull(busyByUser, "busyByUser");
        List<String> pool = new ArrayList<>();
        if (buckets != null) {
            pool.addAll(buckets.getSpecialistIds(postId));
            pool.addAll(buckets.getGeneralIds(postId));
        }
        for (String uid : pool) {
            if (uid == null || uid.isEmpty()) {
                continue;
            }
            if (!overlapsExisting(busyByUser.get(uid), startInclusive, endExclusive)) {
                busyByUser.computeIfAbsent(uid, k -> new ArrayList<>()).add(new int[] {startInclusive, endExclusive});
                return uid;
            }
        }
        return UNASSIGNED_PREFIX + postId;
    }

    private static boolean overlapsExisting(List<int[]> intervals, int startInclusive, int endExclusive) {
        if (intervals == null || intervals.isEmpty()) {
            return false;
        }
        for (int[] iv : intervals) {
            if (iv == null || iv.length < 2) {
                continue;
            }
            if (startInclusive < iv[1] && iv[0] < endExclusive) {
                return true;
            }
        }
        return false;
    }

    private static final class PendingShift {
        final String postId;
        final String kind;
        final String shiftLabel;
        final int start;
        final int end;

        PendingShift(String postId, String kind, String shiftLabel, int start, int end) {
            this.postId = postId == null ? "" : postId;
            this.kind = kind;
            this.shiftLabel = shiftLabel == null ? "" : shiftLabel;
            this.start = start;
            this.end = end;
        }
    }
}
