package cn.iocoder.lyzsys.module.tjt.service.project;

import cn.iocoder.lyzsys.framework.common.pojo.PageResult;
import cn.iocoder.lyzsys.framework.common.util.collection.CollectionUtils;
import cn.iocoder.lyzsys.framework.common.util.object.BeanUtils;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectPageReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectRolePersonRespVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectRolePersonSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.controller.admin.project.vo.ProjectSaveReqVO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.employee.EmployeeDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.outputsplit.ProjectOutputSplitDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planning.ProjectPlanningDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.planningquarter.ProjectPlanningQuarterDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.project.ProjectDO;
import cn.iocoder.lyzsys.module.tjt.dal.dataobject.projectroleperson.ProjectRolePersonDO;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planning.ProjectPlanningMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.planningquarter.ProjectPlanningQuarterMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.project.ProjectMapper;
import cn.iocoder.lyzsys.module.tjt.dal.mysql.projectroleperson.ProjectRolePersonMapper;
import cn.iocoder.lyzsys.module.tjt.service.employee.EmployeeService;
import cn.iocoder.lyzsys.module.tjt.service.outputsplit.ProjectOutputSplitService;
import cn.iocoder.lyzsys.module.tjt.service.planningguidedetail.ProjectPlanningGuideDetailService;
import cn.iocoder.lyzsys.module.tjt.service.specialtyrolesplit.SpecialtyRoleSplitService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.iocoder.lyzsys.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.lyzsys.module.tjt.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS;

/**
 * 项目 Service 实现类
 *
 * @author Codex
 */
@Service
@Validated
public class ProjectServiceImpl implements ProjectService {

    private static final String ROLE_CODE_PROJECT_MANAGER = "project_manager";
    private static final String ROLE_CODE_ENGINEERING_PRINCIPAL = "engineering_principal";
    private static final String ROLE_NAME_PROJECT_MANAGER = "项目经理";
    private static final String ROLE_NAME_ENGINEERING_PRINCIPAL = "工程负责人";
    private static final String STATUS_IN_PROGRESS = "进行中";
    private static final String STATUS_COMPLETED = "完成";
    private static final String STATUS_PAUSED = "暂停";
    private static final String STATUS_TERMINATED = "中止";

    @Resource
    private ProjectMapper projectMapper;
    @Resource
    private ProjectRolePersonMapper projectRolePersonMapper;
    @Resource
    private ProjectPlanningMapper projectPlanningMapper;
    @Resource
    private ProjectPlanningQuarterMapper projectPlanningQuarterMapper;
    @Resource
    private EmployeeService employeeService;
    @Resource
    private ProjectOutputSplitService projectOutputSplitService;
    @Resource
    private SpecialtyRoleSplitService specialtyRoleSplitService;
    @Resource
    private ProjectPlanningGuideDetailService projectPlanningGuideDetailService;

    @Override
    public Long createProject(ProjectSaveReqVO createReqVO) {
        ProjectDO project = BeanUtils.toBean(createReqVO, ProjectDO.class);
        applyProjectDefaults(project, null);
        projectMapper.insert(project);
        saveProjectRolePersons(project.getId(), createReqVO.getRolePersons());
        return project.getId();
    }

    @Override
    public void updateProject(ProjectSaveReqVO updateReqVO) {
        ProjectDO dbProject = validateProjectExists(updateReqVO.getId());
        ProjectDO updateObj = BeanUtils.toBean(updateReqVO, ProjectDO.class);
        applyProjectDefaults(updateObj, dbProject);
        projectMapper.updateById(updateObj);
        saveProjectRolePersons(updateReqVO.getId(), updateReqVO.getRolePersons());
    }

    @Override
    public void deleteProject(Long id) {
        validateProjectExists(id);
        deleteProjectDependencies(Collections.singletonList(id));
        projectRolePersonMapper.delete(ProjectRolePersonDO::getProjectId, id);
        projectMapper.deleteById(id);
    }

    @Override
    public void deleteProjectList(List<Long> ids) {
        ids.forEach(this::validateProjectExists);
        deleteProjectDependencies(ids);
        projectRolePersonMapper.deleteBatch(ProjectRolePersonDO::getProjectId, ids);
        projectMapper.deleteBatchIds(ids);
    }

    @Override
    public PageResult<ProjectRespVO> getProjectPage(ProjectPageReqVO pageReqVO) {
        PageResult<ProjectDO> pageResult = projectMapper.selectPage(pageReqVO);
        if (pageResult.getList().isEmpty()) {
            return new PageResult<>(Collections.emptyList(), pageResult.getTotal());
        }
        List<ProjectDO> projects = pageResult.getList();
        List<Long> projectIds = projects.stream().map(ProjectDO::getId).collect(Collectors.toList());
        Map<Long, List<ProjectRolePersonDO>> rolePersonMap = CollectionUtils.convertMultiMap(
                projectRolePersonMapper.selectListByProjectIds(projectIds), ProjectRolePersonDO::getProjectId);
        List<ProjectRespVO> list = projects.stream()
                .map(project -> buildProjectRespVO(syncProjectStatus(project), rolePersonMap.get(project.getId())))
                .collect(Collectors.toList());
        return new PageResult<>(list, pageResult.getTotal());
    }

