package com.njcn.rdms.module.project.service.workreport.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.WorkReportConstants;
import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportRemindReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportRemindRespVO;
import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportSummaryReqVO;
import com.njcn.rdms.module.project.controller.admin.workreport.team.vo.TeamReportSummaryRespVO;
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO;
import com.njcn.rdms.module.project.dal.dataobject.workreport.monthly.MonthlyReportDO;
import com.njcn.rdms.module.project.dal.dataobject.workreport.project.ProjectReportDO;
import com.njcn.rdms.module.project.dal.dataobject.workreport.weekly.WeeklyReportDO;
import com.njcn.rdms.module.project.dal.mysql.project.ProjectMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
import com.njcn.rdms.module.project.dal.mysql.workreport.monthly.MonthlyReportMapper;
import com.njcn.rdms.module.project.dal.mysql.workreport.project.ProjectReportMapper;
import com.njcn.rdms.module.project.dal.mysql.workreport.weekly.WeeklyReportMapper;
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.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.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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.Set;
import java.util.stream.Collectors;

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

@Service
public class TeamWorkReportServiceImpl implements TeamWorkReportService {

    private static final List<String> SUBMITTED_STATUS_CODES = List.of(
            WorkReportConstants.STATUS_PENDING_APPROVAL,
            WorkReportConstants.STATUS_APPROVED,
            WorkReportConstants.STATUS_REJECTED);

