package com.tutego.ch_04.retry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;

@Component
class RandomPhoto {
    private final Logger logger = LoggerFactory.getLogger(getClass());

    private final int MAX_ATTEMPTS = 5;
    private int unsafeCounter = 1;

    public RandomPhoto() throws IOException {
        logger.info("RetryTemplate retry attempts");

        // somewhat easier to tinker
        var retryTemplate = RetryTemplate.builder()
                .maxAttempts(MAX_ATTEMPTS)
                .retryOn(IOException.class)
                .uniformRandomBackoff(100 /* ms */, 1000 /* ms */)
                .build();

        retryTemplate.execute(
                retryContext -> {
                    var json = this.receive("male");
                    // the declarative approach limits the Context exposure
                    if (retryContext.getRetryCount() == MAX_ATTEMPTS - 1) logger.info("RetryCallback<T, E> result: {}", json);
                    return json;
                },
                recoveryCallback -> {
                    logger.info("RecoveryCallback<T> call");
                    return "{}";
                }
        );

        unsafeCounter = 1;
        logger.info("Declarative retry attempts");
    }

    @Retryable(
            retryFor = IOException.class,
            noRetryFor = NullPointerException.class,
            maxAttempts = MAX_ATTEMPTS - 1,
            backoff = @Backoff(
                    delay = 500L,
                    multiplier = 0.25D,
                    random = true
            )
    )
    public String receive(String gender) throws IOException {
        logger.info("Attempts: {}", unsafeCounter++);
        if (unsafeCounter <= MAX_ATTEMPTS) throw new IOException("Not yet ready to serve");

        var url = "https://randomuser.me/api/?inc=picture&noinfo&gender=" + gender;
        try (var inputStream = URI.create(url).toURL().openStream()) {
            return StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
        }
    }

    // The Throwable first argument is optional (but a method without it will only be called if no others match).
    // Subsequent arguments are populated from the argument list of the failed method in order.
    @Recover
    public String fallback(IOException exception, String gender) {
        logger.info("Fallback for call 'receive(String {})' with exception {}", gender, exception.getMessage());
        return "{}";
    }

}