package com.ningdatech.pmapi.projectdeclared.manage;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ningdatech.basic.function.VUtils;
import com.ningdatech.basic.model.PageVo;
import com.ningdatech.pmapi.common.enumeration.ProjectProcessStageEnum;
import com.ningdatech.pmapi.common.helper.UserInfoHelper;
import com.ningdatech.pmapi.organization.model.entity.DingOrganization;
import com.ningdatech.pmapi.organization.service.IDingOrganizationService;
import com.ningdatech.pmapi.projectdeclared.model.dto.*;
import com.ningdatech.pmapi.projectdeclared.model.entity.ProjectDraft;
import com.ningdatech.pmapi.projectdeclared.model.vo.ProjectDraftVO;
import com.ningdatech.pmapi.projectdeclared.service.IProjectDraftService;
import com.ningdatech.pmapi.projectlib.enumeration.ProjectStatusEnum;
import com.ningdatech.pmapi.projectlib.manage.ProjectLibManage;
import com.ningdatech.pmapi.projectlib.model.dto.ProjectDTO;
import com.ningdatech.pmapi.projectlib.model.entity.Project;
import com.ningdatech.pmapi.projectlib.model.entity.ProjectApplication;
import com.ningdatech.pmapi.projectlib.model.entity.ProjectInst;
import com.ningdatech.pmapi.projectlib.model.req.ProjectListReq;
import com.ningdatech.pmapi.projectlib.model.vo.ProjectLibListItemVO;
import com.ningdatech.pmapi.projectlib.service.IProjectApplicationService;
import com.ningdatech.pmapi.projectlib.service.IProjectInstService;
import com.ningdatech.pmapi.projectlib.service.IProjectService;
import com.ningdatech.pmapi.user.security.auth.model.UserFullInfoDTO;
import com.ningdatech.pmapi.user.util.LoginUserUtil;
import com.wflow.bean.entity.WflowModels;
import com.wflow.bean.entity.WflowOrgModels;
import com.wflow.enums.OrgTypeEnum;
import com.wflow.enums.ProcessDefTypeEnum;
import com.wflow.exception.BusinessException;
import com.wflow.service.OrgProcdefService;
import com.wflow.workflow.bean.dto.OrgInfoDTO;
import com.wflow.workflow.bean.vo.ProcessStartParamsVo;
import com.wflow.workflow.service.ProcessInstanceService;
import com.wflow.workflow.service.ProcessModelService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * @Classname DeclaredProjectManage
 * @Description
 * @Date 2023/2/1 14:48
 * @Author PoffyZhang
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class DeclaredProjectManage {

    private final IProjectService projectService;

    private final IProjectApplicationService projectApplicationService;

    private final IProjectDraftService projectDraftService;

    private final ProcessInstanceService processService;

    private final ProcessModelService processModelService;

    private final IProjectInstService projectInstService;

    private final DefaultDeclaredProjectManage defaultDeclaredProjectManage;

    private final ProjectLibManage projectlibManager;
    
    private final UserInfoHelper userInfoHelper;

    /**
     * 新项目 启动实例
     *
     * @param dto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public String startTheProcess(DefaultDeclaredDTO dto) {
        Long userId = LoginUserUtil.getUserId();
        VUtils.isTrue(Objects.isNull(userId)).throwMessage("获取登录用户失败!");

        ProjectDTO projectInfo = dto.getProjectInfo();

        //如果是重新提交的话 判断下 项目是否存在
        if(Objects.nonNull(projectInfo.getId())){
            Project oldProject = projectService.getById(projectInfo.getId());
            VUtils.isTrue(Objects.isNull(oldProject))
                    .throwMessage(String.format("重新提交失败 该项目【%s】不存在",projectInfo.getId()));

            //重新要判断 项目当前状态 是不是 单位内部审核失败
            VUtils.isTrue(!ProjectStatusEnum.UNDER_INTERNAL_AUDIT_NOT_PASS.getCode().equals(oldProject.getStatus()) ||
                            !ProjectStatusEnum.NOT_APPROVED.getCode().equals(oldProject.getStage()))
                    .throwMessage("重新提交失败 该项目不是 单位内部拒绝或者未立项阶段");

        }

        String regionCode = projectInfo.getAreaCode();

        WflowModels model = processModelService.getOne(Wrappers.lambdaQuery(WflowModels.class)
                .eq(WflowModels::getRegionCode, regionCode)
                .eq(WflowModels::getProcessType, ProjectProcessStageEnum.ORG_INTERNAL_APPROVAL_PROCESS.getCode())
                .last("limit 1"));

        if (Objects.isNull(model)) {
            log.error("此 【{}】区域找不到单位流程配置", regionCode);
            throw new BusinessException(String.format("此 【%s】区域找不到单位流程配置", regionCode));
        }

        //项目名称去重
        defaultDeclaredProjectManage.checkDuplication(projectInfo);

        ProcessStartParamsVo params = new ProcessStartParamsVo();
        params.setUser(defaultDeclaredProjectManage.buildUser(userId));
        params.setProcessUsers(Collections.emptyMap());
        //放入条件判断的项目字段
        ProjectConditionDTO conditionDto = new ProjectConditionDTO();
        BeanUtils.copyProperties(dto.getProjectInfo(), conditionDto);
        dto.getFormData().putAll(
                JSON.parseObject(JSON.toJSONString(conditionDto), new TypeReference<Map<String, Object>>() {
                })
        );
        params.setFormData(dto.getFormData());
        //开始申报
        // 获取发起单位、发起单位主管单位流程信息map
        Map<String, OrgInfoDTO> orgModelMap = defaultDeclaredProjectManage.getOrgModelInfo(userId, model);
        String instanceId = processService.newStartProcess(model.getProcessDefId(),model.getFormId(), params,orgModelMap);
        log.info("申报项目成功 【{}】", instanceId);

        //如果是重新提交的话 判断下 项目是否存在
        saveOrUpdateProject(dto.getProjectInfo(), instanceId, regionCode,userId);

        return instanceId;
    }


    /**
     * 重新提交 启动实例
     *
     * @param dto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public String reStartTheProcess(DefaultDeclaredDTO dto) {
        Long userId = LoginUserUtil.getUserId();
        VUtils.isTrue(Objects.isNull(userId)).throwMessage("获取登录用户失败!");

        ProjectDTO projectDto = dto.getProjectInfo();
        VUtils.isTrue(Objects.isNull(projectDto.getId())).throwMessage("提交失败 缺少项目ID!");
        Project projectInfo = projectService.getById(projectDto.getId());
        VUtils.isTrue(Objects.isNull(projectInfo)).throwMessage("提交失败 此项目不存在!");

        String regionCode = projectInfo.getAreaCode();
        WflowModels model = processModelService.getOne(Wrappers.lambdaQuery(WflowModels.class)
                .eq(WflowModels::getRegionCode, regionCode)
                .eq(WflowModels::getProcessType, ProjectProcessStageEnum.ORG_INTERNAL_APPROVAL_PROCESS.getCode())
                .last("limit 1"));

        if (Objects.isNull(model)) {
            log.error("此 【{}】区域找不到单位流程配置", regionCode);
            throw new BusinessException(String.format("此 【%s】区域找不到单位流程配置", regionCode));
        }

        //首先要判断 项目当前状态 是不是 单位内部拒绝
        VUtils.isTrue(!ProjectStatusEnum.UNDER_INTERNAL_AUDIT_NOT_PASS.getCode().equals(projectInfo.getStatus()) ||
                        !ProjectStatusEnum.NOT_APPROVED.getCode().equals(projectInfo.getStage()))
                .throwMessage("提交失败 该项目不是 单位内部拒绝审核状态或者未立项阶段");

        ProcessStartParamsVo params = new ProcessStartParamsVo();
        params.setUser(defaultDeclaredProjectManage.buildUser(userId));
        params.setProcessUsers(Collections.emptyMap());
        //放入条件判断的项目字段
        ProjectConditionDTO conditionDto = new ProjectConditionDTO();
        BeanUtils.copyProperties(dto.getProjectInfo(), conditionDto);
        dto.getFormData().putAll(
                JSON.parseObject(JSON.toJSONString(conditionDto), new TypeReference<Map<String, Object>>() {
                })
        );
        params.setFormData(dto.getFormData());
        // 获取发起单位、发起单位主管单位流程信息map
        Map<String, OrgInfoDTO> orgModelMap = defaultDeclaredProjectManage.getOrgModelInfo(userId, model);
        String instanceId = processService.newStartProcess(model.getProcessDefId(),model.getFormId(), params,orgModelMap);
        log.info("重新申报项目成功 【{}】", instanceId);

        //保存项目
        saveOrUpdateProject(projectDto,instanceId,regionCode,userId);

        return instanceId;
    }

    /**
     * 申报项目 时 新增项目到项目库
     *
     * @param projectDto
     * @param instanceId
     */
    private void saveOrUpdateProject(ProjectDTO projectDto, String instanceId,
                                     String regionCode,Long userId) {
        //流程启动之后 入库项目 重要业务信息 用于列表查询 展示
        try {
            //保存项目表信息
            Project project = new Project();
            BeanUtils.copyProperties(projectDto, project);
            project.setCreateOn(LocalDateTime.now());
            project.setUpdateOn(LocalDateTime.now());
            project.setAreaCode(regionCode);
            project.setStage(ProjectStatusEnum.NOT_APPROVED.getCode());
            project.setStatus(ProjectStatusEnum.UNDER_INTERNAL_AUDIT.getCode());
            project.setInstCode(instanceId);
            project.setSponsor(userId);
            projectService.saveOrUpdate(project);
            //保存项目应用
            if (CollUtil.isNotEmpty(projectDto.getApplicationList())) {
                //采取批量删除 批量添加的方式
                projectApplicationService.remove(Wrappers.lambdaQuery(ProjectApplication.class)
                        .eq(ProjectApplication::getProjectId,project.getId()));
                List<ProjectApplication> applications = projectDto.getApplicationList().stream().map(application -> {
                    ProjectApplication projectApplication = new ProjectApplication();
                    BeanUtils.copyProperties(application, projectApplication);
                    projectApplication.setProjectId(project.getId());
                    return projectApplication;
                }).collect(Collectors.toList());
                projectApplicationService.saveBatch(applications);
            }
            //保存项目和实例的关系
            ProjectInst projectInst = new ProjectInst();
            projectInst.setProjectId(project.getId());
            projectInst.setInstCode(instanceId);
            projectInst.setCreatOn(LocalDateTime.now());
            projectInst.setUpdateOn(LocalDateTime.now());
            projectInst.setInstType(ProjectProcessStageEnum.ORG_INTERNAL_APPROVAL_PROCESS.getCode());
            projectInstService.save(projectInst);
        } catch (Exception e) {
            log.error("项目信息入库错误 ", e);
            throw new BusinessException("项目信息入库错误 :" + e.getMessage());
        }
    }

    public PageVo<ProjectDraftVO> pageDraft(DeclaredProjectListParamDTO params) {
        Long userId = LoginUserUtil.getUserId();
        Page<ProjectDraft> page = params.page();
        LambdaQueryWrapper<ProjectDraft> wrapper = Wrappers.lambdaQuery(ProjectDraft.class)
                .eq(ProjectDraft::getUserId,userId)
                .ge(Objects.nonNull(params.getStartTime()), ProjectDraft::getCreateOn, params.getStartTime())
                .le(Objects.nonNull(params.getEndTime()), ProjectDraft::getCreateOn, params.getEndTime())
                .eq(Objects.nonNull(params.getProjectType()), ProjectDraft::getProjectType, params.getProjectType())
                .eq(Objects.nonNull(params.getProjectYear()), ProjectDraft::getProjectYear, params.getProjectYear())
                .eq(Objects.nonNull(params.getProjectStage()), ProjectDraft::getStage, params.getProjectStage())
                .eq(Objects.nonNull(params.getProjectStatus()), ProjectDraft::getStatus, params.getProjectStatus())
                .like(StringUtils.isNotBlank(params.getProjectName()), ProjectDraft::getProjectName, params.getProjectName())
                .orderByDesc(ProjectDraft::getUpdateOn);
        projectDraftService.page(page, wrapper);
        if (0L == page.getTotal()) {
            return PageVo.empty();
        }
        List<ProjectDraftVO> res = page.getRecords().stream().map(record -> {
            ProjectDraftVO vo = new ProjectDraftVO();
            BeanUtils.copyProperties(record, vo);
            return vo;
        }).collect(Collectors.toList());
        return PageVo.of(res, page.getTotal());
    }

    public ProjectDraftVO draftDatail(Long id) {
        ProjectDraft draft = projectDraftService.getById(id);
        ProjectDraftVO vo = new ProjectDraftVO();
        BeanUtils.copyProperties(draft, vo);
        if(StringUtils.isNotBlank(draft.getProjectApplicationList())){
            vo.setApplicationList(JSON.parseArray(draft.getProjectApplicationList(),ProjectApplication.class));
        }
        if(StringUtils.isNotBlank(draft.getDynamicForm())){
            vo.setDynamicForm(JSON.parseObject(draft.getDynamicForm(),Map.class));
        }
        return vo;
    }

    /**
     * 保存至草稿箱
     *
     * @param dto
     * @return
     */
    public Long saveToDraft(ProjectDraftSaveDTO dto) {
        Long userId = LoginUserUtil.getUserId();
        ProjectDTO projectInfo = dto.getProjectInfo();
        ProjectDraft draft = new ProjectDraft();
        BeanUtils.copyProperties(projectInfo, draft);
        draft.setUserId(dto.getUser().getUserId());
        if(CollUtil.isNotEmpty(projectInfo.getDynamicForm())){
            draft.setDynamicForm(JSON.toJSONString(projectInfo.getDynamicForm()));
        }
        if(CollUtil.isNotEmpty(projectInfo.getApplicationList())){
            draft.setProjectApplicationList(JSON.toJSONString(projectInfo.getApplicationList()));
        }
        if (Objects.isNull(draft.getId())) {
            draft.setCreateOn(LocalDateTime.now());
        }
        draft.setUpdateOn(LocalDateTime.now());
        draft.setUserId(String.valueOf(userId));
        projectDraftService.saveOrUpdate(draft);
        return draft.getId();
    }

    /**
     * 项目列表
     * @param req
     * @return
     */
    public PageVo<ProjectLibListItemVO> projectLibList(ProjectListReq req) {
        Long userId = LoginUserUtil.getUserId();
        VUtils.isTrue(Objects.isNull(userId)).throwMessage("获取登录用户失败!");
        UserFullInfoDTO userFullInfo = userInfoHelper.getUserFullInfo(userId);
        //放入用户的单位
        req.setBuildOrgCode(userFullInfo.getOrganizationCode());
        return projectlibManager.projectLibList(req);
    }
}
