package jnpf.attendance.service.impl.preschedule;

import jnpf.attendance.schedule.ShiftPlanAssignmentResult;
import jnpf.attendance.schedule.ShiftPlanAssignmentResult.AssignedPostStaffView;
import jnpf.attendance.schedule.ShiftPlanAssignmentResult.AssignedShiftBlockView;
import jnpf.attendance.schedule.ShiftPlanAssignmentResult.AssignedStaffRef;
import jnpf.model.attendance.vo.WorkstationGroupUserVo;
import jnpf.model.attendance.vo.WorkstationWithUsersVo;
import jnpf.model.attendance.vo.scheduling.PreSchedulePostVo;
import jnpf.model.attendance.vo.scheduling.PreScheduleStationVo;
import jnpf.model.attendance.vo.scheduling.PreScheduleTableRowVo;
import jnpf.model.attendance.vo.scheduling.PreScheduleTableVo;
import jnpf.model.attendance.vo.scheduling.PreScheduleUserVo;
import jnpf.util.StringUtil;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * 将 {@link ShiftPlanAssignmentResult} 映射为预排班主表 VO。
 */
public final class PreScheduleResultMapper {

    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ISO_LOCAL_DATE;
    private static final DateTimeFormatter WEEK_FMT =
            DateTimeFormatter.ofPattern("EEEE", Locale.CHINA);

    private PreScheduleResultMapper() {
    }

    public static PreScheduleTableVo toTableVo(
            String groupId,
            LocalDate startDate,
            LocalDate endDate,
            Map<LocalDate, BigDecimal> revenueByDate,
            BigDecimal effTarget,
            ShiftPlanAssignmentResult assignmentResult,
            List<WorkstationWithUsersVo> workstations) {
        PreScheduleTableVo vo = new PreScheduleTableVo();
        vo.setGroupId(groupId);

        Map<String, String> positionNameByPostId = indexPositionNameByPostId(workstations);
        Map<String, List<String>> userWorkstationIds = indexUserWorkstationIds(workstations);
        Map<String, WorkstationWithUsersVo> workstationById = indexWorkstationById(workstations);

        List<PreScheduleTableRowVo> rows = new ArrayList<>();
        for (LocalDate day = startDate; !day.isAfter(endDate); day = day.plusDays(1)) {
            rows.add(
                    toRow(
                            day,
                            revenueByDate.get(day),
                            effTarget,
                            assignmentResult,
                            positionNameByPostId,
                            userWorkstationIds,
                            workstationById));
        }
        vo.setRows(rows);
        filterPostsAndStationsWithoutAnyUsers(vo);
        return vo;
    }

    /**
     * 剔除在整个日期区间内从未出现过人员的岗位 / 工作站；若仅在部分日期有人员、其余日期无人员或不在当行列表中，则保留。
     */
    static void filterPostsAndStationsWithoutAnyUsers(PreScheduleTableVo vo) {
        if (vo == null || vo.getRows() == null || vo.getRows().isEmpty()) {
            return;
        }

        Set<String> postIdsWithUsers = new HashSet<>();
        Set<String> stationIdsWithUsers = new HashSet<>();
        for (PreScheduleTableRowVo row : vo.getRows()) {
            collectPostIdsWithUsers(row.getPosts(), postIdsWithUsers);
            collectStationIdsWithUsers(row.getStations(), stationIdsWithUsers);
        }

        for (PreScheduleTableRowVo row : vo.getRows()) {
            row.setPosts(filterPostsByIdsWithUsers(row.getPosts(), postIdsWithUsers));
            row.setStations(filterStationsByIdsWithUsers(row.getStations(), stationIdsWithUsers));
        }
    }

    private static void collectPostIdsWithUsers(List<PreSchedulePostVo> posts, Set<String> idsWithUsers) {
        if (posts == null) {
            return;
        }
        for (PreSchedulePostVo post : posts) {
            if (post == null || StringUtil.isBlank(post.getId()) || !hasUsers(post.getUsers())) {
                continue;
            }
            idsWithUsers.add(post.getId().trim());
        }
    }