    @Override
    public ProjectRespVO getProject(Long id) {
        ProjectDO project = syncProjectStatus(validateProjectExists(id));
        return buildProjectRespVO(project, projectRolePersonMapper.selectListByProjectId(id));
    }

    private ProjectDO validateProjectExists(Long id) {
        ProjectDO project = projectMapper.selectById(id);
        if (project == null) {
            throw exception(PROJECT_NOT_EXISTS);
        }
        return project;
    }

    private void applyProjectDefaults(ProjectDO project, ProjectDO dbProject) {
        if (project.getProjectStatus() == null || project.getProjectStatus().trim().isEmpty()) {
            project.setProjectStatus(dbProject == null ? STATUS_IN_PROGRESS : dbProject.getProjectStatus());
        }
        if (project.getArchiveFlag() == null) {
            project.setArchiveFlag(dbProject != null && Boolean.TRUE.equals(dbProject.getArchiveFlag()));
        }
        if (project.getArchiveTime() == null && dbProject != null) {
            project.setArchiveTime(dbProject.getArchiveTime());
        }
        if (project.getSortNo() == null) {
            project.setSortNo(dbProject == null || dbProject.getSortNo() == null ? 0 : dbProject.getSortNo());
        }
    }

    private void saveProjectRolePersons(Long projectId, List<ProjectRolePersonSaveReqVO> reqList) {
        projectRolePersonMapper.delete(ProjectRolePersonDO::getProjectId, projectId);
        if (reqList == null || reqList.isEmpty()) {
            return;
        }
        List<ProjectRolePersonDO> createList = new ArrayList<>();
        int autoSortNo = 1;
        for (ProjectRolePersonSaveReqVO item : reqList) {
            if (item == null || item.getEmployeeId() == null) {
                continue;
            }
            String roleCode = item.getRoleCode() == null ? "" : item.getRoleCode().trim();
            if (!Objects.equals(roleCode, ROLE_CODE_PROJECT_MANAGER)
                    && !Objects.equals(roleCode, ROLE_CODE_ENGINEERING_PRINCIPAL)) {
                continue;
            }
            EmployeeDO employee = employeeService.validateEmployeeExists(item.getEmployeeId());
            ProjectRolePersonDO createObj = new ProjectRolePersonDO();
            createObj.setProjectId(projectId);
            createObj.setRoleCode(roleCode);
            createObj.setRoleName(getRoleName(roleCode));
            createObj.setEmployeeId(item.getEmployeeId());
            createObj.setEmployeeName(employee.getEmployeeName());
            createObj.setSortNo(item.getSortNo() == null ? autoSortNo++ : item.getSortNo());
            createObj.setEnabledFlag(Boolean.TRUE);
            createList.add(createObj);
        }
        for (ProjectRolePersonDO createObj : createList) {
            projectRolePersonMapper.insert(createObj);
        }
    }

    private ProjectRespVO buildProjectRespVO(ProjectDO project, List<ProjectRolePersonDO> rolePersons) {
        List<ProjectRolePersonDO> safeRolePersons = rolePersons == null ? Collections.emptyList() : rolePersons;
        ProjectRespVO respVO = BeanUtils.toBean(project, ProjectRespVO.class);
        respVO.setProjectManagerName(joinPersonNamesByRoleCode(safeRolePersons, ROLE_CODE_PROJECT_MANAGER));
        respVO.setEngineeringPrincipalName(joinPersonNamesByRoleCode(safeRolePersons, ROLE_CODE_ENGINEERING_PRINCIPAL));
        respVO.setRolePersons(BeanUtils.toBean(safeRolePersons, ProjectRolePersonRespVO.class));
        return respVO;
    }

    private String joinPersonNamesByRoleCode(List<ProjectRolePersonDO> rolePersons, String roleCode) {
        return rolePersons.stream()
                .filter(item -> Objects.equals(item.getRoleCode(), roleCode))
                .map(ProjectRolePersonDO::getEmployeeName)
                .filter(Objects::nonNull)
                .collect(Collectors.joining("、"));
    }

    private String getRoleName(String roleCode) {
        if (Objects.equals(roleCode, ROLE_CODE_PROJECT_MANAGER)) {
            return ROLE_NAME_PROJECT_MANAGER;
        }
        if (Objects.equals(roleCode, ROLE_CODE_ENGINEERING_PRINCIPAL)) {
            return ROLE_NAME_ENGINEERING_PRINCIPAL;
        }
        return roleCode;
    }

