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

import com.njcn.rdms.framework.common.pojo.CommonResult;
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.dept.DeptApi;
import com.njcn.rdms.module.system.api.dept.dto.DeptRespDTO;
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 DeptApi deptApi;
    @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<String> periodMonths = buildPeriodMonths(range[0], range[1]);
        List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
        Map<Long, AdminUserRespDTO> userMap = loadUserMap(subordinateIds);
        Map<Long, DeptRespDTO> deptMap = loadDeptMap(userMap.values());
        List<PerformanceSheetDO> sheets = performanceSheetMapper
                .selectListByEmployeeIdsAndPeriodMonthRange(subordinateIds, range[0], range[1]);
        Map<String, PerformanceSheetDO> sheetBySlot = buildSheetBySlot(sheets);
        Map<Long, AdminUserRespDTO> directManagerCache = new HashMap<>();
        List<TeamPerformanceSummaryRespVO.PendingSendUser> pendingSendUsers = new ArrayList<>();
        List<TeamPerformanceSummaryRespVO.PendingConfirmUser> pendingConfirmUsers = new ArrayList<>();
        Map<String, DeptAverageAccumulator> deptAverageMap = new LinkedHashMap<>();
        int confirmedCount = 0;

        for (Long subordinateId : subordinateIds) {
            AdminUserRespDTO user = userMap.get(subordinateId);
            for (String periodMonth : periodMonths) {
                PerformanceSheetDO sheet = sheetBySlot.get(slotKey(subordinateId, periodMonth));
                DeptBucket deptBucket = resolveDeptBucket(sheet, user, deptMap);
                DeptAverageAccumulator accumulator = touchDeptAverageAccumulator(deptAverageMap, deptBucket);
                if (accumulator != null) {
                    accumulator.incrementExpectedCount();
                }
                if (isPendingSendStatus(sheet == null ? null : sheet.getStatusCode())) {
                    pendingSendUsers.add(buildPendingSendUser(
                            subordinateId, periodMonth, sheet, user, directManagerCache));
                    continue;
                }
                if (PerformanceConstants.STATUS_SENT.equals(sheet.getStatusCode())) {
                    pendingConfirmUsers.add(buildPendingConfirmUser(sheet, user, periodMonth));
                    continue;
                }
                if (PerformanceConstants.STATUS_CONFIRMED.equals(sheet.getStatusCode())) {
                    confirmedCount++;
                    if (accumulator != null) {
                        accumulator.addConfirmedScore(sheet.getActualScoreTotal());
                    }
                }
            }
        }

        int expectedSheetCount = subordinateIds.size() * periodMonths.size();
        TeamPerformanceSummaryRespVO respVO = new TeamPerformanceSummaryRespVO();
        respVO.setPeriodMonthStart(range[0]);
        respVO.setPeriodMonthEnd(range[1]);
        respVO.setExpectedSheetCount(expectedSheetCount);
        respVO.setPendingSendUsers(pendingSendUsers);
        respVO.setPendingConfirmUsers(pendingConfirmUsers);
        respVO.setPendingSendCount(pendingSendUsers.size());
        respVO.setPendingConfirmCount(pendingConfirmUsers.size());
        respVO.setConfirmedRate(calculateConfirmedRate(expectedSheetCount, confirmedCount));
        respVO.setDeptOrgAverages(deptAverageMap.values().stream()
                .map(DeptAverageAccumulator::toRespVO)
                .toList());
        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<String> periodMonths = buildPeriodMonths(range[0], range[1]);
        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<String, PerformanceSheetDO> sheetBySlot = buildSheetBySlot(sheets);
        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, periodMonths, sheetBySlot, userMap);
        } else {
            throw invalidParamException("催办类型不合法");
        }
        TeamPerformanceRemindRespVO respVO = new TeamPerformanceRemindRespVO();
        respVO.setRemindedCount(remindedCount);
        return respVO;
    }

    private BigDecimal calculateConfirmedRate(int expectedSheetCount, int confirmedCount) {
        if (expectedSheetCount <= 0) {
            return BigDecimal.ZERO.setScale(2);
        }
        return BigDecimal.valueOf(confirmedCount * 100L)
                .divide(BigDecimal.valueOf(expectedSheetCount), 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, List<String> periodMonths,
            Map<String, PerformanceSheetDO> sheetBySlot, Map<Long, AdminUserRespDTO> userMap) {
        int count = 0;
        Map<Long, AdminUserRespDTO> directManagerCache = new HashMap<>();
        for (Long employeeId : targetEmployeeIds) {
            PendingSendContext pendingContext = findPendingSendContext(employeeId, periodMonths, sheetBySlot);
            if (pendingContext == null) {
                continue;
            }
            PerformanceSheetDO pendingSheet = pendingContext.sheet();
            AdminUserRespDTO manager = pendingSheet == null
                    ? directManagerCache.computeIfAbsent(employeeId, this::loadDirectManagerQuietly)
                    : null;
            Long managerId = pendingSheet != null && pendingSheet.getManagerId() != null
                    ? pendingSheet.getManagerId()
                    : (manager == null ? null : manager.getId());
            if (managerId == null) {
                continue;
            }
            String employeeName = pendingSheet == null
                    ? defaultText(userMap.get(employeeId) == null ? null : userMap.get(employeeId).getNickname())
                    : defaultText(pendingSheet.getEmployeeName());
            PerformanceSheetDO noticeSheet = pendingSheet == null
                    ? buildVirtualSheet(pendingContext.periodMonth(), employeeId, employeeName, manager)
                    : pendingSheet;
            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, DeptRespDTO> loadDeptMap(Collection<AdminUserRespDTO> users) {
        if (users == null || users.isEmpty()) {
            return Map.of();
        }
        LinkedHashSet<Long> deptIds = users.stream()
                .filter(Objects::nonNull)
                .map(AdminUserRespDTO::getDeptId)
                .filter(Objects::nonNull)
                .collect(Collectors.toCollection(LinkedHashSet::new));
        if (deptIds.isEmpty()) {
            return Map.of();
        }
        return deptApi.getDeptMap(deptIds);
    }

    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 Map<String, PerformanceSheetDO> buildSheetBySlot(List<PerformanceSheetDO> sheets) {
        if (sheets == null || sheets.isEmpty()) {
            return Map.of();
        }
        return sheets.stream()
                .filter(Objects::nonNull)
                .filter(sheet -> sheet.getEmployeeId() != null)
                .filter(sheet -> StringUtils.hasText(sheet.getPeriodMonth()))
                .collect(Collectors.toMap(
                        sheet -> slotKey(sheet.getEmployeeId(), sheet.getPeriodMonth()),
                        sheet -> sheet,
                        this::preferLatestSheet,
                        LinkedHashMap::new));
    }

    private PerformanceSheetDO preferLatestSheet(PerformanceSheetDO left, PerformanceSheetDO right) {
        if (left == null) {
            return right;
        }
        if (right == null) {
            return left;
        }
        if (left.getUpdateTime() != null && right.getUpdateTime() != null) {
            return left.getUpdateTime().isAfter(right.getUpdateTime()) ? left : right;
        }
        if (left.getCreateTime() != null && right.getCreateTime() != null) {
            return left.getCreateTime().isAfter(right.getCreateTime()) ? left : right;
        }
        if (left.getId() != null && right.getId() != null) {
            return left.getId() >= right.getId() ? left : right;
        }
        return left;
    }

    private List<String> buildPeriodMonths(String periodMonthStart, String periodMonthEnd) {
        YearMonth start = YearMonth.parse(periodMonthStart);
        YearMonth end = YearMonth.parse(periodMonthEnd);
        List<String> result = new ArrayList<>();
        for (YearMonth current = start; !current.isAfter(end); current = current.plusMonths(1)) {
            result.add(current.toString());
        }
        return result;
    }

    private String slotKey(Long employeeId, String periodMonth) {
        return employeeId + ":" + periodMonth;
    }

    private PendingSendContext findPendingSendContext(Long employeeId, List<String> periodMonths,
            Map<String, PerformanceSheetDO> sheetBySlot) {
        for (String periodMonth : periodMonths) {
            PerformanceSheetDO sheet = sheetBySlot.get(slotKey(employeeId, periodMonth));
            if (isPendingSendStatus(sheet == null ? null : sheet.getStatusCode())) {
                return new PendingSendContext(periodMonth, sheet);
            }
        }
        return null;
    }

    private boolean isPendingSendStatus(String statusCode) {
        return statusCode == null
                || PerformanceConstants.STATUS_DRAFT.equals(statusCode)
                || PerformanceConstants.STATUS_REJECTED.equals(statusCode);
    }

    private TeamPerformanceSummaryRespVO.PendingSendUser buildPendingSendUser(Long userId, String periodMonth,
            PerformanceSheetDO sheet, AdminUserRespDTO user, Map<Long, AdminUserRespDTO> directManagerCache) {
        AdminUserRespDTO manager = null;
        if (sheet == null || sheet.getManagerId() == null || !StringUtils.hasText(sheet.getManagerName())) {
            manager = directManagerCache.computeIfAbsent(userId, this::loadDirectManagerQuietly);
        }
        TeamPerformanceSummaryRespVO.PendingSendUser item = new TeamPerformanceSummaryRespVO.PendingSendUser();
        item.setUserId(userId);
        item.setUserNickname(resolveUserNickname(user, sheet));
        item.setPeriodMonth(periodMonth);
        item.setManagerUserId(sheet != null && sheet.getManagerId() != null
                ? sheet.getManagerId()
                : (manager == null ? null : manager.getId()));
        item.setManagerName(sheet != null && StringUtils.hasText(sheet.getManagerName())
                ? defaultText(sheet.getManagerName())
                : (manager == null ? "" : defaultText(manager.getNickname())));
        item.setSheetId(sheet == null ? null : sheet.getId());
        item.setStatusCode(sheet == null ? null : sheet.getStatusCode());
        return item;
    }

    private TeamPerformanceSummaryRespVO.PendingConfirmUser buildPendingConfirmUser(PerformanceSheetDO sheet,
            AdminUserRespDTO user, String periodMonth) {
        TeamPerformanceSummaryRespVO.PendingConfirmUser item = new TeamPerformanceSummaryRespVO.PendingConfirmUser();
        item.setUserId(sheet.getEmployeeId());
        item.setUserNickname(resolveUserNickname(user, sheet));
        item.setPeriodMonth(periodMonth);
        item.setSheetId(sheet.getId());
        item.setSentTime(sheet.getSentTime());
        return item;
    }

    private String resolveUserNickname(AdminUserRespDTO user, PerformanceSheetDO sheet) {
        if (user != null && StringUtils.hasText(user.getNickname())) {
            return defaultText(user.getNickname());
        }
        return sheet == null ? "" : defaultText(sheet.getEmployeeName());
    }

    private DeptAverageAccumulator touchDeptAverageAccumulator(Map<String, DeptAverageAccumulator> averageMap,
            DeptBucket deptBucket) {
        if (deptBucket == null) {
            return null;
        }
        return averageMap.computeIfAbsent(deptBucket.key(), ignored ->
                new DeptAverageAccumulator(deptBucket.deptId(), deptBucket.deptName(), deptBucket.deptOrgType()));
    }

    private DeptBucket resolveDeptBucket(PerformanceSheetDO sheet, AdminUserRespDTO user,
            Map<Long, DeptRespDTO> deptMap) {
        if (sheet != null
                && sheet.getEmployeeDeptId() != null
                && isDeptAverageOrgType(sheet.getDeptOrgType())) {
            return new DeptBucket(sheet.getEmployeeDeptId(),
                    defaultText(sheet.getEmployeeDeptName()), sheet.getDeptOrgType());
        }
        if (user == null || user.getDeptId() == null) {
            return null;
        }
        DeptRespDTO dept = deptMap.get(user.getDeptId());
        if (dept == null || !isDeptAverageOrgType(dept.getOrgType())) {
            return null;
        }
        return new DeptBucket(dept.getId(), defaultText(dept.getName()), dept.getOrgType());
    }

    private boolean isDeptAverageOrgType(String orgType) {
        return PerformanceConstants.ORG_TYPE_DIRECTION.equals(orgType)
                || PerformanceConstants.ORG_TYPE_FUNCTION.equals(orgType);
    }

    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) {
            normalizedStart = normalizedEnd;
        }
        if (normalizedEnd == null) {
            normalizedEnd = normalizedStart;
        }
        if (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");
        }
    }

    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 expectedCount = 0;
        private int confirmedCount = 0;

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

        void incrementExpectedCount() {
            expectedCount++;
        }

        void addConfirmedScore(BigDecimal score) {
            confirmedCount++;
            if (score != null) {
                total = total.add(score);
            }
        }

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

    private record PendingSendContext(String periodMonth, PerformanceSheetDO sheet) {
    }

    private record DeptBucket(Long deptId, String deptName, String deptOrgType) {
        private String key() {
            return deptId + ":" + deptOrgType;
        }
    }
}
