package cn.axzo.workflow.core.conf;

import cn.axzo.workflow.core.common.utils.SpringContextUtils;
import cn.axzo.workflow.core.engine.behavior.CustomActivityBehaviorFactory;
import cn.axzo.workflow.core.engine.cmd.CustomCommandContextFactory;
import cn.axzo.workflow.core.engine.formhandler.CustomFormFieldHandler;
import cn.axzo.workflow.core.engine.id.BasedNacosSnowflakeIdGenerator;
import cn.axzo.workflow.core.engine.interceptor.CustomRetryInterceptor;
import cn.axzo.workflow.core.engine.job.AsyncAbortProcessInstanceJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncActivityCallbackJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncActivityLeaveJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncActivitySetAssigneeJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncActivityTriggerJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncApproveTaskJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncApproveTaskWithFormJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncBackTaskJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncCancelProcessInstanceJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncCountersignUserTaskJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncExtTaskInstJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncRejectTaskJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncRemindTaskJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncResetApproversUserTaskJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncTermNodeAlterJobHandler;
import cn.axzo.workflow.core.engine.job.AsyncTransferUserTaskJobHandler;
import cn.axzo.workflow.core.engine.job.NextActivityConfigCheckJobHandler;
import cn.axzo.workflow.core.engine.job.exception.handle.CustomAsyncJobLogClearTraceExceptionHandler;
import cn.axzo.workflow.core.engine.job.exception.handle.CustomAsyncRunnableExceptionExceptionHandler;
import cn.axzo.workflow.core.engine.persistence.CustomMybatisHistoricProcessInstanceDataManager;
import cn.axzo.workflow.core.service.BpmnProcessActivityService;
import cn.axzo.workflow.core.service.ExtAxHiTaskInstService;
import cn.azxo.framework.common.constatns.Constants;
import cn.hutool.core.util.ReflectUtil;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.cloud.nacos.NacosServiceManager;
import com.google.common.collect.Lists;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Target;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.flowable.common.engine.api.delegate.event.FlowableEventListener;
import org.flowable.common.engine.impl.history.HistoryLevel;
import org.flowable.job.service.JobProcessor;
import org.flowable.spring.SpringProcessEngineConfiguration;
import org.flowable.spring.boot.EngineConfigurationConfigurer;
import org.slf4j.MDC;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.lang.Nullable;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static org.flowable.common.engine.impl.AbstractEngineConfiguration.DB_SCHEMA_UPDATE_TRUE;

/**
 * Flowable 引擎相关全局配置
 *
 * @author wangli
 * @since 2023/7/13 11:18
 */
@Slf4j
@Configuration
public class FlowableConfiguration {

    @Value("${workflow.enableVerboseExecutionTreeLogging:false}")
    private Boolean enableVerboseExecutionTreeLogging;

