package com.njcn.rdms.module.project.service.performance.team;

import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.project.constant.PerformanceConstants;
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceRemindReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceRemindRespVO;
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceSummaryReqVO;
import com.njcn.rdms.module.project.controller.admin.performance.team.vo.TeamPerformanceSummaryRespVO;
import com.njcn.rdms.module.project.dal.dataobject.performance.PerformanceSheetDO;
import com.njcn.rdms.module.project.dal.mysql.performance.PerformanceSheetMapper;
import com.njcn.rdms.module.project.framework.notify.NotifySendEvent;
import com.njcn.rdms.module.project.framework.notify.NotifyTemplateCodeConstants;
import com.njcn.rdms.module.project.service.team.TeamDashboardAccessService;
import com.njcn.rdms.module.system.api.user.AdminUserApi;
import com.njcn.rdms.module.system.api.user.UserManagementRelationApi;
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
import com.njcn.rdms.module.system.enums.notify.NotifyMessageLevelConstants;
import jakarta.annotation.Resource;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.YearMonth;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;

@Service
public class TeamPerformanceServiceImpl implements TeamPerformanceService {

    @Resource
    private TeamDashboardAccessService teamDashboardAccessService;
    @Resource
    private PerformanceSheetMapper performanceSheetMapper;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private UserManagementRelationApi userManagementRelationApi;
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public TeamPerformanceSummaryRespVO getSummary(TeamPerformanceSummaryReqVO reqVO) {
        teamDashboardAccessService.validateTeamDashboardPermission(PerformanceConstants.PERMISSION_TEAM_DASHBOARD);
        String[] range = normalizePeriodMonthRange(
                reqVO == null ? null : reqVO.getPeriodMonthStart(),
                reqVO == null ? null : reqVO.getPeriodMonthEnd());
        List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
        List<PerformanceSheetDO> sheets = performanceSheetMapper
                .selectListByEmployeeIdsAndPeriodMonthRange(subordinateIds, range[0], range[1]);
        Map<Long, List<PerformanceSheetDO>> sheetsByEmployee = sheets.stream()
                .collect(Collectors.groupingBy(PerformanceSheetDO::getEmployeeId));
        Map<Long, AdminUserRespDTO> userMap = loadUserMap(subordinateIds);

        TeamPerformanceSummaryRespVO respVO = new TeamPerformanceSummaryRespVO();
        respVO.setPeriodMonthStart(range[0]);
        respVO.setPeriodMonthEnd(range[1]);
        respVO.setTotalSheetCount(sheets.size());
        respVO.setPendingSendUsers(buildPendingSendUsers(subordinateIds, sheetsByEmployee, userMap));
        respVO.setPendingConfirmUsers(buildPendingConfirmUsers(sheets, userMap));
        respVO.setPendingSendCount(respVO.getPendingSendUsers().size());
        respVO.setPendingConfirmCount(respVO.getPendingConfirmUsers().size());
        respVO.setConfirmedRate(calculateConfirmedRate(sheets));
        respVO.setDeptOrgAverages(buildDeptOrgAverages(sheets));
        return respVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public TeamPerformanceRemindRespVO remind(TeamPerformanceRemindReqVO reqVO) {
        teamDashboardAccessService.validateTeamDashboardPermission(PerformanceConstants.PERMISSION_TEAM_DASHBOARD);
        String[] range = normalizePeriodMonthRange(reqVO.getPeriodMonthStart(), reqVO.getPeriodMonthEnd());
        List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
        List<Long> targetEmployeeIds = reqVO.getUserIds() == null
                ? subordinateIds
                : teamDashboardAccessService.resolveRequestedSubordinateUserIds(
                        reqVO.getUserIds(), PerformanceConstants.PERMISSION_TEAM_DASHBOARD);
        List<PerformanceSheetDO> sheets = performanceSheetMapper
                .selectListByEmployeeIdsAndPeriodMonthRange(targetEmployeeIds, range[0], range[1]);
        Map<Long, List<PerformanceSheetDO>> sheetsByEmployee = sheets.stream()
                .collect(Collectors.groupingBy(PerformanceSheetDO::getEmployeeId));
        Map<Long, AdminUserRespDTO> userMap = loadUserMap(targetEmployeeIds);

        int remindedCount;
        if (PerformanceConstants.REMIND_TYPE_PENDING_CONFIRM.equals(reqVO.getRemindType())) {
            remindedCount = remindPendingConfirm(sheets);
        } else if (PerformanceConstants.REMIND_TYPE_PENDING_SEND.equals(reqVO.getRemindType())) {
            remindedCount = remindPendingSend(targetEmployeeIds, sheetsByEmployee, userMap, range[0]);
        } else {
            throw invalidParamException("催办类型不合法");
        }
        TeamPerformanceRemindRespVO respVO = new TeamPerformanceRemindRespVO();
        respVO.setRemindedCount(remindedCount);
        return respVO;
    }

    private List<TeamPerformanceSummaryRespVO.PendingSendUser> buildPendingSendUsers(
            List<Long> subordinateIds, Map<Long, List<PerformanceSheetDO>> sheetsByEmployee,
            Map<Long, AdminUserRespDTO> userMap) {
        List<TeamPerformanceSummaryRespVO.PendingSendUser> result = new ArrayList<>();
        for (Long subordinateId : subordinateIds) {
            List<PerformanceSheetDO> employeeSheets = sheetsByEmployee.getOrDefault(subordinateId, List.of());
            if (!hasPendingSend(employeeSheets)) {
                continue;
            }
            AdminUserRespDTO user = userMap.get(subordinateId);
            PerformanceSheetDO firstPending = employeeSheets.isEmpty() ? null : employeeSheets.get(0);
            AdminUserRespDTO manager = firstPending == null ? loadDirectManagerQuietly(subordinateId) : null;
            TeamPerformanceSummaryRespVO.PendingSendUser item = new TeamPerformanceSummaryRespVO.PendingSendUser();
            item.setUserId(subordinateId);
            item.setUserNickname(user == null ? "" : defaultText(user.getNickname()));
            item.setManagerUserId(
                    firstPending == null ? (manager == null ? null : manager.getId()) : firstPending.getManagerId());
            item.setManagerName(firstPending == null ? (manager == null ? "" : defaultText(manager.getNickname()))
                    : firstPending.getManagerName());
            item.setSheetId(firstPending == null ? null : firstPending.getId());
            item.setStatusCode(firstPending == null ? null : firstPending.getStatusCode());
            result.add(item);
        }
        return result;
    }

    private List<TeamPerformanceSummaryRespVO.PendingConfirmUser> buildPendingConfirmUsers(
            List<PerformanceSheetDO> sheets, Map<Long, AdminUserRespDTO> userMap) {
        return sheets.stream()
                .filter(sheet -> PerformanceConstants.STATUS_SENT.equals(sheet.getStatusCode()))
                .map(sheet -> {
                    AdminUserRespDTO user = userMap.get(sheet.getEmployeeId());
                    TeamPerformanceSummaryRespVO.PendingConfirmUser item = new TeamPerformanceSummaryRespVO.PendingConfirmUser();
                    item.setUserId(sheet.getEmployeeId());
                    item.setUserNickname(
                            user == null ? defaultText(sheet.getEmployeeName()) : defaultText(user.getNickname()));
                    item.setSheetId(sheet.getId());
                    item.setSentTime(sheet.getSentTime());
                    return item;
                })
                .toList();
    }

    private List<TeamPerformanceSummaryRespVO.DeptOrgAverage> buildDeptOrgAverages(List<PerformanceSheetDO> sheets) {
        Map<String, DeptAverageAccumulator> averages = new LinkedHashMap<>();
        for (PerformanceSheetDO sheet : sheets) {
            if (!PerformanceConstants.STATUS_CONFIRMED.equals(sheet.getStatusCode())
                    || sheet.getActualScoreTotal() == null
                    || sheet.getEmployeeDeptId() == null
                    || (!PerformanceConstants.ORG_TYPE_DIRECTION.equals(sheet.getDeptOrgType())
                            && !PerformanceConstants.ORG_TYPE_FUNCTION.equals(sheet.getDeptOrgType()))) {
                continue;
            }
            String key = sheet.getEmployeeDeptId() + ":" + sheet.getDeptOrgType();
            averages.computeIfAbsent(key, ignored -> new DeptAverageAccumulator(
                    sheet.getEmployeeDeptId(), sheet.getEmployeeDeptName(), sheet.getDeptOrgType()))
                    .add(sheet.getActualScoreTotal());
        }
        return averages.values().stream()
                .map(DeptAverageAccumulator::toRespVO)
                .toList();
    }

    private BigDecimal calculateConfirmedRate(List<PerformanceSheetDO> sheets) {
        if (sheets.isEmpty()) {
            return BigDecimal.ZERO.setScale(2);
        }
        long confirmed = sheets.stream()
                .filter(sheet -> PerformanceConstants.STATUS_CONFIRMED.equals(sheet.getStatusCode()))
                .count();
        return BigDecimal.valueOf(confirmed * 100)
                .divide(BigDecimal.valueOf(sheets.size()), 2, RoundingMode.HALF_UP);
    }

    private int remindPendingConfirm(List<PerformanceSheetDO> sheets) {
        int count = 0;
        for (PerformanceSheetDO sheet : sheets) {
            if (!PerformanceConstants.STATUS_SENT.equals(sheet.getStatusCode())) {
                continue;
            }
            publishNotice(List.of(sheet.getEmployeeId()),
                    NotifyTemplateCodeConstants.PERFORMANCE_PENDING_CONFIRM_REMIND, sheet, sheet.getEmployeeName());
            count++;
        }
        return count;
    }

    private int remindPendingSend(List<Long> targetEmployeeIds, Map<Long, List<PerformanceSheetDO>> sheetsByEmployee,
            Map<Long, AdminUserRespDTO> userMap, String defaultPeriodMonth) {
        int count = 0;
        for (Long employeeId : targetEmployeeIds) {
            List<PerformanceSheetDO> employeeSheets = sheetsByEmployee.getOrDefault(employeeId, List.of());
            if (!hasPendingSend(employeeSheets)) {
                continue;
            }
            PerformanceSheetDO firstPending = employeeSheets.isEmpty() ? null : employeeSheets.get(0);
            AdminUserRespDTO manager = firstPending == null ? loadDirectManagerQuietly(employeeId) : null;
            Long managerId = firstPending == null ? (manager == null ? null : manager.getId())
                    : firstPending.getManagerId();
            if (managerId == null) {
                continue;
            }
            String employeeName = firstPending == null
                    ? defaultText(userMap.get(employeeId) == null ? null : userMap.get(employeeId).getNickname())
                    : firstPending.getEmployeeName();
            PerformanceSheetDO noticeSheet = firstPending == null
                    ? buildVirtualSheet(defaultPeriodMonth, employeeId, employeeName, manager)
                    : firstPending;
            publishNotice(List.of(managerId),
                    NotifyTemplateCodeConstants.PERFORMANCE_PENDING_SEND_REMIND, noticeSheet, employeeName);
            count++;
        }
        return count;
    }

    private PerformanceSheetDO buildVirtualSheet(String periodMonth, Long employeeId, String employeeName,
            AdminUserRespDTO manager) {
        PerformanceSheetDO sheet = new PerformanceSheetDO();
        sheet.setPeriodMonth(periodMonth);
        sheet.setEmployeeId(employeeId);
        sheet.setEmployeeName(employeeName);
        sheet.setManagerId(manager == null ? null : manager.getId());
        sheet.setManagerName(manager == null ? "" : defaultText(manager.getNickname()));
        return sheet;
    }

    private void publishNotice(Collection<Long> recipients, String templateCode, PerformanceSheetDO sheet,
            String employeeName) {
        if (recipients == null || recipients.isEmpty()) {
            return;
        }
        Map<String, Object> params = new HashMap<>();
        params.put("periodMonth", sheet.getPeriodMonth());
        params.put("employeeName", employeeName);
        params.put("managerName", sheet.getManagerName());
        params.put("sheetId", sheet.getId());
        params.put("statusName", statusName(sheet.getStatusCode()));
        applicationEventPublisher.publishEvent(NotifySendEvent.of(new ArrayList<>(new LinkedHashSet<>(recipients)),
                templateCode, params, NotifyMessageLevelConstants.REMIND));
    }

    private Map<Long, AdminUserRespDTO> loadUserMap(Collection<Long> userIds) {
        if (userIds == null || userIds.isEmpty()) {
            return Map.of();
        }
        CommonResult<List<AdminUserRespDTO>> result = adminUserApi.getUserList(new LinkedHashSet<>(userIds));
        List<AdminUserRespDTO> users = result == null || result.getCheckedData() == null
                ? List.of()
                : result.getCheckedData();
        return users.stream()
                .filter(Objects::nonNull)
                .filter(user -> user.getId() != null)
                .collect(Collectors.toMap(AdminUserRespDTO::getId, user -> user, (left, right) -> left));
    }

    private AdminUserRespDTO loadDirectManagerQuietly(Long employeeId) {
        try {
            CommonResult<AdminUserRespDTO> result = userManagementRelationApi.getDirectManager(employeeId);
            return result == null ? null : result.getCheckedData();
        } catch (Exception ignored) {
            return null;
        }
    }

    /**
     * 校验并归一化月份区间，两者均为空时默认当前月。
     */
    private String[] normalizePeriodMonthRange(String start, String end) {
        String normalizedStart = StringUtils.hasText(start) ? normalizeSinglePeriodMonth(start) : null;
        String normalizedEnd = StringUtils.hasText(end) ? normalizeSinglePeriodMonth(end) : null;
        if (normalizedStart == null && normalizedEnd == null) {
            String currentMonth = YearMonth.now().toString();
            return new String[] { currentMonth, currentMonth };
        }
        if (normalizedStart != null && normalizedEnd != null && normalizedStart.compareTo(normalizedEnd) > 0) {
            throw invalidParamException("绩效月份起始不能晚于结束月份");
        }
        return new String[] { normalizedStart, normalizedEnd };
    }

    private String normalizeSinglePeriodMonth(String periodMonth) {
        try {
            return YearMonth.parse(periodMonth.trim()).toString();
        } catch (DateTimeParseException ex) {
            throw invalidParamException("绩效月份格式必须为 yyyy-MM");
        }
    }

    /**
     * 判断员工在区间内的绩效表是否存在“待发送”状态（无记录 / 含 draft / 含 rejected）。
     */
    private boolean hasPendingSend(List<PerformanceSheetDO> sheets) {
        if (sheets == null || sheets.isEmpty()) {
            return true;
        }
        return sheets.stream().anyMatch(sheet -> PerformanceConstants.STATUS_DRAFT.equals(sheet.getStatusCode())
                || PerformanceConstants.STATUS_REJECTED.equals(sheet.getStatusCode()));
    }

    private String statusName(String statusCode) {
        if (statusCode == null) {
            return "未创建";
        }
        return switch (statusCode) {
            case PerformanceConstants.STATUS_DRAFT -> "待发送";
            case PerformanceConstants.STATUS_SENT -> "待确认";
            case PerformanceConstants.STATUS_CONFIRMED -> "已确认";
            case PerformanceConstants.STATUS_REJECTED -> "已退回";
            default -> statusCode;
        };
    }

    private String defaultText(String text) {
        return StringUtils.hasText(text) ? text.trim() : "";
    }

    private static class DeptAverageAccumulator {

        private final Long deptId;
        private final String deptName;
        private final String deptOrgType;
        private BigDecimal total = BigDecimal.ZERO;
        private int count = 0;

        DeptAverageAccumulator(Long deptId, String deptName, String deptOrgType) {
            this.deptId = deptId;
            this.deptName = deptName;
            this.deptOrgType = deptOrgType;
        }

        void add(BigDecimal score) {
            total = total.add(score);
            count++;
        }

        TeamPerformanceSummaryRespVO.DeptOrgAverage toRespVO() {
            TeamPerformanceSummaryRespVO.DeptOrgAverage respVO = new TeamPerformanceSummaryRespVO.DeptOrgAverage();
            respVO.setDeptId(deptId);
            respVO.setDeptName(deptName);
            respVO.setDeptOrgType(deptOrgType);
            respVO.setConfirmedCount(count);
            respVO.setAverageScore(count == 0
                    ? BigDecimal.ZERO.setScale(2)
                    : total.divide(BigDecimal.valueOf(count), 2, RoundingMode.HALF_UP));
            return respVO;
        }
    }
}
