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

import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.project.controller.admin.project.project.vo.workbench.MyWorklogWeekRespVO;
import com.njcn.rdms.module.project.controller.admin.project.project.vo.workbench.TeamWorklogWeekRespVO;
import com.njcn.rdms.module.project.dal.dataobject.personal.PersonalItemDO;
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO;
import com.njcn.rdms.module.project.dal.dataobject.project.task.ProjectTaskDO;
import com.njcn.rdms.module.project.dal.dataobject.project.task.TaskWorklogDO;
import com.njcn.rdms.module.project.dal.mysql.personal.PersonalItemMapper;
import com.njcn.rdms.module.project.dal.mysql.project.ProjectMapper;
import com.njcn.rdms.module.project.dal.mysql.project.task.ProjectTaskMapper;
import com.njcn.rdms.module.project.dal.mysql.project.task.TaskWorklogMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class MyWorklogServiceImpl implements MyWorklogService {

    @Resource
    private TaskWorklogMapper taskWorklogMapper;
    @Resource
    private ProjectTaskMapper projectTaskMapper;
    @Resource
    private PersonalItemMapper personalItemMapper;
    @Resource
    private ProjectMapper projectMapper;
    @Resource
    private MyTeamService myTeamService;

    @Override
    public MyWorklogWeekRespVO getMyWorklogWeek(LocalDate weekStart) {
        Long me = SecurityFrameworkUtils.getLoginUserId();
        LocalDate monday = MyWorklogWeekSupport.normalizeToMonday(weekStart);
        // 查询区间含周末:周六/周日的段要兜底归周五,必须查回来
        List<TaskWorklogDO> worklogs = taskWorklogMapper
                .selectListByUserIdAndPeriod(me, monday, monday.plusDays(6));
        // 逐日累计(scale=4)与每任务份额累计
        Map<LocalDate, BigDecimal> daily = new LinkedHashMap<>();
        Map<Long, BigDecimal> hoursByTask = new HashMap<>();
        for (TaskWorklogDO w : worklogs) {
            Map<LocalDate, BigDecimal> shares = MyWorklogWeekSupport
                    .apportionToWeek(w.getStartDate(), w.getEndDate(), w.getDurationHours(), monday);
            for (Map.Entry<LocalDate, BigDecimal> e : shares.entrySet()) {
                daily.merge(e.getKey(), e.getValue(), BigDecimal::add);
                hoursByTask.merge(w.getTaskId(), e.getValue(), BigDecimal::add);
            }
        }
        MyWorklogWeekRespVO resp = new MyWorklogWeekRespVO();
        resp.setWeekStart(monday);
        List<BigDecimal> dailyHours = new ArrayList<>(5);
        for (int i = 0; i < 5; i++) {
            dailyHours.add(daily.getOrDefault(monday.plusDays(i), BigDecimal.ZERO)
                    .setScale(2, RoundingMode.HALF_UP));
        }
        resp.setDailyHours(dailyHours);
        resp.setDistribution(buildDistribution(hoursByTask));
        return resp;
    }

    @Override
    public TeamWorklogWeekRespVO getTeamWorklogWeek(LocalDate weekStart) {
        LocalDate monday = MyWorklogWeekSupport.normalizeToMonday(weekStart);
        List<MyTeamService.TeamMember> members = myTeamService.resolveTeamMembers();
        List<Long> userIds = members.stream().map(MyTeamService.TeamMember::userId).collect(Collectors.toList());
        List<TaskWorklogDO> worklogs = taskWorklogMapper
                .selectListByUserIdsAndPeriod(userIds, monday, monday.plusDays(6));
        // 每成员每任务的本周份额合计
        Map<Long, Map<Long, BigDecimal>> hoursByUserAndTask = new LinkedHashMap<>();
        for (TaskWorklogDO w : worklogs) {
            BigDecimal weekTotal = MyWorklogWeekSupport
                    .apportionToWeek(w.getStartDate(), w.getEndDate(), w.getDurationHours(), monday)
                    .values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
            if (weekTotal.signum() > 0) {
                hoursByUserAndTask.computeIfAbsent(w.getUserId(), k -> new HashMap<>())
                        .merge(w.getTaskId(), weekTotal, BigDecimal::add);
            }
        }
        TeamWorklogWeekRespVO resp = new TeamWorklogWeekRespVO();
        resp.setWeekStart(monday);
        resp.setMembers(members.stream().map(m -> {
            TeamWorklogWeekRespVO.MemberVO vo = new TeamWorklogWeekRespVO.MemberVO();
            vo.setUserId(m.userId());
            vo.setUserNickname(m.nickname());
            vo.setItems(buildDistribution(
                    hoursByUserAndTask.getOrDefault(m.userId(), Collections.emptyMap())));
            return vo;
        }).collect(Collectors.toList()));
        return resp;
    }

    /**
     * 把 taskId→本周工时 聚成归属分布:工时表的 task_id 既可能指向任务也可能指向个人事项
     * (个人事项工时复用 rdms_task_worklog),先按任务表归 project,再按个人事项表归 personal,
     * 都对不上归 other。排序:project 行按 hours 降序,personal/other 殿后。
     * 被软删的任务/事项的残留工时归 other(两表主键同为雪花 id 全局不撞,不存在误归类)。
     */
    private List<MyWorklogWeekRespVO.DistributionItemVO> buildDistribution(Map<Long, BigDecimal> hoursByTask) {
        if (hoursByTask.isEmpty()) {
            return Collections.emptyList();
        }
        Set<Long> taskIds = hoursByTask.keySet();
        Map<Long, ProjectTaskDO> taskMap = projectTaskMapper.selectBatchIds(taskIds).stream()
                .collect(Collectors.toMap(ProjectTaskDO::getId, Function.identity(), (a, b) -> a));
        Set<Long> personalIds = taskIds.stream()
                .filter(id -> !taskMap.containsKey(id)).collect(Collectors.toSet());
        Set<Long> personalHit = personalIds.isEmpty() ? Collections.emptySet()
                : personalItemMapper.selectBatchIds(personalIds).stream()
                        .map(PersonalItemDO::getId).collect(Collectors.toSet());
        // 聚合:projectId→hours / personal / other
        Map<Long, BigDecimal> hoursByProject = new LinkedHashMap<>();
        BigDecimal personalHours = BigDecimal.ZERO;
        BigDecimal otherHours = BigDecimal.ZERO;
        for (Map.Entry<Long, BigDecimal> e : hoursByTask.entrySet()) {
            ProjectTaskDO task = taskMap.get(e.getKey());
            if (task != null) {
                hoursByProject.merge(task.getProjectId(), e.getValue(), BigDecimal::add);
            } else if (personalHit.contains(e.getKey())) {
                personalHours = personalHours.add(e.getValue());
            } else {
                otherHours = otherHours.add(e.getValue());
            }
        }
        Map<Long, ProjectDO> projectMap = hoursByProject.isEmpty() ? Collections.emptyMap()
                : projectMapper.selectBatchIds(hoursByProject.keySet()).stream()
                        .collect(Collectors.toMap(ProjectDO::getId, Function.identity(), (a, b) -> a));
        List<MyWorklogWeekRespVO.DistributionItemVO> items = new ArrayList<>();
        hoursByProject.entrySet().stream()
                .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
                .forEach(e -> {
                    MyWorklogWeekRespVO.DistributionItemVO item = new MyWorklogWeekRespVO.DistributionItemVO();
                    item.setProjectId(e.getKey());
                    ProjectDO project = projectMap.get(e.getKey());
                    item.setProjectName(project == null ? null : project.getProjectName());
                    item.setKind("project");
                    item.setHours(e.getValue().setScale(2, RoundingMode.HALF_UP));
                    items.add(item);
                });
        if (personalHours.signum() > 0) {
            MyWorklogWeekRespVO.DistributionItemVO item = new MyWorklogWeekRespVO.DistributionItemVO();
            item.setKind("personal");
            item.setHours(personalHours.setScale(2, RoundingMode.HALF_UP));
            items.add(item);
        }
        if (otherHours.signum() > 0) {
            MyWorklogWeekRespVO.DistributionItemVO item = new MyWorklogWeekRespVO.DistributionItemVO();
            item.setKind("other");
            item.setHours(otherHours.setScale(2, RoundingMode.HALF_UP));
            items.add(item);
        }
        return items;
    }

}
