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

import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.project.constant.OvertimeApplicationConstants;
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.overtime.OvertimeApplicationDO;
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.overtime.OvertimeApplicationMapper;
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 org.springframework.util.StringUtils;

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 {

    private static final BigDecimal OVERTIME_DAY_HOURS = new BigDecimal("6");

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

    @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));
        Map<LocalDate, BigDecimal> daily = new LinkedHashMap<>();
        Map<Long, BigDecimal> hoursByTask = new HashMap<>();
        for (TaskWorklogDO worklog : worklogs) {
            Map<LocalDate, BigDecimal> shares = MyWorklogWeekSupport
                    .apportionToWeek(worklog.getStartDate(), worklog.getEndDate(), worklog.getDurationHours(), monday);
            for (Map.Entry<LocalDate, BigDecimal> entry : shares.entrySet()) {
                daily.merge(entry.getKey(), entry.getValue(), BigDecimal::add);
                hoursByTask.merge(worklog.getTaskId(), entry.getValue(), BigDecimal::add);
            }
        }
        MyWorklogWeekRespVO resp = new MyWorklogWeekRespVO();
        resp.setWeekStart(monday);
        List<BigDecimal> dailyHours = new ArrayList<>(7);
        for (int i = 0; i < 7; 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);
        LocalDate sunday = monday.plusDays(6);
        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, sunday);

        Map<Long, Map<Long, BigDecimal>> hoursByUserAndTask = new LinkedHashMap<>();
        for (TaskWorklogDO worklog : worklogs) {
            BigDecimal weekTotal = MyWorklogWeekSupport
                    .apportionToWeek(worklog.getStartDate(), worklog.getEndDate(), worklog.getDurationHours(), monday)
                    .values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
            if (weekTotal.signum() > 0) {
                hoursByUserAndTask.computeIfAbsent(worklog.getUserId(), key -> new HashMap<>())
                        .merge(worklog.getTaskId(), weekTotal, BigDecimal::add);
            }
        }

        Map<Long, BigDecimal> overtimeHoursByUser = buildApprovedOvertimeHoursByUser(userIds, monday, sunday);
        TeamWorklogWeekRespVO resp = new TeamWorklogWeekRespVO();
        resp.setWeekStart(monday);
        resp.setMembers(members.stream().map(member -> {
            TeamWorklogWeekRespVO.MemberVO vo = new TeamWorklogWeekRespVO.MemberVO();
            vo.setUserId(member.userId());
            vo.setUserNickname(member.nickname());
            vo.setItems(buildDistribution(
                    hoursByUserAndTask.getOrDefault(member.userId(), Collections.emptyMap())));
            vo.setOvertimeHours(overtimeHoursByUser.getOrDefault(member.userId(), BigDecimal.ZERO)
                    .setScale(2, RoundingMode.HALF_UP));
            return vo;
        }).collect(Collectors.toList()));
        return resp;
    }

    private Map<Long, BigDecimal> buildApprovedOvertimeHoursByUser(List<Long> userIds,
                                                                    LocalDate startDate,
                                                                    LocalDate endDate) {
        if (userIds == null || userIds.isEmpty()) {
            return Collections.emptyMap();
        }
        List<OvertimeApplicationDO> applications = overtimeApplicationMapper.selectApprovedListByApplicantIdsAndDateRange(
                userIds, startDate, endDate, OvertimeApplicationConstants.STATUS_APPROVED);
        if (applications.isEmpty()) {
            return Collections.emptyMap();
        }
        Map<Long, BigDecimal> overtimeHoursByUser = new HashMap<>();
        for (OvertimeApplicationDO application : applications) {
            if (application == null || application.getApplicantId() == null) {
                continue;
            }
            BigDecimal overtimeHours = parseOvertimeHours(application.getOvertimeDuration());
            if (overtimeHours.signum() <= 0) {
                continue;
            }
            overtimeHoursByUser.merge(application.getApplicantId(), overtimeHours, BigDecimal::add);
        }
        return overtimeHoursByUser;
    }

    private BigDecimal parseOvertimeHours(String overtimeDuration) {
        if (!StringUtils.hasText(overtimeDuration)) {
            return BigDecimal.ZERO;
        }
        String normalized = overtimeDuration.trim()
                .replace("\u5929", "")
                .replace("\u5c0f\u65f6", "")
                .replace("h", "")
                .trim();
        if (!StringUtils.hasText(normalized)) {
            return BigDecimal.ZERO;
        }
        try {
            return new BigDecimal(normalized).multiply(OVERTIME_DAY_HOURS);
        } catch (NumberFormatException ex) {
            return BigDecimal.ZERO;
        }
    }

    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());

        Map<Long, BigDecimal> hoursByProject = new LinkedHashMap<>();
        BigDecimal personalHours = BigDecimal.ZERO;
        BigDecimal otherHours = BigDecimal.ZERO;
        for (Map.Entry<Long, BigDecimal> entry : hoursByTask.entrySet()) {
            ProjectTaskDO task = taskMap.get(entry.getKey());
            if (task != null) {
                hoursByProject.merge(task.getProjectId(), entry.getValue(), BigDecimal::add);
            } else if (personalHit.contains(entry.getKey())) {
                personalHours = personalHours.add(entry.getValue());
            } else {
                otherHours = otherHours.add(entry.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(entry -> {
                    MyWorklogWeekRespVO.DistributionItemVO item = new MyWorklogWeekRespVO.DistributionItemVO();
                    item.setProjectId(entry.getKey());
                    ProjectDO project = projectMap.get(entry.getKey());
                    item.setProjectName(project == null ? null : project.getProjectName());
                    item.setKind("project");
                    item.setHours(entry.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;
    }

}
