package jnpf.attendance.service.impl.preschedule;

import cn.hutool.core.collection.CollUtil;
import jnpf.attendance.schedule.ShiftPlanAssignmentResult;
import jnpf.attendance.schedule.ShiftPlanAssignmentResult.AssignedEmployeeView;
import jnpf.attendance.schedule.ShiftPlanAssignmentResult.AssignedShiftRef;
import jnpf.entity.attendance.AttendanceShiftNameEntity;
import jnpf.enums.attendance.SchedulesTypeEnum;
import jnpf.model.attendance.vo.SchedulesDayVo;
import jnpf.model.attendance.vo.SchedulesItemVo;
import jnpf.model.attendance.vo.SchedulesV2Vo;
import jnpf.util.DateDetail;
import jnpf.util.DateUtil;
import jnpf.util.StringUtil;

import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.BiFunction;

/**
 * 将 {@link ShiftPlanAssignmentResult#getByEmployee()}（{@link AssignedEmployeeView} 列表）
 * 转为 {@link SchedulesV2Vo}，供预排班草稿查询接口使用。
 *
 * <p>字段对应关系：
 * <ul>
 *   <li>{@link AssignedEmployeeView#getUserId()} → {@link SchedulesV2Vo#getUserId()}</li>
 *   <li>{@link AssignedEmployeeView#getUserName()} → {@link SchedulesV2Vo#getRealName()}</li>
 *   <li>{@link AssignedShiftRef#getScheduleDay()} → {@code val} 的 key（yyyy-MM-dd）</li>
 *   <li>{@link AssignedShiftRef#getTimeRangeText()}（如 {@code 9:00-14:00}）→ {@link SchedulesItemVo#getStart()} /
 *       {@link SchedulesItemVo#getEnd()}（统一为 {@code HH:mm}，与正式排班一致）、{@link SchedulesItemVo#getValidDuration()}</li>
 *   <li>{@link AssignedShiftRef} 其余字段 → {@link SchedulesItemVo}（附图字段；{@code postId} 不参与 V2 展示）</li>
 * </ul>
 */
public final class ByEmployeeSchedulesV2Converter {

    /** 与接口约定：itemVos.type 默认 1。 */
    private static final int ITEM_TYPE_DEFAULT = 2;
    /** 与接口约定：schedulesType 默认 0（全天）。 */
    private static final int SCHEDULES_TYPE_ALL_DAY = 0;
    private static final String LINE_SCHEDULE_NAME = "划线排班";
    /** 与 {@link DateDetail#DF10}、正式排班 {@code start}/{@code end} 展示一致。 */
    private static final DateTimeFormatter HM_STANDARD = DateTimeFormatter.ofPattern("HH:mm");

    private ByEmployeeSchedulesV2Converter() {}

    /**
     * 由 {@code byEmployee} 转为考勤组全员维度的 {@link SchedulesV2Vo} 列表（区间内每日补全）。
     */
    public static List<SchedulesV2Vo> toSchedulesV2List(
            List<AssignedEmployeeView> byEmployee,
            List<String> userIdsInOrder,
            Map<String, String> realNameByUserId,
            Map<String, AttendanceShiftNameEntity> shiftMap,
            List<Date> datesByPeriod,
            BiFunction<String, Date, SchedulesDayVo> dayVoBuilder) {
        if (CollUtil.isEmpty(userIdsInOrder)) {
            return Collections.emptyList();
        }
        Map<String, Map<String, List<AssignedShiftRef>>> shiftsByUserAndDay =
                indexShiftsByUserAndDay(byEmployee);
        mergeRealNamesFromByEmployee(byEmployee, realNameByUserId);

        List<SchedulesV2Vo> result = new ArrayList<>(userIdsInOrder.size());
        for (String userId : userIdsInOrder) {
            if (StringUtil.isEmpty(userId)) {
                continue;
            }
            String uid = userId.trim();
            SchedulesV2Vo vo = new SchedulesV2Vo();
            vo.setUserId(uid);
            vo.setRealName(realNameByUserId.getOrDefault(uid, ""));
            Map<String, SchedulesDayVo> val = new TreeMap<>();
            Map<String, List<AssignedShiftRef>> userDays =
                    shiftsByUserAndDay.getOrDefault(uid, Collections.emptyMap());
            for (Date day : datesByPeriod) {
                String dayKey = DateUtil.daFormat(day);
                SchedulesDayVo dayVo = dayVoBuilder.apply(uid, day);
                List<AssignedShiftRef> refs = userDays.get(dayKey);
                if (CollUtil.isNotEmpty(refs)) {
                    List<SchedulesItemVo> items = new ArrayList<>(refs.size());
                    for (AssignedShiftRef ref : refs) {
                        items.add(toSchedulesItemVo(ref, shiftMap, day));
                    }
                    items.sort(
                            Comparator.comparing(
                                    SchedulesItemVo::getStart, Comparator.nullsLast(String::compareTo)));
                    dayVo.setItemVos(items);
                    dayVo.setIsLineSchedule(
                            refs.stream().anyMatch(ref -> ref != null && !ref.isFixedScheduling()));
                } else if (CollUtil.isEmpty(dayVo.getItemVos())) {
                    dayVo.setItemVos(
                            List.of(
                                    SchedulesItemVo.builder()
                                            .type(SchedulesTypeEnum.NONE.getCode())
                                            .build()));
                }
                val.put(dayKey, dayVo);
            }
            vo.setVal(val);
            result.add(vo);
        }
        return result;
    }