    @Resource
    private TeamDashboardAccessService teamDashboardAccessService;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private WeeklyReportMapper weeklyReportMapper;
    @Resource
    private MonthlyReportMapper monthlyReportMapper;
    @Resource
    private ProjectReportMapper projectReportMapper;
    @Resource
    private ProjectMapper projectMapper;
    @Resource
    private ObjectStatusModelMapper objectStatusModelMapper;
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public TeamReportSummaryRespVO getSummary(TeamReportSummaryReqVO reqVO) {
        teamDashboardAccessService.validateTeamDashboardPermission(WorkReportConstants.PERMISSION_TEAM_DASHBOARD);
        ReportContext context = buildReportContext(normalizeReportType(reqVO.getReportType()), reqVO.getPeriodKey());
        TeamReportSummaryRespVO respVO = new TeamReportSummaryRespVO();
        respVO.setTotalShouldSubmit(context.expectedUserIds().size());
        respVO.setSubmittedCount(context.submittedUserIds().size());
        respVO.setPendingApprovalCount(context.pendingApprovalUserIds().size());
        List<TeamReportSummaryRespVO.PendingUser> unsubmittedUsers = buildPendingUsers(context.expectedUserIds(), context.submittedUserIds());
        respVO.setUnsubmittedUsers(unsubmittedUsers);
        respVO.setUnsubmittedCount(unsubmittedUsers.size());
        return respVO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public TeamReportRemindRespVO remind(TeamReportRemindReqVO reqVO) {
        teamDashboardAccessService.validateTeamDashboardPermission(WorkReportConstants.PERMISSION_TEAM_DASHBOARD);
        String reportType = normalizeReportType(reqVO.getReportType());
        ReportContext context = buildReportContext(reportType, reqVO.getPeriodKey());
        List<Long> remindUserIds = resolveRemindUserIds(reqVO.getUserIds(), context);

        if (!remindUserIds.isEmpty()) {
            Map<String, Object> params = new HashMap<>();
            params.put("reportTypeName", reportTypeDisplayName(reportType));
            params.put("periodKey", reqVO.getPeriodKey());
            params.put("managerName", defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
            applicationEventPublisher.publishEvent(NotifySendEvent.of(remindUserIds,
                    NotifyTemplateCodeConstants.WORK_REPORT_TEAM_REMIND, params, NotifyMessageLevelConstants.REMIND));
        }

        TeamReportRemindRespVO respVO = new TeamReportRemindRespVO();
        respVO.setRemindedCount(remindUserIds.size());
        return respVO;
    }

    private ReportContext buildReportContext(String reportType, String periodKey) {
        if (!StringUtils.hasText(periodKey)) {
            throw invalidParamException("周期主键不能为空");
        }
        if (WorkReportConstants.REPORT_TYPE_PROJECT.equals(reportType)) {
            return buildProjectContext(periodKey);
        }
        List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
        if (subordinateIds.isEmpty()) {
            return new ReportContext(Collections.emptyList(), Collections.emptySet(), Collections.emptySet());
        }
        if (WorkReportConstants.REPORT_TYPE_WEEKLY.equals(reportType)) {
            return buildWeeklyContext(periodKey, subordinateIds);
        }
        return buildMonthlyContext(periodKey, subordinateIds);
    }

    private ReportContext buildWeeklyContext(String periodKey, List<Long> subordinateIds) {
        List<WeeklyReportDO> reports = weeklyReportMapper.selectListByReporterIdsAndPeriodKey(
                subordinateIds, periodKey, SUBMITTED_STATUS_CODES);
        Set<Long> submittedUserIds = new LinkedHashSet<>();
        Set<Long> pendingApprovalUserIds = new LinkedHashSet<>();
        for (WeeklyReportDO report : reports) {
            if (report == null || report.getReporterId() == null) {
                continue;
            }
            submittedUserIds.add(report.getReporterId());
            if (WorkReportConstants.STATUS_PENDING_APPROVAL.equals(report.getStatusCode())) {
                pendingApprovalUserIds.add(report.getReporterId());
            }
        }
        return new ReportContext(subordinateIds, submittedUserIds, pendingApprovalUserIds);
    }

    private ReportContext buildMonthlyContext(String periodKey, List<Long> subordinateIds) {
        List<MonthlyReportDO> reports = monthlyReportMapper.selectListByReporterIdsAndPeriodKey(
                subordinateIds, periodKey, SUBMITTED_STATUS_CODES);
        Set<Long> submittedUserIds = new LinkedHashSet<>();
        Set<Long> pendingApprovalUserIds = new LinkedHashSet<>();
        for (MonthlyReportDO report : reports) {
            if (report == null || report.getReporterId() == null) {
                continue;
            }
            submittedUserIds.add(report.getReporterId());
            if (WorkReportConstants.STATUS_PENDING_APPROVAL.equals(report.getStatusCode())) {
                pendingApprovalUserIds.add(report.getReporterId());
            }
        }
        return new ReportContext(subordinateIds, submittedUserIds, pendingApprovalUserIds);
    }

    private ReportContext buildProjectContext(String periodKey) {
        List<Long> subordinateIds = teamDashboardAccessService.getAllSubordinateUserIds();
        if (subordinateIds.isEmpty()) {
            return new ReportContext(Collections.emptyList(), Collections.emptySet(), Collections.emptySet());
        }
        List<ProjectDO> activeProjects = loadActiveProjectsForSubordinates(subordinateIds);
        if (activeProjects.isEmpty()) {
            return new ReportContext(Collections.emptyList(), Collections.emptySet(), Collections.emptySet());
        }
        Map<Long, List<ProjectDO>> projectsByOwner = activeProjects.stream()
                .filter(Objects::nonNull)
                .filter(project -> project.getManagerUserId() != null)
                .collect(Collectors.groupingBy(ProjectDO::getManagerUserId, LinkedHashMap::new, Collectors.toList()));
        LinkedHashSet<Long> expectedUserIds = new LinkedHashSet<>(projectsByOwner.keySet());
        List<ProjectReportDO> reports = projectReportMapper.selectListByProjectOwnerIdsAndPeriodKey(
                expectedUserIds, periodKey, SUBMITTED_STATUS_CODES);
        Map<Long, Set<Long>> submittedProjectsByOwner = new HashMap<>();
        Set<Long> submittedUserIds = new LinkedHashSet<>();
        Set<Long> pendingApprovalUserIds = new LinkedHashSet<>();
        for (ProjectReportDO report : reports) {
            if (report == null || report.getProjectOwnerId() == null || report.getProjectId() == null) {
                continue;
            }
            Long ownerId = report.getProjectOwnerId();
            if (!expectedUserIds.contains(ownerId)) {
                continue;
            }
            if (WorkReportConstants.STATUS_PENDING_APPROVAL.equals(report.getStatusCode())) {
                pendingApprovalUserIds.add(ownerId);
            }
            submittedProjectsByOwner.computeIfAbsent(ownerId, key -> new LinkedHashSet<>()).add(report.getProjectId());
        }
        for (Map.Entry<Long, List<ProjectDO>> entry : projectsByOwner.entrySet()) {
            Long ownerId = entry.getKey();
            Set<Long> submittedProjectIds = submittedProjectsByOwner.getOrDefault(ownerId, Collections.emptySet());
            boolean allSubmitted = entry.getValue().stream()
                    .map(ProjectDO::getId)
                    .filter(Objects::nonNull)
                    .allMatch(submittedProjectIds::contains);
            if (allSubmitted) {
                submittedUserIds.add(ownerId);
            }
        }
        return new ReportContext(new ArrayList<>(expectedUserIds), submittedUserIds, pendingApprovalUserIds);
    }

    private List<ProjectDO> loadActiveProjectsForSubordinates(Collection<Long> subordinateIds) {
        if (subordinateIds == null || subordinateIds.isEmpty()) {
            return Collections.emptyList();
        }
        List<String> terminalStatusCodes = objectStatusModelMapper
                .selectTerminalStatusCodesByObjectTypeEnabled(com.njcn.rdms.module.project.constant.ProjectObjectConstants.OBJECT_TYPE);
        List<ProjectDO> allProjects = projectMapper.selectListByManagerUserIdsAndStatusCodesNotIn(
                subordinateIds, terminalStatusCodes);
        if (allProjects.isEmpty()) {
            return Collections.emptyList();
        }
        return allProjects.stream()
                .filter(Objects::nonNull)
                .filter(project -> project.getManagerUserId() != null)
                .collect(Collectors.toList());
    }

    private List<Long> resolveRemindUserIds(List<Long> requestedUserIds, ReportContext context) {
        LinkedHashSet<Long> unsubmittedUserIds = new LinkedHashSet<>(context.expectedUserIds());
        unsubmittedUserIds.removeAll(context.submittedUserIds());
        if (requestedUserIds == null) {
            return new ArrayList<>(unsubmittedUserIds);
        }
        List<Long> validatedIds = teamDashboardAccessService.resolveRequestedSubordinateUserIds(
                requestedUserIds, WorkReportConstants.PERMISSION_TEAM_DASHBOARD);
        return validatedIds.stream()
                .filter(unsubmittedUserIds::contains)
                .collect(Collectors.toList());
    }

    private List<TeamReportSummaryRespVO.PendingUser> buildPendingUsers(List<Long> expectedUserIds,
                                                                        Set<Long> submittedUserIds) {
        LinkedHashSet<Long> pendingIds = new LinkedHashSet<>(expectedUserIds);
        pendingIds.removeAll(submittedUserIds);
        if (pendingIds.isEmpty()) {
            return Collections.emptyList();
        }
        CommonResult<List<AdminUserRespDTO>> result = adminUserApi.getUserList(pendingIds);
        List<AdminUserRespDTO> users = result == null ? null : result.getCheckedData();
        if (users == null || users.isEmpty()) {
            users = Collections.emptyList();
        }
        Map<Long, AdminUserRespDTO> userMap = users.stream()
                .filter(Objects::nonNull)
                .filter(user -> user.getId() != null)
                .collect(Collectors.toMap(AdminUserRespDTO::getId, user -> user, (left, right) -> left, LinkedHashMap::new));
        List<TeamReportSummaryRespVO.PendingUser> respList = new ArrayList<>();
        for (Long pendingId : pendingIds) {
            AdminUserRespDTO user = userMap.get(pendingId);
            TeamReportSummaryRespVO.PendingUser item = new TeamReportSummaryRespVO.PendingUser();
            item.setUserId(pendingId);
            item.setUserNickname(user == null ? "" : defaultText(user.getNickname()));
            respList.add(item);
        }
        return respList;
    }

    private String normalizeReportType(String reportType) {
        if (WorkReportConstants.REPORT_TYPE_WEEKLY.equals(reportType)
                || WorkReportConstants.REPORT_TYPE_MONTHLY.equals(reportType)
                || WorkReportConstants.REPORT_TYPE_PROJECT.equals(reportType)) {
            return reportType;
        }
        throw invalidParamException("报告类型不合法");
    }

    private String reportTypeDisplayName(String reportType) {
        return switch (reportType) {
            case WorkReportConstants.REPORT_TYPE_WEEKLY -> "周报";
            case WorkReportConstants.REPORT_TYPE_MONTHLY -> "月报";
            case WorkReportConstants.REPORT_TYPE_PROJECT -> "项目半月报";
            default -> reportType;
        };
    }

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

    private record ReportContext(List<Long> expectedUserIds, Set<Long> submittedUserIds, Set<Long> pendingApprovalUserIds) {
    }
}