    private static void collectStationIdsWithUsers(
            List<PreScheduleStationVo> stations, Set<String> idsWithUsers) {
        if (stations == null) {
            return;
        }
        for (PreScheduleStationVo station : stations) {
            if (station == null || StringUtil.isBlank(station.getId()) || !hasUsers(station.getUsers())) {
                continue;
            }
            idsWithUsers.add(station.getId().trim());
        }
    }

    private static List<PreSchedulePostVo> filterPostsByIdsWithUsers(
            List<PreSchedulePostVo> posts, Set<String> idsWithUsers) {
        if (posts == null || posts.isEmpty()) {
            return Collections.emptyList();
        }
        List<PreSchedulePostVo> filtered = new ArrayList<>();
        for (PreSchedulePostVo post : posts) {
            if (post == null || StringUtil.isBlank(post.getId())) {
                continue;
            }
            if (idsWithUsers.contains(post.getId().trim())) {
                filtered.add(post);
            }
        }
        return filtered;
    }

    private static List<PreScheduleStationVo> filterStationsByIdsWithUsers(
            List<PreScheduleStationVo> stations, Set<String> idsWithUsers) {
        if (stations == null || stations.isEmpty()) {
            return Collections.emptyList();
        }
        List<PreScheduleStationVo> filtered = new ArrayList<>();
        for (PreScheduleStationVo station : stations) {
            if (station == null || StringUtil.isBlank(station.getId())) {
                continue;
            }
            if (idsWithUsers.contains(station.getId().trim())) {
                filtered.add(station);
            }
        }
        return filtered;
    }

    private static boolean hasUsers(List<PreScheduleUserVo> users) {
        if (users == null || users.isEmpty()) {
            return false;
        }
        for (PreScheduleUserVo user : users) {
            if (user != null && StringUtil.isNotBlank(user.getId())) {
                return true;
            }
        }
        return false;
    }

    private static PreScheduleTableRowVo toRow(
            LocalDate day,
            BigDecimal revenue,
            BigDecimal effTarget,
            ShiftPlanAssignmentResult assignmentResult,
            Map<String, String> positionNameByPostId,
            Map<String, List<String>> userWorkstationIds,
            Map<String, WorkstationWithUsersVo> workstationById) {
        PreScheduleTableRowVo row = new PreScheduleTableRowVo();
        row.setDate(day.format(DATE_FMT));
        row.setWeek(day.format(WEEK_FMT));
        row.setRevenue(revenue);
        row.setEffTarget(effTarget);

        List<AssignedShiftBlockView> dayShifts = filterShiftsByDay(assignmentResult, day);
        Map<String, String> userNameById = indexUserNames(dayShifts);
        Set<String> distinctEmployeeIds = new HashSet<>(userNameById.keySet());
        row.setPosts(buildPosts(dayShifts, positionNameByPostId));
        row.setStations(buildStations(distinctEmployeeIds, userNameById, userWorkstationIds, workstationById));
        enrichEfficiencyMetrics(row, distinctEmployeeIds.size());
        return row;
    }

    private static List<AssignedShiftBlockView> filterShiftsByDay(
            ShiftPlanAssignmentResult result, LocalDate day) {
        if (result == null || result.getByShift() == null || result.getByShift().isEmpty()) {
            return Collections.emptyList();
        }
        List<AssignedShiftBlockView> list = new ArrayList<>();
        for (AssignedShiftBlockView shift : result.getByShift()) {
            if (shift != null && day.equals(shift.getScheduleDay())) {
                list.add(shift);
            }
        }
        return list;
    }

    private static Map<String, String> indexUserNames(List<AssignedShiftBlockView> dayShifts) {
        Map<String, String> map = new HashMap<>();
        for (AssignedShiftBlockView shift : dayShifts) {
            for (AssignedPostStaffView post : shift.getPostAssignments()) {
                for (AssignedStaffRef staff : post.getStaff()) {
                    if (staff == null || StringUtil.isBlank(staff.getUserId())) {
                        continue;
                    }
                    String uid = staff.getUserId().trim();
                    map.putIfAbsent(uid, resolveUserName(staff));
                }
            }
        }
        return map;
    }