    public static Map<String, Map<String, List<AssignedShiftRef>>> indexShiftsByUserAndDay(
            List<AssignedEmployeeView> byEmployee) {
        if (CollUtil.isEmpty(byEmployee)) {
            return Collections.emptyMap();
        }
        Map<String, Map<String, List<AssignedShiftRef>>> index = new LinkedHashMap<>();
        for (AssignedEmployeeView emp : byEmployee) {
            if (emp == null || StringUtil.isEmpty(emp.getUserId()) || CollUtil.isEmpty(emp.getShifts())) {
                continue;
            }
            String uid = emp.getUserId().trim();
            for (AssignedShiftRef ref : emp.getShifts()) {
                if (ref == null || ref.getScheduleDay() == null) {
                    continue;
                }
                String dayKey = ref.getScheduleDay().toString();
                index.computeIfAbsent(uid, k -> new LinkedHashMap<>())
                        .computeIfAbsent(dayKey, k -> new ArrayList<>())
                        .add(ref);
            }
        }
        return index;
    }

    public static Set<String> collectShiftIds(List<AssignedEmployeeView> byEmployee) {
        if (CollUtil.isEmpty(byEmployee)) {
            return Collections.emptySet();
        }
        Set<String> ids = new LinkedHashSet<>();
        for (AssignedEmployeeView emp : byEmployee) {
            if (emp == null || CollUtil.isEmpty(emp.getShifts())) {
                continue;
            }
            for (AssignedShiftRef ref : emp.getShifts()) {
                if (ref != null && StringUtil.isNotEmpty(ref.getShiftId())) {
                    ids.add(ref.getShiftId().trim());
                }
            }
        }
        return ids;
    }

    static void mergeRealNamesFromByEmployee(
            List<AssignedEmployeeView> byEmployee, Map<String, String> realNameByUserId) {
        if (CollUtil.isEmpty(byEmployee) || realNameByUserId == null) {
            return;
        }
        for (AssignedEmployeeView emp : byEmployee) {
            if (emp != null && StringUtil.isNotEmpty(emp.getUserId())) {
                realNameByUserId.putIfAbsent(
                        emp.getUserId().trim(),
                        emp.getUserName() == null ? "" : emp.getUserName());
            }
        }
    }

    public static SchedulesItemVo toSchedulesItemVo(
            AssignedShiftRef ref,
            Map<String, AttendanceShiftNameEntity> shiftMap,
            Date scheduleDay) {
        TimeRangeParts parts = parseTimeRangeText(ref.getTimeRangeText());
        SchedulesItemVo item = new SchedulesItemVo();
        item.setType(ITEM_TYPE_DEFAULT);
        item.setSchedulesType(SCHEDULES_TYPE_ALL_DAY);
        item.setStart(parts.start);
        item.setEnd(parts.end);
        item.setValidDuration(parts.validDurationMinutes);
        Date startPoint = combineDayAndTime(scheduleDay, parts.start);
        Date endPoint = combineDayAndTime(scheduleDay, parts.end);
        item.setIsStartTomorrow(isTomorrow(scheduleDay, startPoint));
        item.setIsEndTomorrow(isTomorrow(scheduleDay, endPoint));
        if (!ref.isFixedScheduling()) {
            item.setShiftId("");
            item.setName(LINE_SCHEDULE_NAME);
            return item;
        }
        item.setShiftId(ref.getShiftId() == null ? "" : ref.getShiftId());
        AttendanceShiftNameEntity shiftEntity =
                shiftMap == null ? null : shiftMap.get(ref.getShiftId());
        if (shiftEntity != null) {
            item.setName(StringUtil.isNotEmpty(shiftEntity.getName()) ? shiftEntity.getName() : "");
            item.setShortName(
                    StringUtil.isNotEmpty(shiftEntity.getShortName())
                            ? shiftEntity.getShortName()
                            : "");
            item.setColour(
                    StringUtil.isNotEmpty(shiftEntity.getColour()) ? shiftEntity.getColour() : "");
        } else {
            item.setName("");
            item.setShortName("");
            item.setColour("");
        }
        return item;
    }