    private ProjectDO syncProjectStatus(ProjectDO project) {
        String nextStatus = calculateProjectStatus(project);
        Boolean nextArchiveFlag = STATUS_COMPLETED.equals(nextStatus);
        LocalDateTime nextArchiveTime = nextArchiveFlag
                ? (project.getArchiveTime() == null ? LocalDateTime.now() : project.getArchiveTime())
                : null;
        boolean needUpdate = !Objects.equals(project.getProjectStatus(), nextStatus)
                || !Objects.equals(project.getArchiveFlag(), nextArchiveFlag)
                || !Objects.equals(project.getArchiveTime(), nextArchiveTime);
        if (needUpdate) {
            ProjectDO updateObj = new ProjectDO();
            updateObj.setId(project.getId());
            updateObj.setProjectStatus(nextStatus);
            updateObj.setArchiveFlag(nextArchiveFlag);
            updateObj.setArchiveTime(nextArchiveTime);
            if (!nextArchiveFlag) {
                updateObj.setArchiveTime(null);
            }
            projectMapper.updateById(updateObj);
            project.setProjectStatus(nextStatus);
            project.setArchiveFlag(nextArchiveFlag);
            project.setArchiveTime(nextArchiveTime);
        }
        return project;
    }

    private String calculateProjectStatus(ProjectDO project) {
        if (STATUS_TERMINATED.equals(project.getProjectStatus())) {
            return STATUS_TERMINATED;
        }
        if (isProjectCompleted(project.getId())) {
            return STATUS_COMPLETED;
        }
        if (STATUS_PAUSED.equals(project.getProjectStatus())
                && project.getPauseReason() != null
                && !project.getPauseReason().trim().isEmpty()) {
            return STATUS_PAUSED;
        }
        int currentYear = LocalDateTime.now().getYear();
        if (project.getProjectStartYear() != null && currentYear - project.getProjectStartYear() >= 5) {
            return STATUS_PAUSED;
        }
        return STATUS_IN_PROGRESS;
    }

    private boolean isProjectCompleted(Long projectId) {
        List<ProjectPlanningDO> planningList = projectPlanningMapper.selectListByProjectId(projectId);
        if (planningList == null || planningList.isEmpty()) {
            return false;
        }
        List<Long> planningIds = planningList.stream().map(ProjectPlanningDO::getId).collect(Collectors.toList());
        Map<Long, List<ProjectPlanningQuarterDO>> quarterMap = CollectionUtils.convertMultiMap(
                projectPlanningQuarterMapper.selectListByPlanningIds(planningIds), ProjectPlanningQuarterDO::getPlanningId);
        for (ProjectPlanningDO planning : planningList) {
            java.math.BigDecimal allocatedRatio = quarterMap.getOrDefault(planning.getId(), Collections.emptyList()).stream()
                    .map(ProjectPlanningQuarterDO::getDistributionRatio)
                    .filter(Objects::nonNull)
                    .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add)
                    .setScale(4, java.math.RoundingMode.HALF_UP);
            java.math.BigDecimal totalDistributionAmount = planning.getTotalDistributionAmount() == null
                    ? java.math.BigDecimal.ONE.setScale(4, java.math.RoundingMode.HALF_UP)
                    : planning.getTotalDistributionAmount().setScale(4, java.math.RoundingMode.HALF_UP);
            if (allocatedRatio.compareTo(totalDistributionAmount) < 0) {
                return false;
            }
        }
        return true;
    }

    private void deleteProjectDependencies(Collection<Long> projectIds) {
        if (projectIds == null || projectIds.isEmpty()) {
            return;
        }
        List<ProjectPlanningDO> planningList = projectPlanningMapper.selectList(
                new cn.iocoder.lyzsys.framework.mybatis.core.query.LambdaQueryWrapperX<ProjectPlanningDO>()
                        .inIfPresent(ProjectPlanningDO::getProjectId, projectIds));
        if (planningList == null || planningList.isEmpty()) {
            return;
        }
        List<Long> planningIds = planningList.stream()
                .map(ProjectPlanningDO::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (planningIds.isEmpty()) {
            return;
        }
        Map<Long, ProjectOutputSplitDO> outputSplitMap = projectOutputSplitService.getProjectOutputSplitMap(planningIds);
        if (!outputSplitMap.isEmpty()) {
            specialtyRoleSplitService.deleteByOutputSplitIds(outputSplitMap.values().stream()
                    .map(ProjectOutputSplitDO::getId)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList()));
            projectOutputSplitService.deleteByPlanningIds(planningIds);
        }
        projectPlanningQuarterMapper.deleteBatch(ProjectPlanningQuarterDO::getPlanningId, planningIds);
        projectPlanningGuideDetailService.deleteByPlanningIds(planningIds);
        projectPlanningMapper.deleteBatchIds(planningIds);
    }

}
