package jnpf.attendance.schedule;

import jnpf.model.attendance.vo.ShiftPeriodVo;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 为 {@link ShiftPlanBlock} 绑定考勤组班次 id，或剔除无法匹配的班次块。
 *
 * <p>规则与 {@link AttendanceGroupShiftPeriodMatcher} 一致，并在 {@linkplain ShiftPlanBlock#getHistoricalAttendanceShiftId()} 存在且与某考勤班次 {@linkplain ShiftPeriodVo#getId()} 相同时，
 * 先于时段阈值绑定该班次 id。
 * <ul>
 *   <li>「可用」：覆盖度、有效率均为 1（计划时段与考勤班次完全对齐）。</li>
 *   <li>否则：历史覆盖度 = 重叠 / 计划时长 &gt; 阈值，且候选有效率 = 重叠 / 考勤班次总时长 ≥ 阈值（默认 70%、60%）。</li>
 *   <li>无满足项则从当日计划中移除该块。</li>
 * </ul>
 */
public final class ShiftPlanAttendanceShiftIdResolver {

    private ShiftPlanAttendanceShiftIdResolver() {}

    /**
     * @param shiftPlanByScheduleDay 按日完整班次计划（通常来自 {@link HalfHourDemandBasedShiftPlanBuilder}）
     * @param shiftPeriodVoList      考勤组班次配置；{@code null} 表示不处理（原样返回）；空列表则清空各日块
     * @return 仅保留能匹配到考勤班次的块，并写入 {@link ShiftPlanBlock#getShiftId()}
     */
    public static Map<LocalDate, List<ShiftPlanBlock>> assignGroupShiftIdsOrRemoveUnmatched(
            Map<LocalDate, List<ShiftPlanBlock>> shiftPlanByScheduleDay,
            List<ShiftPeriodVo> shiftPeriodVoList,
            AttendanceGroupShiftMatchConfig cfg,
            Random random) {
        if (shiftPlanByScheduleDay == null || shiftPlanByScheduleDay.isEmpty()) {
            return shiftPlanByScheduleDay == null
                    ? Collections.emptyMap()
                    : Collections.unmodifiableMap(new LinkedHashMap<>());
        }
        if (shiftPeriodVoList == null) {
            return Collections.unmodifiableMap(new LinkedHashMap<>(shiftPlanByScheduleDay));
        }
        if (shiftPeriodVoList.isEmpty()) {
            Map<LocalDate, List<ShiftPlanBlock>> empty = new LinkedHashMap<>();
            for (LocalDate day : shiftPlanByScheduleDay.keySet()) {
                empty.put(day, Collections.emptyList());
            }
            return Collections.unmodifiableMap(empty);
        }
        AttendanceGroupShiftMatchConfig c = cfg == null ? AttendanceGroupShiftMatchConfig.defaults() : cfg;
        Random rnd = random == null ? ThreadLocalRandom.current() : random;
        Map<LocalDate, List<ShiftPlanBlock>> out = new LinkedHashMap<>();
        for (Map.Entry<LocalDate, List<ShiftPlanBlock>> e : shiftPlanByScheduleDay.entrySet()) {
            List<ShiftPlanBlock> kept = new ArrayList<>();
            for (ShiftPlanBlock block : emptyIfNull(e.getValue())) {
                Optional<AttendanceGroupShiftMatchResult> match =
                        AttendanceGroupShiftPeriodMatcher.pickBestMatchForTimeWindow(
                                block.getStartMinuteInclusive(),
                                block.getEndMinuteExclusive(),
                                shiftPeriodVoList,
                                c,
                                rnd,
                                block.getHistoricalAttendanceShiftId());
                match.ifPresent(
                        r -> kept.add(block.withShiftId(r.getGroupShiftPeriodId())));
            }
            out.put(e.getKey(), Collections.unmodifiableList(kept));
        }
        return Collections.unmodifiableMap(out);
    }

    private static List<ShiftPlanBlock> emptyIfNull(List<ShiftPlanBlock> list) {
        return list == null ? Collections.emptyList() : list;
    }
}