    public static Integer normalizeExistStatus(Integer existStatus) {
        if (Objects.equals(existStatus, 4) || Objects.equals(existStatus, 3)) {
            return 1;
        }
        return existStatus;
    }

    private static Integer isTomorrow(Date day, Date date) {
        if (Objects.isNull(date)) {
            return 1;
        }
        Date nextDay = DateUtil.dateAddDays(day, 1);
        if (nextDay.before(date)) {
            return 2;
        }
        return 1;
    }

    private static Date combineDayAndTime(Date day, String hm) {
        if (day == null || StringUtil.isEmpty(hm)) {
            return null;
        }
        String dayStr = DateDetail.getDate2Str(day, DateDetail.DF);
        return DateUtil.stringToDates(dayStr + " " + hm.trim());
    }

    /**
     * 解析 {@code timeRangeText}，并将可识别的起止时间规范为 {@code HH:mm}（如 {@code 9:00} → {@code 09:00}）。
     */
    private static TimeRangeParts parseTimeRangeText(String timeRangeText) {
        TimeRangeParts parts = new TimeRangeParts();
        if (StringUtil.isEmpty(timeRangeText)) {
            return parts;
        }
        String trimmed = timeRangeText.trim();
        int dash = trimmed.indexOf('-');
        if (dash <= 0 || dash >= trimmed.length() - 1) {
            String normalized = formatMinuteToHm(parseHmToMinute(trimmed));
            parts.start = StringUtil.isNotEmpty(normalized) ? normalized : trimmed;
            return parts;
        }
        String rawStart = trimmed.substring(0, dash).trim();
        String rawEnd = trimmed.substring(dash + 1).trim();
        int startMin = parseHmToMinute(rawStart);
        int endMin = parseHmToMinute(rawEnd);
        if (endMin == 0 && startMin > 0) {
            endMin = 24 * 60;
        }
        if (startMin >= 0 && endMin > startMin) {
            parts.validDurationMinutes = endMin - startMin;
        } else if (startMin >= 0 && endMin >= 0) {
            parts.validDurationMinutes = Math.max(0, 24 * 60 - startMin + endMin);
        }
        parts.start = startMin >= 0 ? formatMinuteToHm(startMin) : rawStart;
        parts.end = endMin >= 0 ? formatMinuteToHm(endMin) : rawEnd;
        return parts;
    }

    /** 自然日分钟坐标 → {@code HH:mm}；1440 表示当日 24:00，展示为 {@code 00:00}。 */
    private static String formatMinuteToHm(int minutes) {
        if (minutes < 0) {
            return "";
        }
        if (minutes >= 24 * 60) {
            return LocalTime.MIDNIGHT.format(HM_STANDARD);
        }
        return LocalTime.of(minutes / 60, minutes % 60).format(HM_STANDARD);
    }

    private static int parseHmToMinute(String hm) {
        if (StringUtil.isEmpty(hm)) {
            return -1;
        }
        String[] seg = hm.split(":");
        if (seg.length < 2) {
            return -1;
        }
        try {
            int h = Integer.parseInt(seg[0].trim());
            int m = Integer.parseInt(seg[1].trim());
            if (h == 24 && m == 0) {
                return 24 * 60;
            }
            return h * 60 + m;
        } catch (NumberFormatException e) {
            return -1;
        }
    }

    private static final class TimeRangeParts {
        String start = "";
        String end = "";
        Integer validDurationMinutes;
    }
}
