/* (C) 2024 YiRing, Inc. */
package com.yiring.common.core;

import jakarta.validation.constraints.NotNull;
import java.util.Objects;
import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.RetryContext;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.backoff.FixedBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Component;

/**
 * 重试执行器（重试者）
 * 用途: 主要用于解决切面重试机制的问题，减少自定义实现 AOP 重试机制的代码量
 *
 * @author Jim
 */

@SuppressWarnings("unused")
@Slf4j
@Component
@EnableRetry
public class Retriever {

    /**
     * 包装重试方法实现
     * 默认 RetryTemplate 配置为 1s 重试间隔 重试 3 次，可用过自定义参数配置
     *
     * @param accomplish 重试业务实现
     * @param <S>        重试业务的返回值类型
     * @return 重试业务的返回值，例如：一个连接对象、网络请求结果
     */
    public <S> S execute(@NotNull RetrySupplier<S> accomplish) {
        return execute(accomplish, null, null);
    }

    /**
     * 包装重试方法实现
     * 默认 RetryTemplate 配置为 1s 重试间隔 重试 3 次，可用过自定义参数配置
     *
     * @param accomplish 重试业务实现
     * @param consumer   重试模板
     * @param <S>        重试业务的返回值类型
     * @return 重试业务的返回值，例如：一个连接对象、网络请求结果
     */
    public <S> S execute(@NotNull RetrySupplier<S> accomplish, Consumer<RetryTemplate> consumer) {
        return execute(accomplish, null, consumer);
    }

    /**
     * 包装重试方法实现
     *
     * @param accomplish 重试业务实现
     * @param recover    重试失败的回退实现
     * @param consumer   重试模板
     * @param <S>        重试业务的返回值类型
     * @return 重试业务的返回值，例如：一个连接对象、网络请求结果
     */
    public <S> S execute(
        @NotNull RetrySupplier<S> accomplish,
        RetrySupplier<S> recover,
        Consumer<RetryTemplate> consumer
    ) {
        // 创建默认的重试模板
        RetryTemplate template = RetryTemplate.builder().retryOn(Exception.class).build();

        // 重试模板的自定义实现，例如一些重试机制的配置
        if (Objects.nonNull(consumer)) {
            consumer.accept(template);
        }

        // 通过模板执行重试实现机制和默认的回退实现
        return template.execute(
            accomplish::get,
            ctx -> {
                if (Objects.nonNull(recover)) {
                    return recover.get(ctx);
                }

                // 没有添加保底恢复实现的情况下，抛出原始异常
                Throwable throwable = ctx.getLastThrowable();
                if (throwable == null) {
                    throw new RuntimeException("retry fail");
                }

                log.warn(
                    "[Retriever] execute fail, retry count: {}, e: {}",
                    ctx.getRetryCount(),
                    throwable.getMessage()
                );

                if (throwable instanceof Exception e) {
                    throw e;
                } else {
                    throw new RuntimeException(throwable.getMessage(), throwable);
                }
            }
        );
    }

    /**
     * 默认的重试策略，重试 3 次，每次间隔 1s
     *
     * @param template 重试模板
     */
    public static void defaultPolicy(RetryTemplate template) {
        setSimplePolicy(template, 3, 1000L);
    }

    /**
     * 设置简单的重试策略
     * 复杂的自定义重试策略可以使用 Consumer 函数回调自行实现
     *
     * @param template      重试模板
     * @param maxAttempts   最大重试次数
     * @param backOffPeriod 重试间隔时间
     */
    public static void setSimplePolicy(RetryTemplate template, int maxAttempts, long backOffPeriod) {
        // 设置简单的重试次数
        SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy();
        simpleRetryPolicy.setMaxAttempts(maxAttempts);
        template.setRetryPolicy(simpleRetryPolicy);

        // 设置固定的重试时间间隔，单位: ms 毫秒
        FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
        backOffPolicy.setBackOffPeriod(backOffPeriod);
        template.setBackOffPolicy(backOffPolicy);
    }

    @FunctionalInterface
    public interface RetrySupplier<R> {
        R get(RetryContext ctx);
    }
}
