package com.yingpaipay.business.service.impl;

import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yingpaipay.business.constant.DocumentTypeConst;
import com.yingpaipay.system.constant.UserAppletStatusConst;
import com.yingpaipay.business.domain.Document;
import com.yingpaipay.business.domain.bo.*;
import com.yingpaipay.business.domain.vo.*;
import com.yingpaipay.business.enumeration.ProjectStatusEnum;
import com.yingpaipay.system.domain.SysUserFolders;
import com.yingpaipay.system.domain.SysUserProjects;
import com.yingpaipay.system.mapper.SysUserFoldersMapper;
import com.yingpaipay.system.mapper.SysUserProjectsMapper;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.service.ISysDeptService;
import org.dromara.system.service.ISysOssService;
import org.dromara.system.service.ISysUserService;
import org.springframework.stereotype.Service;
import com.yingpaipay.business.domain.Project;
import com.yingpaipay.business.mapper.ProjectMapper;
import com.yingpaipay.business.service.IProjectService;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 项目管理Service业务层处理
 *
 * @author Huanyi
 * @date 2025-12-05
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class ProjectServiceImpl implements IProjectService {

    private final ProjectMapper baseMapper;
    private final SysUserProjectsMapper userProjectsMapper;
    private final SysUserMapper userMapper;
    private final SysUserFoldersMapper userFoldersMapper;

    private final ISysOssService ossService;
    private final ISysDeptService deptService;
    private final ISysUserService userService;
    private final CommonDocumentService documentService;

    /**
     * 查询项目管理
     *
     * @param id 主键
     * @return 项目管理
     */
    @Override
    public ProjectVo queryById(Long id) {
        ProjectVo vo = baseMapper.selectVoById(id);
        if (vo.getIcon() != null) {
            vo.setIconUrl(ossService.getById(vo.getIcon()).getUrl());
        }
        return vo;
    }

    /**
     * 分页查询项目管理列表
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 项目管理分页列表
     */
    @Override
    public TableDataInfo<ProjectVo> queryPageList(ProjectBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<Project> lqw = buildQueryWrapper(bo);
        Page<ProjectVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        List<Long> iconIds = new ArrayList<>();
        result.getRecords().forEach(e -> iconIds.add(e.getIcon()));
        Map<Long, String> iconMap = new HashMap<>();
        ossService.queryListByIds(iconIds).forEach(e -> iconMap.put(e.getOssId(), e.getUrl()));
        result.getRecords().forEach(e -> e.setIconUrl(iconMap.get(e.getIcon())));
        return TableDataInfo.build(result);
    }

    /**
     * 查询符合条件的项目管理列表
     *
     * @param bo 查询条件
     * @return 项目管理列表
     */
    @Override
    public List<ProjectVo> queryList(ProjectBo bo) {
        LambdaQueryWrapper<Project> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<Project> buildQueryWrapper(ProjectBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<Project> lqw = Wrappers.lambdaQuery();
        lqw.orderByDesc(Project::getId);
        lqw.like(StringUtils.isNotBlank(bo.getCode()), Project::getCode, bo.getCode());
        lqw.like(StringUtils.isNotBlank(bo.getName()), Project::getName, bo.getName());
        lqw.eq(StringUtils.isNotBlank(bo.getLanguage()), Project::getLanguage, bo.getLanguage());
        lqw.eq(StringUtils.isNotBlank(bo.getType()), Project::getType, bo.getType());
        lqw.eq(bo.getStatus() != null, Project::getStatus, bo.getStatus());
        lqw.like(StringUtils.isNotBlank(bo.getPdGpd()), Project::getPdGpd, bo.getPdGpd());
        lqw.like(StringUtils.isNotBlank(bo.getPmGpm()), Project::getPmGpm, bo.getPmGpm());
        lqw.like(StringUtils.isNotBlank(bo.getCtaGcta()), Project::getCtaGcta, bo.getCtaGcta());
        lqw.like(StringUtils.isNotBlank(bo.getSponsor()), Project::getSponsor, bo.getSponsor());
        lqw.like(StringUtils.isNotBlank(bo.getCro()), Project::getCro, bo.getCro());
        lqw.between(params.get("beginStartTime") != null && params.get("endStartTime") != null,
            Project::getStartTime, params.get("beginStartTime"), params.get("endStartTime"));
        lqw.between(params.get("beginEndTime") != null && params.get("endEndTime") != null,
            Project::getEndTime, params.get("beginEndTime"), params.get("endEndTime"));
        lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
            Project::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
        lqw.between(params.get("beginUpdateTime") != null && params.get("endUpdateTime") != null,
            Project::getUpdateTime, params.get("beginUpdateTime"), params.get("endUpdateTime"));

        // 只有拥有该项目数据权限的人才能够在列表中看到该项目
        String projects = userProjectsMapper.selectOne(
            Wrappers.lambdaQuery(SysUserProjects.class)
                .eq(SysUserProjects::getUserId, LoginHelper.getUserId())
        ).getProjects();
        if (projects.isEmpty()) {
            lqw.in(Project::getId, List.of(-1L));
        } else if (!projects.equals("*")) {
            lqw.in(Project::getId, Arrays.stream(projects.split(",")).map(Long::valueOf).toList());
        }

        return lqw;
    }

    /**
     * 新增项目管理
     *
     * @param bo 项目管理
     * @return 是否新增成功
     */
    @Override
    public Boolean insertByBo(ProjectBo bo) {
        Project add = MapstructUtils.convert(bo, Project.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    /**
     * 修改项目管理
     *
     * @param bo 项目管理
     * @return 是否修改成功
     */
    @Override
    public Boolean updateByBo(ProjectBo bo) {
        Project update = MapstructUtils.convert(bo, Project.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(Project entity) {
        //TODO 做一些数据校验,如唯一约束
    }

    /**
     * 校验并批量删除项目管理信息
     *
     * @param ids     待删除的主键集合
     * @param isValid 是否进行有效性校验
     * @return 是否删除成功
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteByIds(ids) > 0;
    }

    @Override
    public List<ProjectListByNameVo> getListByName(String name) {
        List<ProjectListByNameVo> vos = new ArrayList<>();
        baseMapper.selectList(
            Wrappers.lambdaQuery(Project.class)
                .like(StringUtils.isNotBlank(name), Project::getName, name)
        ).forEach(e -> {
            ProjectListByNameVo vo = new ProjectListByNameVo();
            vo.setId(e.getId());
            vo.setName(e.getName());
            vos.add(vo);
        });
        return vos;
    }

    @Override
    public List<ProjectListByNameVo> getList() {
        List<ProjectListByNameVo> vos = new ArrayList<>();
        baseMapper.selectList().forEach(e -> {
            ProjectListByNameVo vo = new ProjectListByNameVo();
            vo.setId(e.getId());
            vo.setName(e.getName());
            vos.add(vo);
        });
        return vos;
    }

    @Override
    public int updateStatus(ProjectUpdateStatusBo bo) {
        return baseMapper.update(
            Wrappers.lambdaUpdate(Project.class)
                .eq(Project::getId, bo.getId())
                .set(Project::getStatus, bo.getStatus())
        );
    }

    @Override
    public TableDataInfo<ProjectMemberListVo> selectMemberListById(Long id, PageQuery pageQuery) {
        List<Long> userIds = new ArrayList<>();
        Page<SysUserProjects> page = userProjectsMapper.selectPage(
            pageQuery.build(),
            Wrappers.lambdaQuery(SysUserProjects.class)
                .and(wrapper -> wrapper
                    .eq(SysUserProjects::getProjects, "*")
                    .or()
                    .apply(DataBaseHelper.findInSet(id, "projects")))
                .orderByDesc(SysUserProjects::getUserId)
        );
        page.getRecords().forEach(e -> userIds.add(e.getUserId()));
        if (userIds.isEmpty()) {
            return TableDataInfo.build();
        }
        Map<Long, SysUser> userMap = new HashMap<>();
        List<Long> deptIds = new ArrayList<>();
        Map<Long, String> deptMap = new HashMap<>();
        List<SysUser> userList = userMapper.selectByIds(userIds);
        userList.forEach(e -> {
            userMap.put(e.getUserId(), e);
            deptIds.add(e.getDeptId());
        });
        if (!deptIds.isEmpty()) {
            deptService.selectDeptByIds(deptIds)
                .forEach(e -> deptMap.put(e.getDeptId(), e.getDeptName()));
        }
        return TableDataInfo.build(page.convert(e -> {
            ProjectMemberListVo vo = new ProjectMemberListVo();
            SysUser user = userMap.get(e.getUserId());
            if (user == null) {
                return null;
            }
            vo.setId(user.getUserId());
            vo.setName(user.getNickName());
            vo.setPhoneNumber(user.getPhonenumber());
            vo.setDept(deptMap.get(user.getDeptId()));
            vo.setTime(user.getCreateTime());
            return vo;
        }));
    }


    @Override
    public TableDataInfo<ProjectOnDocumentVo> listOnDocument(ProjectOnDocumentBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<Project> wrapper = buildQueryOnDocumentWrapper(bo);
        IPage<Project> page = baseMapper.selectPage(pageQuery.build(), wrapper);
        return TableDataInfo.build(page.convert(e -> {
            ProjectOnDocumentVo vo = new ProjectOnDocumentVo();
            vo.setId(e.getId());
            vo.setCode(e.getCode());
            vo.setName(e.getName());
            vo.setLanguage(e.getLanguage());
            vo.setType(e.getType());
            vo.setStatus(ProjectStatusEnum.getLabel(e.getStatus()));
            vo.setStartTime(e.getStartTime());
            vo.setEndTime(e.getEndTime());
            vo.setCreateTime(e.getCreateTime());
            vo.setUpdateTime(e.getUpdateTime());
            return vo;
        }));
    }

    @Override
    public TableDataInfo<ProjectMemberNotInCenterVo> queryMemberNotInCenter(Long projectId, Long folderId, String name, PageQuery pageQuery) {
        // 只能是项目中的人
        List<Long> userIdsInProject = new ArrayList<>();
        userProjectsMapper.selectList(
            Wrappers.lambdaQuery(SysUserProjects.class)
                .and(wrapper -> wrapper
                    .eq(SysUserProjects::getProjects, "*")
                    .or()
                    .apply(DataBaseHelper.findInSet(projectId, "projects")))
                .orderByDesc(SysUserProjects::getUserId)
        ).forEach(e -> userIdsInProject.add(e.getUserId()));
        if (userIdsInProject.isEmpty()) {
            return TableDataInfo.build();
        }
        IPage<SysUser> page = userMapper.selectPage(
            pageQuery.build(),
            Wrappers.lambdaQuery(SysUser.class)
                .in(SysUser::getUserId, userIdsInProject)
                .like(StringUtils.isNotBlank(name), SysUser::getNickName, name)
        );
        if (page.getRecords().isEmpty()) {
            return TableDataInfo.build();
        }
        List<Long> deptIds = new ArrayList<>();
        Map<Long, String> deptMap = new HashMap<>();
        page.getRecords().forEach(e -> deptIds.add(e.getDeptId()));
        deptService.selectDeptByIds(deptIds).forEach(e -> deptMap.put(e.getDeptId(), e.getDeptName()));
        return TableDataInfo.build(page.convert(e -> {
            ProjectMemberNotInCenterVo vo = new ProjectMemberNotInCenterVo();
            vo.setId(e.getUserId());
            vo.setName(e.getNickName());
            vo.setDept(deptMap.get(e.getDeptId()));
            vo.setPhoneNumber(e.getPhonenumber());
            return vo;
        }));
    }

    @Override
    public TableDataInfo<ProjectInDashboardWorkbenchVo> queryPageListOnDashboardWorkbench(ProjectListOnDashboardWorkbenchBo bo, PageQuery pageQuery) {
        IPage<Project> page = baseMapper.selectPage(
            pageQuery.build(),
            Wrappers.lambdaQuery(Project.class)
                .like(StringUtils.isNotBlank(bo.getContent()), Project::getCode, bo.getContent())
                .or()
                .like(StringUtils.isNotBlank(bo.getContent()), Project::getName, bo.getContent())
                .or()
                .like(StringUtils.isNotBlank(bo.getContent()), Project::getPdGpd, bo.getContent())
                .or()
                .like(StringUtils.isNotBlank(bo.getContent()), Project::getPmGpm, bo.getContent())
                .or()
                .like(StringUtils.isNotBlank(bo.getContent()), Project::getCtaGcta, bo.getContent())
                .orderByDesc(Project::getId)
        );
        List<Long> projectIds = new ArrayList<>();
        page.getRecords().forEach(e -> projectIds.add(e.getId()));
        List<Document> documents = documentService.queryListByProjectIds(projectIds);
        Map<Long, List<Document>> documentMap = documents.stream().collect(Collectors.groupingBy(Document::getProjectId));
        return TableDataInfo.build(page.convert(e -> {
            List<Document> documentList = documentMap.get(e.getId());
            ProjectInDashboardWorkbenchVo vo = new ProjectInDashboardWorkbenchVo();
            vo.setId(e.getId());
            vo.setCode(e.getCode());
            vo.setName(e.getName());
            long total = 1L, onTimeSubmit = 0L, lateSubmit = 0L, notSubmit = 0L;
            if (documentList != null) {
                List<Document> planDocuments = documentList.stream()
                    .filter(item -> item.getType().equals(DocumentTypeConst.PLAN))
                    .toList();
                total = planDocuments.size();
                if (total > 0L) {
                    onTimeSubmit = planDocuments.stream()
                        .filter(item -> item.getActualDocument() != null)
                        .filter(item -> item.getSubmitTime().before(item.getSubmitDeadline()))
                        .count();
                    lateSubmit = planDocuments.stream()
                        .filter(item -> item.getActualDocument() != null)
                        .filter(item -> item.getSubmitDeadline().before(item.getSubmitTime()))
                        .count();
                    notSubmit = planDocuments.stream()
                        .filter(item -> item.getActualDocument() == null)
                        .count();
                }
            }

            // 如果 total == 0 , 那么计算两个比例的时候就会变为 NaN
            total = total > 0L ? total : 1L;
            vo.setOnTimeSubmissionRate(Double.valueOf(String.format("%.2f", (double) onTimeSubmit / total * 100L)));
            vo.setLateSubmissionCount(lateSubmit);
            vo.setSubmissionProgress(Double.valueOf(String.format("%.2f", (double) (total - notSubmit) / total * 100L)));
            vo.setPdGpd(e.getPdGpd());
            vo.setPmGpm(e.getPmGpm());
            vo.setCtaGcta(e.getCtaGcta());
            vo.setCreateTime(e.getCreateTime());
            vo.setUpdateTime(e.getUpdateTime());
            vo.setStartTime(e.getStartTime());
            vo.setEndTime(e.getEndTime());
            return vo;
        }));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean insertUser(ProjectAddMemberBo bo) {

        SysUserBo user = buildSysUser(bo);
        boolean flag = userService.insertUser(user) == 0;
        if (flag) {
            throw new RuntimeException("新增用户失败");
        }

        // 建立用户和文件夹之间的联系
        SysUserFolders userFolders = new SysUserFolders();
        userFolders.setUserId(user.getUserId());
        userFolders.setFolders(bo.getFolders());
        userFolders.setProjectId(bo.getProjectId());
        boolean userFoldersFlag = userFoldersMapper.insert(userFolders) == 0;
        if (userFoldersFlag) {
            throw new RuntimeException("建立用户与文件夹链接失败");
        }

        return true;
    }

    @Override
    public ProjectFolderVo getFoldersByUserId(Long userId, Long projectId) {
        ProjectFolderVo vo = new ProjectFolderVo();
        SysUserFolders folder = userFoldersMapper.selectOne(
            Wrappers.lambdaQuery(SysUserFolders.class)
                .eq(SysUserFolders::getUserId, userId)
                .eq(SysUserFolders::getProjectId, projectId)
        );
        vo.setFolders(Optional.ofNullable(folder).map(SysUserFolders::getFolders).orElse(""));
        return vo;
    }

    @Override
    public boolean updateFolders(ProjectMemberUpdateFoldersBo bo) {
        SysUserFolders sysUserFolders = userFoldersMapper.selectOne(
            Wrappers.lambdaUpdate(SysUserFolders.class)
                .eq(SysUserFolders::getUserId, bo.getUserId())
                .eq(SysUserFolders::getProjectId, bo.getProjectId())
        );
        if (sysUserFolders == null) {
            sysUserFolders = new SysUserFolders();
            sysUserFolders.setUserId(bo.getUserId());
            sysUserFolders.setProjectId(bo.getProjectId());
            sysUserFolders.setFolders(bo.getFolders());
            return userFoldersMapper.insert(sysUserFolders) > 0;
        }
        sysUserFolders.setFolders(bo.getFolders());
        return userFoldersMapper.updateById(sysUserFolders) > 0;
    }

    private SysUserBo buildSysUser(ProjectAddMemberBo bo) {
        SysUserBo user = new SysUserBo();
        user.setDeptId(bo.getDeptId());
        user.setUserName(bo.getUsername());
        user.setNickName(bo.getNickname());
        user.setUserType("sys_user");
        user.setEmail(bo.getEmail());
        user.setPhonenumber(bo.getPhoneNumber());
        user.setSex(bo.getGender());
        user.setPassword(BCrypt.hashpw(bo.getPassword()));
        user.setStatus(SystemConstants.NORMAL);
        user.setAppletStatus(UserAppletStatusConst.NORMAL);
        user.setRoleIds(bo.getRoleIds());
        user.setProjects(bo.getProjectId().toString());
        return user;
    }

    private LambdaQueryWrapper<Project> buildQueryOnDocumentWrapper(ProjectOnDocumentBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<Project> lqw = Wrappers.lambdaQuery();
        lqw.orderByDesc(Project::getId);
        lqw.like(StringUtils.isNotBlank(bo.getCode()), Project::getCode, bo.getCode());
        lqw.like(StringUtils.isNotBlank(bo.getName()), Project::getName, bo.getName());
        lqw.eq(StringUtils.isNotBlank(bo.getLanguage()), Project::getLanguage, bo.getLanguage());
        lqw.eq(StringUtils.isNotBlank(bo.getType()), Project::getType, bo.getType());
        lqw.eq(bo.getStatus() != null, Project::getStatus, bo.getStatus());
        lqw.between(params.get("beginStartTime") != null && params.get("endStartTime") != null,
            Project::getStartTime, params.get("beginStartTime"), params.get("endStartTime"));
        lqw.between(params.get("beginEndTime") != null && params.get("endEndTime") != null,
            Project::getEndTime, params.get("beginEndTime"), params.get("endEndTime"));
        lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
            Project::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
        lqw.between(params.get("beginUpdateTime") != null && params.get("endUpdateTime") != null,
            Project::getUpdateTime, params.get("beginUpdateTime"), params.get("endUpdateTime"));

        // 只有拥有该项目数据权限的人才能够在列表中看到该项目
        String projects = userProjectsMapper.selectOne(
            Wrappers.lambdaQuery(SysUserProjects.class)
                .eq(SysUserProjects::getUserId, LoginHelper.getUserId())
        ).getProjects();
        if (projects.isEmpty()) {
            lqw.in(Project::getId, List.of(-1L));
        } else if (!projects.equals("*")) {
            lqw.in(Project::getId, Arrays.stream(projects.split(",")).map(Long::valueOf).toList());
        }

        return lqw;
    }
}