    @Bean
    public EngineConfigurationConfigurer<SpringProcessEngineConfiguration> processEngineConfigurer(
            ObjectProvider<FlowableEventListener> listeners,
            CustomActivityBehaviorFactory customActivityBehaviorFactory,
            ExtAxHiTaskInstService extAxHiTaskInstService,
            BpmnProcessActivityService bpmnProcessActivityService,
            List<JobProcessor> jobProcessors,
            NacosServiceManager nacosServiceManager,
            NacosDiscoveryProperties nacosDiscoveryProperties,
            SupportRefreshProperties refreshProperties,
            List<ProcessExtConfigurer> configurers) {
        return configuration -> {
            configuration.setEnableHistoricTaskLogging(true);
            configuration.setHistoryLevel(HistoryLevel.AUDIT);
            configuration.setHistory(HistoryLevel.AUDIT.getKey());
            configuration.setJobProcessors(jobProcessors);
            configuration.setEventListeners(Lists.newArrayList(listeners));
            configuration.setActivityBehaviorFactory(customActivityBehaviorFactory);
            configuration.setDatabaseSchemaUpdate(DB_SCHEMA_UPDATE_TRUE);
            configuration.setEnableSafeBpmnXml(false);
            //            configuration.setCreateDiagramOnDeploy(false);
//            configuration.setIdGenerator(new TimeBasedIdGenerator());
//            configuration.setIdGenerator(new DistributedTimeBasedIdGenerator(stringRedisTemplate));
            configuration.setIdGenerator(new BasedNacosSnowflakeIdGenerator(nacosServiceManager, nacosDiscoveryProperties));
            configuration.setHistoricProcessInstanceDataManager(new CustomMybatisHistoricProcessInstanceDataManager(configuration));
            // 自定义的异步任务处理器
            configuration.addCustomJobHandler(new AsyncAbortProcessInstanceJobHandler(extAxHiTaskInstService));
            configuration.addCustomJobHandler(new AsyncActivitySetAssigneeJobHandler());
            configuration.addCustomJobHandler(new AsyncActivityTriggerJobHandler());
            configuration.addCustomJobHandler(new AsyncApproveTaskJobHandler());
            configuration.addCustomJobHandler(new AsyncBackTaskJobHandler());
            configuration.addCustomJobHandler(new AsyncCancelProcessInstanceJobHandler(extAxHiTaskInstService));
            configuration.addCustomJobHandler(new AsyncCountersignUserTaskJobHandler(extAxHiTaskInstService));
            configuration.addCustomJobHandler(new AsyncExtTaskInstJobHandler(extAxHiTaskInstService));
            configuration.addCustomJobHandler(new AsyncRejectTaskJobHandler(extAxHiTaskInstService));
            configuration.addCustomJobHandler(new AsyncTransferUserTaskJobHandler());
            configuration.addCustomJobHandler(new AsyncTermNodeAlterJobHandler(refreshProperties));
            configuration.addCustomJobHandler(new AsyncCountersignUserTaskJobHandler(extAxHiTaskInstService));
            configuration.addCustomJobHandler(new AsyncActivityLeaveJobHandler(bpmnProcessActivityService));
            configuration.addCustomJobHandler(new AsyncActivityCallbackJobHandler());
            configuration.addCustomJobHandler(new AsyncApproveTaskWithFormJobHandler());
            configuration.addCustomJobHandler(new AsyncRemindTaskJobHandler(refreshProperties));
            configuration.addCustomJobHandler(new AsyncResetApproversUserTaskJobHandler(extAxHiTaskInstService));
            configuration.addCustomJobHandler(new NextActivityConfigCheckJobHandler());
            configurers.forEach(i -> configuration.addCustomJobHandler(i.getJobHandler()));
            // 异步任务异常重试时间间隔
            configuration.setDefaultFailedJobWaitTime(30);
            configuration.setAsyncFailedJobWaitTime(30);
            configuration.getAsyncExecutorConfiguration().setAsyncJobLockTime(Duration.ofMinutes(5));
            configuration.getAsyncExecutorConfiguration().setGlobalAcquireLockEnabled(true);
            configuration.setAddDefaultExceptionHandler(false);
            configuration.setEnableVerboseExecutionTreeLogging(enableVerboseExecutionTreeLogging);
            configuration.setCustomAsyncRunnableExecutionExceptionHandlers(Lists.newArrayList(
                    new CustomAsyncJobLogClearTraceExceptionHandler(),
                    // 移除掉该异常处理器，避免因为 job 抛出 WorkflowEngineException 时，不会重试的问题
//                    new CustomWorkflowEngineExceptionHandler(),
                    new CustomAsyncRunnableExceptionExceptionHandler()));
            configuration.setCommandContextFactory(new CustomCommandContextFactory());
            configuration.setCustomPreCommandInterceptors(Lists.newArrayList(
                    new CustomRetryInterceptor()
            ));
            // form configuration
            configuration.setFormFieldValidationEnabled(true);
            configuration.setFormFieldHandler(new CustomFormFieldHandler());
            configuration.setConfigurators(Lists.newArrayList(new CustomJobServiceConfiguration()));
        };
    }

    @Bean
    public CustomActivityBehaviorFactory customActivityBehaviorFactory() {
        return new CustomActivityBehaviorFactory();
    }

    @Bean
    @Order
    public SpringContextUtils springContextUtils(SpringContext springContext) {
        return new SpringContextUtils(springContext);
    }

    @Configuration
    public static class SpringContext implements SpringContextUtils.SpringContext, ApplicationContextAware {
        private ApplicationContext applicationContext;

        public SpringContext() {
        }

        public <T> T getBean(Class<T> clazz) {
            return this.applicationContext.getBean(clazz);
        }

        public <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) {
            return this.applicationContext.getBeansOfType(type);
        }

        public void setApplicationContext(@Nullable ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    }

    @Configuration
    @Slf4j
    @Profile({"local","dev", "test"})
    public static class FeignConfiguration implements RequestInterceptor {
        @Resource
        private Environment environment;
        @Value("${ossEnvUrl:http://dev-app.axzo.cn/oss}")
        private String ossEvnUrl;
        private static String POD_NAMESPACE;

        static {
            Map<String, String> env = System.getenv();
            if (env != null) {
                POD_NAMESPACE = env.get("MY_POD_NAMESPACE");
            }
            log.info("init FeignConfig, POD_NAMESPACE value is {}", POD_NAMESPACE);
        }

        @SneakyThrows
        @Override
        public void apply(RequestTemplate requestTemplate) {
            if (POD_NAMESPACE == null) {
                Target.HardCodedTarget target = (Target.HardCodedTarget) requestTemplate.feignTarget();
                String url = requestTemplate.feignTarget().url();
                // 如需修改微服务地址,建议通过外部化参数来调整
                url = url.replace("http://oss:9123", ossEvnUrl);
                String profile = environment.getProperty("spring.profiles.active");
                if (Objects.equals(profile, "test") && url.contains("dev-app.axzo.cn")) {
                    url = url.replace("dev-app", "test-api");
                }
                if (Objects.equals(profile, "pre") && url.contains("dev-app.axzo.cn")) {
                    url = url.replace("dev-app", "pre-api");
                }
                if (Objects.equals(profile, "live") && url.contains("dev-app.axzo.cn")) {
                    url = url.replace("dev-app", "live-api");
                }
                requestTemplate.target(url);
                Field field = ReflectUtil.getField(target.getClass(), "url");
                field.setAccessible(true);
                field.set(target, url);
            }
            requestTemplate.header(Constants.CTX_LOG_ID_MDC, MDC.get(Constants.CTX_LOG_ID_MDC));
            requestTemplate.header("X-SERVER-NAME", environment.getProperty("spring.application.name"));
            log.info("FeignConfig traceId:{}", MDC.get(Constants.CTX_LOG_ID_MDC));
        }
    }

}
