package cn.axzo.workflow.core.engine.interceptor;

import cn.axzo.workflow.common.exception.WorkflowEngineException;
import cn.axzo.workflow.core.common.utils.TraceUtil;
import cn.axzo.workflow.core.engine.cmd.AbstractCommand;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.exceptions.PersistenceException;
import org.flowable.common.engine.impl.interceptor.AbstractCommandInterceptor;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandConfig;
import org.flowable.common.engine.impl.interceptor.CommandExecutor;

import static cn.axzo.workflow.common.code.FlowableEngineRespCode.ENGINE_ASYNC_COMMAND_EXECUTION_RETRY_GIVE_UP;

/**
 * 基于 RetryInterceptor 的命令重试拦截器, 本意想用来处理多人同时操作同一个任务, 但会存在超过重试次数后,会丢弃原有的命令.
 *
 * @author wangli
 * @since 2024/7/1 13:51
 */
@Slf4j
public class CustomRetryInterceptor extends AbstractCommandInterceptor {

    protected int numOfRetries = 3;
    protected int waitTimeInMs = 60;
    protected int waitIncreaseFactor = 5;

    @Override
    public <T> T execute(CommandConfig config, Command<T> command, CommandExecutor commandExecutor) {
        long waitTime = waitTimeInMs;
        int failedAttempts = 0;
        Throwable lastException = null;
        do {
            if (failedAttempts > 0) {
                log.warn("Waiting for {}ms before retrying the command.", waitTime);
                waitBeforeRetry(waitTime);
                waitTime *= waitIncreaseFactor;
            }

            try {
                // try to execute the command
                if (AbstractCommand.class.isAssignableFrom(command.getClass())) {
                    // 如果在以后,重试三次也不能解决的话, 可以利用这里的拿到的参数,重新自动构造CMD,并执行.
                    log.warn("Executing command params: {} traceId:{} ", TraceUtil.traceId(),
                            ((AbstractCommand<T>) command).paramToJsonString());
                }
                return next.execute(config, command, commandExecutor);

            } catch (PersistenceException e) {
                log.warn("Caught persistence exception: {}", e.getMessage(), e);
                lastException = e;
            }

            failedAttempts++;
        } while (failedAttempts <= numOfRetries);

        throw new WorkflowEngineException(ENGINE_ASYNC_COMMAND_EXECUTION_RETRY_GIVE_UP, String.valueOf(numOfRetries), lastException.getMessage());
    }

    protected void waitBeforeRetry(long waitTime) {
        try {
            Thread.sleep(waitTime);
        } catch (InterruptedException e) {
            log.warn("I am interrupted while waiting for a retry.");
        }
    }

    public void setNumOfRetries(int numOfRetries) {
        this.numOfRetries = numOfRetries;
    }

    public void setWaitIncreaseFactor(int waitIncreaseFactor) {
        this.waitIncreaseFactor = waitIncreaseFactor;
    }

    public void setWaitTimeInMs(int waitTimeInMs) {
        this.waitTimeInMs = waitTimeInMs;
    }

    public int getNumOfRetries() {
        return numOfRetries;
    }

    public int getWaitIncreaseFactor() {
        return waitIncreaseFactor;
    }

    public int getWaitTimeInMs() {
        return waitTimeInMs;
    }
}