    private static List<PreSchedulePostVo> buildPosts(
            List<AssignedShiftBlockView> dayShifts, Map<String, String> positionNameByPostId) {
        Map<String, PreSchedulePostVo> postById = new TreeMap<>();

        for (AssignedShiftBlockView shift : dayShifts) {
            for (AssignedPostStaffView postView : shift.getPostAssignments()) {
                String postId = postView.getPostId();
                if (StringUtil.isBlank(postId)) {
                    continue;
                }
                String key = postId.trim();
                PreSchedulePostVo post =
                        postById.computeIfAbsent(
                                key,
                                id -> {
                                    PreSchedulePostVo p = new PreSchedulePostVo();
                                    p.setId(id);
                                    String name = positionNameByPostId.get(id);
                                    if (StringUtil.isBlank(name)) {
                                        name = postView.getPositionName();
                                    }
                                    p.setName(StringUtil.isNotBlank(name) ? name : id);
                                    p.setUsers(new ArrayList<>());
                                    return p;
                                });
                mergeStaffIntoPost(post, postView.getStaff());
            }
        }

        List<PreSchedulePostVo> posts = new ArrayList<>(postById.values());
        for (PreSchedulePostVo post : posts) {
            List<PreScheduleUserVo> users = dedupeAndSortUsers(post.getUsers());
            post.setUsers(users);
            post.setCount(users.size());
        }
        return posts;
    }

    private static void mergeStaffIntoPost(PreSchedulePostVo post, List<AssignedStaffRef> staffList) {
        if (staffList == null || staffList.isEmpty()) {
            return;
        }
        List<PreScheduleUserVo> users = post.getUsers();
        if (users == null) {
            users = new ArrayList<>();
            post.setUsers(users);
        }
        Set<String> existing = new HashSet<>();
        for (PreScheduleUserVo u : users) {
            if (u != null && StringUtil.isNotBlank(u.getId())) {
                existing.add(u.getId().trim());
            }
        }
        for (AssignedStaffRef staff : staffList) {
            if (staff == null || StringUtil.isBlank(staff.getUserId())) {
                continue;
            }
            String uid = staff.getUserId().trim();
            if (!existing.add(uid)) {
                continue;
            }
            PreScheduleUserVo user = new PreScheduleUserVo();
            user.setId(uid);
            user.setName(resolveUserName(staff));
            users.add(user);
        }
    }

    private static List<PreScheduleStationVo> buildStations(
            Set<String> assignedUserIds,
            Map<String, String> userNameById,
            Map<String, List<String>> userWorkstationIds,
            Map<String, WorkstationWithUsersVo> workstationById) {
        if (assignedUserIds == null || assignedUserIds.isEmpty()) {
            return Collections.emptyList();
        }
        Map<String, PreScheduleStationVo> stationById = new HashMap<>();

        for (String userId : assignedUserIds) {
            List<String> wsIds = userWorkstationIds.get(userId);
            if (wsIds == null || wsIds.isEmpty()) {
                continue;
            }
            for (String wsId : wsIds) {
                WorkstationWithUsersVo ws = workstationById.get(wsId);
                if (ws == null) {
                    continue;
                }
                PreScheduleStationVo station =
                        stationById.computeIfAbsent(
                                wsId,
                                id -> {
                                    PreScheduleStationVo s = new PreScheduleStationVo();
                                    s.setId(id);
                                    s.setName(
                                            StringUtil.isNotBlank(ws.getWorkstationName())
                                                    ? ws.getWorkstationName()
                                                    : id);
                                    s.setUsers(new ArrayList<>());
                                    return s;
                                });
                addUserToStation(station, userId, userNameById.get(userId));
            }
        }

        List<PreScheduleStationVo> stations = new ArrayList<>(stationById.values());
        for (PreScheduleStationVo station : stations) {
            station.setUsers(dedupeAndSortUsers(station.getUsers()));
        }
        stations.sort(Comparator.comparing(PreScheduleStationVo::getId, Comparator.nullsLast(String::compareTo)));
        return stations;
    }

