package com.ningdatech.pmapi.common.statemachine.builder;

import com.ningdatech.pmapi.common.statemachine.action.ProjectDeclareChoiceAction;
import com.ningdatech.pmapi.common.statemachine.action.ProjectDeclareAction;
import com.ningdatech.pmapi.common.statemachine.factory.ProjectDeclareGuardFactory;
import com.ningdatech.pmapi.common.statemachine.event.ProjectStatusChangeEvent;
import com.ningdatech.pmapi.projectlib.enumeration.ProjectStatusEnum;
import com.ningdatech.pmapi.projectlib.model.entity.Project;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineBuilder;
import org.springframework.statemachine.persist.DefaultStateMachinePersister;
import org.springframework.statemachine.persist.StateMachinePersister;
import org.springframework.statemachine.support.DefaultStateMachineContext;
import org.springframework.stereotype.Component;

import java.util.EnumSet;

/**
 * 项目申报状态机
 *
 * @author CMM
 * @since 2023/02/07 15:56
 */
@Component
@EnableStateMachine(name= ProjectDeclareStateMachineBuilder.MACHINEID_TO)
@RequiredArgsConstructor
public class ProjectDeclareStateMachineBuilder {
    private static final ProjectDeclareGuardFactory projectDeclareGuardFactory = new ProjectDeclareGuardFactory();
    private static final ProjectDeclareAction projectDeclareAction = new ProjectDeclareAction();
    private final BeanFactory beanFactory;
    private Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * TO状态机
     */
    public final static String MACHINEID_TO = "MACHINEID_TO";
    public StateMachine<ProjectStatusEnum, ProjectStatusChangeEvent> build() throws Exception {
        StateMachine<ProjectStatusEnum, ProjectStatusChangeEvent> stateMachine = build(beanFactory);
        logger.info("状态机ID：" + stateMachine.getId());
        stateMachine.start();
        return stateMachine;
    }
    /**
     * 构建状态机
     * @param beanFactory
     * @author CMM
     * @since 2023/02/07 16:15
     */
    public StateMachine<ProjectStatusEnum, ProjectStatusChangeEvent> build(BeanFactory beanFactory) throws Exception {
        StateMachineBuilder.Builder<ProjectStatusEnum, ProjectStatusChangeEvent> builder = StateMachineBuilder.builder();
        builder.configureConfiguration()
                .withConfiguration()
                .machineId(MACHINEID_TO)
                .beanFactory(beanFactory);

        builder.configureStates()
                .withStates()
                .initial(ProjectStatusEnum.UNDER_INTERNAL_AUDIT)
                .choice(ProjectStatusEnum.PENDING_PREQUALIFICATION_CHOICE)
                .states(EnumSet.allOf(ProjectStatusEnum.class));

        builder.configureTransitions()
                // 单位内部审核通过,从单位内部审核中到待预审
                .withExternal()
                .source(ProjectStatusEnum.UNDER_INTERNAL_AUDIT)
                .target(ProjectStatusEnum.PENDING_PREQUALIFICATION)
                .event(ProjectStatusChangeEvent.UNDER_INTERNAL_PASS).and()
                // 单位内部审核驳回，从单位内部审核中到单位内部审核不通过
                .withExternal()
                .source(ProjectStatusEnum.UNDER_INTERNAL_AUDIT)
                .target(ProjectStatusEnum.UNDER_INTERNAL_AUDIT_NOT_PASS)
                .event(ProjectStatusChangeEvent.UNDER_INTERNAL_REJECT).and()
                // 单位内部审核中撤回，从单位内部审核中到待申报
                .withExternal()
                .source(ProjectStatusEnum.UNDER_INTERNAL_AUDIT)
                .target(ProjectStatusEnum.TO_BE_DECLARED)
                .event(ProjectStatusChangeEvent.UNDER_INTERNAL_WITHDRAW).and()
                // 待预审预审申报，从待预审到待预审选择
                .withExternal()
                .source(ProjectStatusEnum.PENDING_PREQUALIFICATION)
                .target(ProjectStatusEnum.PENDING_PREQUALIFICATION_CHOICE)
                .event(ProjectStatusChangeEvent.PRELIMINARY_REVIEW_DECLARE).and()
                // 待预审申报，从待预审选择->省级部门联审中，预审中，完成其中一种状态
                .withChoice()
                .source(ProjectStatusEnum.PENDING_PREQUALIFICATION_CHOICE)
                .first(ProjectStatusEnum.JOINT_REVIEW_BY_PROVINCIAL_DEPARTMENTS,projectDeclareGuardFactory.new PendingPreQualificationChoiceGuard(),projectDeclareAction.new ProjectDeclareChoiceAction())
                .last(ProjectStatusEnum.PRE_APPLYING,projectDeclareAction.new ProjectDeclareChoiceAction()).and()
                // 省级部门联审通过，从省级部门联审中到预审中
                .withExternal()
                .source(ProjectStatusEnum.JOINT_REVIEW_BY_PROVINCIAL_DEPARTMENTS)
                .target(ProjectStatusEnum.PRE_APPLYING)
                .event(ProjectStatusChangeEvent.PROVINCIAL_DEPARTMENT_REVIEW_PASS).and()
                // 省级部门联审不通过，从省级部门联审中到省级部门联审不通过
                .withExternal()
                .source(ProjectStatusEnum.JOINT_REVIEW_BY_PROVINCIAL_DEPARTMENTS)
                .target(ProjectStatusEnum.THE_JOINT_REVIEW_OF_PROVINCIAL_DEPARTMENTS_FAILED)
                .event(ProjectStatusChangeEvent.PROVINCIAL_DEPARTMENT_REVIEW_REJECT).and()
                // 省级部门联审中撤回，从省级部门联审中到省级部门联审不通过
                .withExternal()
                .source(ProjectStatusEnum.JOINT_REVIEW_BY_PROVINCIAL_DEPARTMENTS)
                .target(ProjectStatusEnum.PENDING_PREQUALIFICATION)
                .event(ProjectStatusChangeEvent.JOINT_REVIEW_BY_PROVINCIAL_DEPARTMENTS_WITHDRAW).and()
                // 预审通过，从预审中到部门联审中
                .withExternal()
                .source(ProjectStatusEnum.PRE_APPLYING)
                .target(ProjectStatusEnum.DEPARTMENT_JOINT_REVIEW)
                .event(ProjectStatusChangeEvent.PRELIMINARY_REVIEW_PASS).and()
                // 预审驳回，从预审中到预审不通过
                .withExternal()
                .source(ProjectStatusEnum.PRE_APPLYING)
                .target(ProjectStatusEnum.PREQUALIFICATION_FAILED)
                .event(ProjectStatusChangeEvent.PRELIMINARY_REVIEW_REJECT).and()
                // 预审中撤回，从预审中到待预审选择
                .withExternal()
                .source(ProjectStatusEnum.PRE_APPLYING)
                .target(ProjectStatusEnum.PENDING_PREQUALIFICATION_CHOICE)
                .event(ProjectStatusChangeEvent.PRE_APPLYING_WITHDRAW).and()
                // 预审中撤回，从待预审选择->省级部门联审中，待预审，完成其中一种状态
                .withChoice()
                .source(ProjectStatusEnum.PRE_APPLYING)
                .first(ProjectStatusEnum.JOINT_REVIEW_BY_PROVINCIAL_DEPARTMENTS,projectDeclareGuardFactory.new PendingPreQualificationChoiceGuard(),new ProjectDeclareChoiceAction())
                .last(ProjectStatusEnum.PENDING_PREQUALIFICATION,projectDeclareAction.new ProjectDeclareChoiceAction()).and()
                // 部门联审通过，从部门联审中到年度计划中
                .withExternal()
                .source(ProjectStatusEnum.DEPARTMENT_JOINT_REVIEW)
                .target(ProjectStatusEnum.IN_THE_ANNUAL_PLAN)
                .event(ProjectStatusChangeEvent.DEPARTMENT_UNITED_REVIEW_PASS).and()
                // 部门联审驳回，从部门联审中到部门联审不通过
                .withExternal()
                .source(ProjectStatusEnum.DEPARTMENT_JOINT_REVIEW)
                .target(ProjectStatusEnum.DEPARTMENT_JOINT_REVIEW_FAILED)
                .event(ProjectStatusChangeEvent.DEPARTMENT_UNITED_REVIEW_REJECT).and()
                // 部门联审中撤回，从部门联审中到预审中
                .withExternal()
                .source(ProjectStatusEnum.DEPARTMENT_JOINT_REVIEW)
                .target(ProjectStatusEnum.PRE_APPLYING)
                .event(ProjectStatusChangeEvent.DEPARTMENT_JOINT_REVIEW_WITHDRAW).and()
                // 年度计划中开启方案申报，从年度计划中到方案待申报
                .withExternal()
                .source(ProjectStatusEnum.IN_THE_ANNUAL_PLAN)
                .target(ProjectStatusEnum.PLAN_TO_BE_DECLARED)
                .event(ProjectStatusChangeEvent.ANNUAL_PLAN_PROJECT_OPEN_PLAN_DECLARE).and()
                // 年度计划中暂缓，从年度计划中到被暂缓
                .withExternal()
                .source(ProjectStatusEnum.IN_THE_ANNUAL_PLAN)
                .target(ProjectStatusEnum.BE_SUSPENDED)
                .event(ProjectStatusChangeEvent.ANNUAL_PLAN_SUSPEND).and()
                // 方案待申报申报方案，从方案待申报到方案评审中
                .withExternal()
                .source(ProjectStatusEnum.PLAN_TO_BE_DECLARED)
                .target(ProjectStatusEnum.SCHEME_UNDER_REVIEW)
                .event(ProjectStatusChangeEvent.DECLARE_PLAN).and()
                // 方案评审通过，从方案评审中到待立项批复
                .withExternal()
                .source(ProjectStatusEnum.SCHEME_UNDER_REVIEW)
                .target(ProjectStatusEnum.TO_BE_APPROVED)
                .event(ProjectStatusChangeEvent.PLAN_REVIEW_PASS).and()
                // 方案评审驳回，从方案评审中到方案评审不通过
                .withExternal()
                .source(ProjectStatusEnum.SCHEME_UNDER_REVIEW)
                .target(ProjectStatusEnum.SCHEME_REVIEW_FAILED)
                .event(ProjectStatusChangeEvent.PLAN_REVIEW_REJECT).and()
                // 方案评审中撤回，从方案评审中到方案待申报
                .withExternal()
                .source(ProjectStatusEnum.SCHEME_UNDER_REVIEW)
                .target(ProjectStatusEnum.PLAN_TO_BE_DECLARED)
                .event(ProjectStatusChangeEvent.SCHEME_UNDER_REVIEW_WITHDRAW).and()
                // 待立项批复批复，从待立项批复到待采购
                .withExternal()
                .source(ProjectStatusEnum.TO_BE_APPROVED)
                .target(ProjectStatusEnum.TO_BE_PURCHASED)
                .event(ProjectStatusChangeEvent.PROJECT_APPROVAL).and()
                // 待采购采购备案，从待采购到建设中
                .withExternal()
                .source(ProjectStatusEnum.TO_BE_PURCHASED)
                .target(ProjectStatusEnum.UNDER_CONSTRUCTION)
                .event(ProjectStatusChangeEvent.PURCHASE_PUT_ON_RECORD).and()
                // 建设中初验备案，从建设中到待终验
                .withExternal()
                .source(ProjectStatusEnum.UNDER_CONSTRUCTION)
                .target(ProjectStatusEnum.TO_BE_FINALLY_INSPECTED)
                .event(ProjectStatusChangeEvent.PRELIMINARY_ACCEPTANCE_PUT_ON_RECORD).and()
                // 待终验终验申请，从待终验到终验审核中
                .withExternal()
                .source(ProjectStatusEnum.TO_BE_FINALLY_INSPECTED)
                .target(ProjectStatusEnum.FINAL_ACCEPTANCE_IS_UNDER_REVIEW)
                .event(ProjectStatusChangeEvent.FINAL_ACCEPTANCE_APPLICATION).and()
                // 终审审核通过，从终审审核中到已归档
                .withExternal()
                .source(ProjectStatusEnum.FINAL_ACCEPTANCE_IS_UNDER_REVIEW)
                .target(ProjectStatusEnum.ARCHIVED)
                .event(ProjectStatusChangeEvent.FINAL_ACCEPTANCE_PASS).and()
                // 终审审核不通过，从终审审核中到终审审核不通过
                .withExternal()
                .source(ProjectStatusEnum.FINAL_ACCEPTANCE_IS_UNDER_REVIEW)
                .target(ProjectStatusEnum.FINAL_ACCEPTANCE_REVIEW_FAILED)
                .event(ProjectStatusChangeEvent.FINAL_ACCEPTANCE_REJECT).and()
                // 终审审核中撤回，从终审审核中到待终验
                .withExternal()
                .source(ProjectStatusEnum.FINAL_ACCEPTANCE_IS_UNDER_REVIEW)
                .target(ProjectStatusEnum.FINAL_ACCEPTANCE_IS_UNDER_REVIEW)
                .event(ProjectStatusChangeEvent.FINAL_ACCEPTANCE_IS_UNDER_REVIEW_WITHDRAW).and();
        return builder.build();
    }

    /**
     * 持久化配置
     * @author CMM
     * @since 2023/02/07 16:22
     */
    @Bean(name = "projectDeclareStateMachinePersister")
    public StateMachinePersister<ProjectStatusEnum, ProjectStatusChangeEvent, Project> getProjectPersister() {
        return new DefaultStateMachinePersister<>(new StateMachinePersist<ProjectStatusEnum, ProjectStatusChangeEvent, Project>() {
            @Override
            public void write(StateMachineContext<ProjectStatusEnum, ProjectStatusChangeEvent> context, Project contextObj) {
            }

            @Override
            public StateMachineContext<ProjectStatusEnum, ProjectStatusChangeEvent> read(Project contextObj) {
                StateMachineContext<ProjectStatusEnum, ProjectStatusChangeEvent> result = new DefaultStateMachineContext(ProjectStatusEnum.getValue(contextObj.getStatus()),
                        null, null, null, null, MACHINEID_TO);
                return result;
            }
        });
    }
}
