package com.hz.pm.api.projectdeclared.manage;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.excel.EasyExcel;
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.hz.pm.api.common.enumeration.CommonEnum;
import com.hz.pm.api.common.enumeration.ProjectProcessStageEnum;
import com.hz.pm.api.common.helper.RegionCacheHelper;
import com.hz.pm.api.common.helper.UserInfoHelper;
import com.hz.pm.api.common.model.constant.BizConst;
import com.hz.pm.api.common.model.constant.CommonConst;
import com.hz.pm.api.common.statemachine.util.StateMachineUtil;
import com.hz.pm.api.common.util.ExcelDownUtil;
import com.hz.pm.api.common.util.ExcelExportStyle;
import com.hz.pm.api.projectdeclared.model.dto.ConstructionPlanExportDTO;
import com.hz.pm.api.projectdeclared.model.dto.ContructionSuggestionsDTO;
import com.hz.pm.api.projectdeclared.model.dto.DefaultDeclaredDTO;
import com.hz.pm.api.projectdeclared.model.entity.ProjectConstructionSuggestions;
import com.hz.pm.api.projectdeclared.model.req.ConstrctionPlanListReq;
import com.hz.pm.api.projectdeclared.service.IConstrctionSuggestionsService;
import com.hz.pm.api.projectlib.helper.ProjectHelper;
import com.hz.pm.api.projectlib.manage.ProjectLibManage;
import com.hz.pm.api.projectlib.model.dto.ProjectDTO;
import com.hz.pm.api.projectlib.model.entity.Project;
import com.hz.pm.api.projectlib.model.entity.ProjectInst;
import com.hz.pm.api.projectlib.model.enumeration.InstTypeEnum;
import com.hz.pm.api.projectlib.model.enumeration.ProjectStatusEnum;
import com.hz.pm.api.projectlib.model.enumeration.ProjectTypeNewEnum;
import com.hz.pm.api.projectlib.model.req.ProjectListReq;
import com.hz.pm.api.projectlib.model.vo.ProjectLibListItemVO;
import com.hz.pm.api.projectlib.service.IProjectInstService;
import com.hz.pm.api.projectlib.service.IProjectService;
import com.hz.pm.api.staging.enums.MsgTypeEnum;
import com.hz.pm.api.sys.manage.NoticeManage;
import com.hz.pm.api.todocenter.constant.WorkNoticeConstant;
import com.hz.pm.api.user.security.model.UserFullInfoDTO;
import com.hz.pm.api.user.util.LoginUserUtil;
import com.ningdatech.basic.exception.BizException;
import com.ningdatech.basic.function.VUtils;
import com.ningdatech.basic.model.PageVo;
import com.ningdatech.basic.util.NdDateUtils;
import com.wflow.bean.entity.WflowModels;
import com.wflow.exception.BusinessException;
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.flowable.engine.HistoryService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

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

    private final IProjectService projectService;
    private final ProcessInstanceService processService;
    private final ProcessModelService processModelService;
    private final StateMachineUtil stateMachineUtil;
    private final IProjectInstService projectInstService;
    private final ProjectLibManage projectLibManage;
    private final UserInfoHelper userInfoHelper;
    private final DefaultDeclaredProjectManage declaredProjectManage;
    private final DefaultDeclaredProjectManage defaultDeclaredProjectManage;
    private final NoticeManage noticeManage;
    private final RegionCacheHelper regionCacheHelper;
    private final HistoryService historyService;

    private final IConstrctionSuggestionsService constrctionSuggestionsService;

    /**
     * 建设方案
     *
     * @param dto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public String startTheProcess(DefaultDeclaredDTO dto) {
        UserFullInfoDTO user = userInfoHelper.getUserFullInfo(LoginUserUtil.getUserId());
        Long userId = user.getUserId();
        ProjectDTO projectInfo = dto.getProjectInfo();
        Long projectInfoId = projectInfo.getId();
        VUtils.isTrue(Objects.isNull(projectInfoId)).throwMessage("提交失败，缺少项目ID!");
        Project oldProject = projectService.getById(projectInfoId);
        VUtils.isTrue(StringUtils.isBlank(projectInfo.getConstructionPlanFile())).throwMessage("提交失败，请提交建设方案!");

        // 判断 项目当前状态 是不是 方案待申报
        VUtils.isTrue(!ProjectStatusEnum.PLAN_TO_BE_DECLARED.getCode().equals(oldProject.getStatus()) ||
                        !ProjectStatusEnum.NOT_APPROVED.getCode().equals(oldProject.getStage()))
                .throwMessage("提交失败 该项目不是 方案待申报状态或者未立项阶段");

        projectInfo.setBuildOrgCode(user.getMhUnitIdStr());
        projectInfo.setBuildOrgName(user.getMhUnitName());

        // 项目名称去重
        if (StringUtils.isNotBlank(projectInfo.getProjectName()) && !projectInfo.getProjectName()
                .equals(oldProject.getProjectName())) {
            projectInfo.setProjectCode(oldProject.getProjectCode());
            defaultDeclaredProjectManage.checkDuplication(projectInfo);
        }
        //判断申报金额 是否等于总的 判断年度支付金额 是否等于总金额
        defaultDeclaredProjectManage.checkAmount(projectInfo);

        //如果主管单位没有 那么主管单位就是自己
        if (CommonEnum.NO.getCode().equals(projectInfo.getIsSuperOrg())) {
            projectInfo.setSuperOrgCode(user.getMhUnitIdStr());
            projectInfo.setSuperOrg(user.getMhUnitName());
        }

        Project constructProject = new Project();
        BeanUtils.copyProperties(projectInfo, constructProject);
        constructProject.setStatus(oldProject.getStatus());
        constructProject.setStage(oldProject.getStage());

        WflowModels model = processModelService.getOne(Wrappers.lambdaQuery(WflowModels.class)
                .eq(WflowModels::getProcessType, ProjectProcessStageEnum.CONSTRUCTION_PROJECT_APPROVAL_PROCESS.getCode())
                .last(BizConst.LIMIT_1));

        if (Objects.isNull(model)) {
            throw new BusinessException("找不到建设申报流程配置");
        }

        //如果被禁用了的话 直接跳过 进入到下一个状态
        if (Boolean.TRUE.equals(model.getIsStop())) {
            //被禁用了 调2次状态机
            stateMachineUtil.pass(constructProject);
            stateMachineUtil.pass(constructProject);
            constructProject.setUpdateOn(LocalDateTime.now());
            projectService.updateById(constructProject);
            return "因为建设方案流程被禁用了 直接跳过!";
        }

        ProcessStartParamsVo params = new ProcessStartParamsVo();
        params.setUser(declaredProjectManage.buildUser(userId));
        params.setProcessUsers(Collections.emptyMap());
        //放入条件判断的项目字段
        //把条件值给放入工作流
        defaultDeclaredProjectManage.buildCondition(params, oldProject, dto);
        // 获取发起单位、发起单位主管单位、发起单位上级条线主管单位信息
        Map<String, OrgInfoDTO> orgModelMap = defaultDeclaredProjectManage.buildOrgModelMap(userId, constructProject);
        String instanceId = processService.startProcessLs(model, params, orgModelMap);
        log.info("建设方案项目申报成功 【{}】", instanceId);

        // 保存建设项目相关
        Project buildProject;
        if (Boolean.TRUE.equals(dto.getRestart())) {
            //如果是重新提交 不用生成新版本 前面已经生成过了
            buildProject = contructionPlanModifyProject(oldProject, instanceId);
        } else {
            buildProject = projectLibManage.saveConstructProjectInDeclared(projectInfo, instanceId, userId, oldProject);
        }

        //发送给第一个审批人消息
        noticeManage.sendFirstUser(buildProject, model.getFormName(), instanceId,
                WorkNoticeConstant.PASS_MSG_TEMPLATE, MsgTypeEnum.PROJECT_REVIEW);

        return instanceId;
    }

    private Project contructionPlanModifyProject(Project project, String instanceId) {
        stateMachineUtil.pass(project);
        project.setUpdateOn(LocalDateTime.now());
        project.setInstCode(instanceId);
        projectService.updateById(project);

        //保存项目和实例的关系
        ProjectInst projectInst = new ProjectInst();
        projectInst.setProjectId(project.getId());
        projectInst.setInstCode(instanceId);
        projectInst.setCreatOn(LocalDateTime.now());
        projectInst.setUpdateOn(LocalDateTime.now());
        projectInst.setInstType(ProjectProcessStageEnum.CONSTRUCTION_PROJECT_APPROVAL_PROCESS.getCode());
        projectInstService.save(projectInst);

        return project;
    }

    /**
     * 驳回 重新提交建设方案
     *
     * @param dto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public String restartTheProcess(DefaultDeclaredDTO dto) {
        ProjectDTO projectDto = dto.getProjectInfo();
        VUtils.isTrue(Objects.isNull(projectDto.getId())).throwMessage("提交失败 缺少项目ID!");
        Project projectInfo = projectService.getById(projectDto.getId());
        VUtils.isTrue(Objects.isNull(projectInfo)).throwMessage("提交失败 此项目不存在!");
        VUtils.isTrue(StringUtils.isBlank(projectDto.getConstructionPlanFile())).throwMessage("提交失败 请提交建设方案!");
        //直接先到待方案审批
        Project project = projectLibManage.saveProjectWithVersionAndStatus(projectDto, null,
                ProjectStatusEnum.PLAN_TO_BE_DECLARED.getCode(), Boolean.TRUE);
        dto.getProjectInfo().setId(project.getId());
        dto.setRestart(Boolean.TRUE);
        return startTheProcess(dto);
    }

    /**
     * 提交建设方案项目 时 更新信息
     *
     * @param project
     * @param instanceId
     */
    private void modifyProject(Project project, String instanceId, String constructionPlanFile) {
        //流程启动之后 入库项目 重要业务信息 用于列表查询 展示
        try {
            project.setUpdateOn(LocalDateTime.now());
            project.setInstCode(instanceId);
            project.setConstructionPlanFile(constructionPlanFile);
            //调用状态机 进入下一个通过状态
            stateMachineUtil.pass(project);
            projectService.updateById(project);

            //保存项目和实例的关系
            ProjectInst projectInst = new ProjectInst();
            projectInst.setProjectId(project.getId());
            projectInst.setInstCode(instanceId);
            projectInst.setInstType(InstTypeEnum.CONSTRUCTION_PLAN_REVIEW.getCode());
            projectInst.setCreatOn(LocalDateTime.now());
            projectInst.setUpdateOn(LocalDateTime.now());
            projectInstService.save(projectInst);
        } catch (Exception e) {
            log.error("提交建设方案 项目信息修改 错误 ", e);
            throw new BusinessException("提交建设方案 项目信息修改 错误 :" + e.getMessage());
        }
    }

    /**
     * 查项目
     *
     * @param planReq
     * @return
     */
    public PageVo<ProjectLibListItemVO> projectLibList(ConstrctionPlanListReq planReq) {
        //限定参数 复制bean
        ProjectListReq req = new ProjectListReq();
        BeanUtils.copyProperties(planReq, req);
        //项目阶段 状态 已定 方案待申报
        req.setStage(ProjectStatusEnum.NOT_APPROVED.getCode());
        req.setStatus(ProjectStatusEnum.PLAN_TO_BE_DECLARED.getCode());
        UserFullInfoDTO user = userInfoHelper.getUserFullInfo(LoginUserUtil.getUserId());
        VUtils.isTrue(Objects.isNull(user)).throwMessage("获取登录用户失败!");
        //放入用户的单位
        req.setBuildOrgCode(user.getMhUnitIdStr());
        return projectLibManage.projectLibList(req);
    }

    public void exportList(HttpServletResponse response, ConstrctionPlanListReq planReq) {
        //限定参数 复制bean
        ProjectListReq req = new ProjectListReq();
        BeanUtils.copyProperties(planReq, req);
        //项目阶段 状态 已定 方案待申报
        req.setStage(ProjectStatusEnum.NOT_APPROVED.getCode());
        req.setStatus(ProjectStatusEnum.PLAN_TO_BE_DECLARED.getCode());
        UserFullInfoDTO user = userInfoHelper.getUserFullInfo(LoginUserUtil.getUserId());
        VUtils.isTrue(Objects.isNull(user)).throwMessage("获取登录用户失败!");
        //放入用户的单位
        req.setBuildOrgCode(user.getMhUnitIdStr());
        req.setPageNumber(CommonConst.EXPORT_PAGE_NUMBER);
        req.setPageSize(CommonConst.EXPORT_PAGE_SIZE);
        LambdaQueryWrapper<Project> query = ProjectHelper.projectQuery(req);
        Page<Project> page = projectService.page(req.page(), query);
        List<Project> records = page.getRecords();


        List<ConstructionPlanExportDTO> collect = records.stream().map(r -> {
            ConstructionPlanExportDTO exportDTO = new ConstructionPlanExportDTO();
            BeanUtils.copyProperties(r, exportDTO);
            exportDTO.setProjectTypeName(ProjectTypeNewEnum.getDesc(r.getProjectType()));
            String createOnStr = NdDateUtils.format(r.getCreateOn(), "yyyy-MM-dd HH:mm");
            exportDTO.setCreateOn(createOnStr);
            return exportDTO;
        }).collect(Collectors.toList());
        for (int i = 0; i < collect.size(); i++) {
            collect.get(i).setSerialNumber(i + 1);
        }
        String fileName = "建设方案申报项目列表";
        ExcelDownUtil.setFileName(fileName, response);
        //数据导出处理函数
        try {
            EasyExcel.write(response.getOutputStream(), ConstructionPlanExportDTO.class)
                    .autoCloseStream(false)
                    .registerWriteHandler(ExcelExportStyle.formalStyle())
                    .sheet(fileName)
                    .doWrite(collect);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 专家建设方案建议 暂存 等流程成功后 保存到项目
     *
     * @param dto
     */
    public void constructionSuggestions(ContructionSuggestionsDTO dto) {
        String instanceId = dto.getInstanceId();

        Project project = projectService.getProjectByCode(dto.getProjectCode());
        if (Objects.isNull(project)) {
            throw new BizException("该项目不存在!");
        }
        ProjectInst projectInst = projectInstService.getOne(Wrappers.lambdaQuery(ProjectInst.class)
                .eq(ProjectInst::getInstCode, instanceId)
                .eq(ProjectInst::getProjectId, project.getId())
                .last(BizConst.LIMIT_1));
        if (Objects.isNull(projectInst)) {
            throw new BizException("该流程的项目关联信息不存在!");
        }

        if (!InstTypeEnum.CONSTRUCTION_PLAN_REVIEW.getCode().equals(projectInst.getInstType())) {
            throw new BizException("此流程不是建设方案流程 保存失败!");
        }

        ProjectConstructionSuggestions saveEntity = BeanUtil.copyProperties(dto, ProjectConstructionSuggestions.class);
        ProjectConstructionSuggestions pcs = constrctionSuggestionsService.getOne(Wrappers.lambdaQuery(ProjectConstructionSuggestions.class)
                .eq(ProjectConstructionSuggestions::getProjectCode, dto.getProjectCode())
                .eq(ProjectConstructionSuggestions::getInstanceId, instanceId)
                .last(BizConst.LIMIT_1));
        if (Objects.nonNull(pcs)) {
            saveEntity.setId(pcs.getId());
        }
        saveEntity.setInstanceId(instanceId);
        constrctionSuggestionsService.saveOrUpdate(saveEntity);
    }

}