    private static void addUserToStation(PreScheduleStationVo station, String userId, String userName) {
        List<PreScheduleUserVo> users = station.getUsers();
        if (users == null) {
            users = new ArrayList<>();
            station.setUsers(users);
        }
        for (PreScheduleUserVo existing : users) {
            if (existing != null && userId.equals(existing.getId())) {
                return;
            }
        }
        PreScheduleUserVo user = new PreScheduleUserVo();
        user.setId(userId);
        user.setName(StringUtil.isNotBlank(userName) ? userName : userId);
        users.add(user);
    }

    private static void enrichEfficiencyMetrics(PreScheduleTableRowVo row, int headcount) {
        row.setHeadcount(headcount);
        BigDecimal revenue = row.getRevenue();
        BigDecimal effTarget = row.getEffTarget();
        if (headcount <= 0) {
            BigDecimal effEst = BigDecimal.ZERO;
            row.setEffEst(effEst);
            row.setEffDiff(effTarget != null ? effTarget.subtract(effEst) : BigDecimal.ZERO.subtract(effEst));
            return;
        }
        if (revenue == null) {
            row.setEffEst(BigDecimal.ZERO);
            row.setEffDiff(null);
            return;
        }
        BigDecimal effEst = revenue.divide(BigDecimal.valueOf(headcount), 2, RoundingMode.HALF_UP);
        row.setEffEst(effEst);
        row.setEffDiff(effTarget != null ? effTarget.subtract(effEst) : null);
    }

    private static List<PreScheduleUserVo> dedupeAndSortUsers(List<PreScheduleUserVo> users) {
        if (users == null || users.isEmpty()) {
            return Collections.emptyList();
        }
        Map<String, PreScheduleUserVo> map = new TreeMap<>();
        for (PreScheduleUserVo u : users) {
            if (u == null || StringUtil.isBlank(u.getId())) {
                continue;
            }
            map.putIfAbsent(u.getId().trim(), u);
        }
        return new ArrayList<>(map.values());
    }

    private static String resolveUserName(AssignedStaffRef staff) {
        if (StringUtil.isNotBlank(staff.getUserName())) {
            return staff.getUserName().trim();
        }
        return staff.getUserId();
    }

    private static Map<String, String> indexPositionNameByPostId(List<WorkstationWithUsersVo> workstations) {
        Map<String, String> map = new HashMap<>();
        if (workstations == null) {
            return map;
        }
        for (WorkstationWithUsersVo ws : workstations) {
            if (ws == null || StringUtil.isBlank(ws.getPositionId())) {
                continue;
            }
            String postId = ws.getPositionId().trim();
            map.putIfAbsent(postId, ws.getPositionName());
        }
        return map;
    }

    private static Map<String, WorkstationWithUsersVo> indexWorkstationById(
            List<WorkstationWithUsersVo> workstations) {
        Map<String, WorkstationWithUsersVo> map = new HashMap<>();
        if (workstations == null) {
            return map;
        }
        for (WorkstationWithUsersVo ws : workstations) {
            if (ws == null || StringUtil.isBlank(ws.getWorkstationId())) {
                continue;
            }
            map.put(ws.getWorkstationId().trim(), ws);
        }
        return map;
    }

    /**
     * userId → 所属工作站 ID 列表（员工可在多站出现）。
     */
    private static Map<String, List<String>> indexUserWorkstationIds(List<WorkstationWithUsersVo> workstations) {
        Map<String, List<String>> map = new HashMap<>();
        if (workstations == null) {
            return map;
        }
        for (WorkstationWithUsersVo ws : workstations) {
            if (ws == null || StringUtil.isBlank(ws.getWorkstationId()) || ws.getUserList() == null) {
                continue;
            }
            String wsId = ws.getWorkstationId().trim();
            for (WorkstationGroupUserVo u : ws.getUserList()) {
                if (u == null || StringUtil.isBlank(u.getUserId())) {
                    continue;
                }
                String uid = u.getUserId().trim();
                map.computeIfAbsent(uid, k -> new ArrayList<>());
                List<String> ids = map.get(uid);
                if (!ids.contains(wsId)) {
                    ids.add(wsId);
                }
            }
        }
        return map;
    }
}
