package com.ningdatech.pmapi.projectdeclared.manage;

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.ningdatech.basic.function.VUtils;
import com.ningdatech.basic.model.PageVo;
import com.ningdatech.basic.util.NdDateUtils;
import com.ningdatech.pmapi.common.constant.BizConst;
import com.ningdatech.pmapi.common.constant.CommonConst;
import com.ningdatech.pmapi.common.constant.RegionConst;
import com.ningdatech.pmapi.common.enumeration.CommonEnum;
import com.ningdatech.pmapi.common.enumeration.ProjectProcessStageEnum;
import com.ningdatech.pmapi.common.helper.RegionCacheHelper;
import com.ningdatech.pmapi.common.helper.UserInfoHelper;
import com.ningdatech.pmapi.common.statemachine.util.StateMachineUtils;
import com.ningdatech.pmapi.common.util.ExcelDownUtil;
import com.ningdatech.pmapi.common.util.ExcelExportStyle;
import com.ningdatech.pmapi.projectdeclared.model.dto.ConstructionPlanExportDTO;
import com.ningdatech.pmapi.projectdeclared.model.dto.DefaultDeclaredDTO;
import com.ningdatech.pmapi.projectdeclared.model.req.ConstrctionPlanListReq;
import com.ningdatech.pmapi.projectlib.enumeration.InstTypeEnum;
import com.ningdatech.pmapi.projectlib.enumeration.ProjectStatusEnum;
import com.ningdatech.pmapi.projectlib.enumeration.ProjectTypeEnum;
import com.ningdatech.pmapi.projectlib.helper.ProjectHelper;
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.ProjectInst;
import com.ningdatech.pmapi.projectlib.model.req.ProjectListReq;
import com.ningdatech.pmapi.projectlib.model.vo.ProjectLibListItemVO;
import com.ningdatech.pmapi.projectlib.service.IProjectInstService;
import com.ningdatech.pmapi.projectlib.service.IProjectService;
import com.ningdatech.pmapi.staging.enums.MsgTypeEnum;
import com.ningdatech.pmapi.sys.manage.NoticeManage;
import com.ningdatech.pmapi.todocenter.constant.WorkNoticeConstant;
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.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.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 StateMachineUtils stateMachineUtils;
    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;

    /**
     * 建设方案
     *
     * @param dto
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    public String startTheProcess(DefaultDeclaredDTO dto) {
        UserFullInfoDTO user = userInfoHelper.getUserFullInfo(LoginUserUtil.getUserId());
        String employeeCode = user.getEmployeeCode();
        VUtils.isTrue(Objects.isNull(user) || Objects.isNull(employeeCode))
                .throwMessage("获取登录用户失败!");

        ProjectDTO projectInfo = dto.getProjectInfo();
        Long projectInfoId = projectInfo.getId();
        VUtils.isTrue(Objects.isNull(projectInfoId)).throwMessage("提交失败 缺少项目ID!");
        Project oldProject = projectService.getById(projectInfoId);
        VUtils.isTrue(Objects.isNull(projectInfo)).throwMessage("提交失败 此项目不存在!");
        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("提交失败 该项目不是 方案待申报状态或者未立项阶段");

        if(StringUtils.isNotBlank(user.getRegionCode())){
            projectInfo.setAreaCode(user.getRegionCode());
            projectInfo.setArea(regionCacheHelper.getRegionName(user.getRegionCode(), RegionConst.RL_COUNTY));
        }

        projectInfo.setBuildOrgCode(user.getEmpPosUnitCode());
        projectInfo.setBuildOrgName(user.getEmpPosUnitName());

        // 项目名称去重
        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.getEmpPosUnitCode());
            projectInfo.setSuperOrg(user.getEmpPosUnitName());
        }

        //如果是撤回重新提交的话 判断下 项目是否存在
        if(Objects.nonNull(projectInfo.getId())){
            //新申报的项目不允许带项目id
            projectInfo.setId(null);
        }

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

        String regionCode = user.getRegionCode();

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

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

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

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

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

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

        return instanceId;
    }

    private Project contructionPlanModifyProject(Project project, String instanceId) {
        stateMachineUtils.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) {
        UserFullInfoDTO user = userInfoHelper.getUserFullInfo(LoginUserUtil.getUserId());
        VUtils.isTrue(Objects.isNull(user) || Objects.isNull(user.getEmployeeCode()))
                .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("提交失败 此项目不存在!");
        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);
            //调用状态机 进入下一个通过状态
            stateMachineUtils.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.getEmpPosUnitCode());
        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.getEmpPosUnitCode());
        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(ProjectTypeEnum.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);
        }
    }
}
