package org.rnorth.ducttape;

import org.junit.Before;
import org.junit.Test;
import org.rnorth.ducttape.circuitbreakers.Breaker;
import org.rnorth.ducttape.circuitbreakers.BreakerBuilder;
import org.rnorth.ducttape.circuitbreakers.ExampleService;
import org.rnorth.ducttape.circuitbreakers.State;
import org.rnorth.ducttape.ratelimits.RateLimiter;
import org.rnorth.ducttape.ratelimits.RateLimiterBuilder;
import org.rnorth.ducttape.timeouts.Timeouts;
import org.rnorth.ducttape.unreliables.Unreliables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

import static org.rnorth.visibleassertions.VisibleAssertions.assertEquals;
import static org.rnorth.visibleassertions.VisibleAssertions.assertTrue;

/**
 * Created by rnorth on 29/08/2015.
 */
public class CompositeTest {

    private static final Logger LOGGER = LoggerFactory.getLogger(CompositeTest.class);
    private Breaker circuitBreaker;
    private RateLimiter rateLimiter;

    @Before
    public void setUp() throws Exception {
        circuitBreaker = BreakerBuilder.newBuilder().build();
        rateLimiter = RateLimiterBuilder.newBuilder()
                .withRate(20, TimeUnit.SECONDS)
                .withConstantThroughput()
                .build();
    }

    @Test
    public void simpleCompositeTest() throws Exception {
        ExampleService exampleService = new ExampleService(20L);
        String result = execute(exampleService);

        assertEquals("the result is passed through", "value", result);
        assertEquals("the breaker is not tripped", State.OK, circuitBreaker.getState());
        assertEquals("the service was only called once", 1, exampleService.getInvocationCount());
    }

    @Test
    public void simpleCompositeTestWhenTimingOut() throws Exception {
        ExampleService exampleService = new ExampleService(110L);
        String result = execute(exampleService);

        assertEquals("the default value is returned", "default value", result);
        assertEquals("the breaker is tripped", State.BROKEN, circuitBreaker.getState());
        int invocationCount = exampleService.getInvocationCount();
        assertTrue("the service was called less than 40 times (max 20 per second for 2 seconds)", invocationCount <= 40);
    }

    private String execute(ExampleService exampleService) throws Exception {

        return circuitBreaker.tryGet(() -> {
            return Unreliables.retryUntilSuccess(2, TimeUnit.SECONDS, () -> {
                return rateLimiter.getWhenReady(() -> {
                    return Timeouts.getWithTimeout(100, TimeUnit.MILLISECONDS, () -> {
                        return exampleService.getValue("Hello World");
                    });
                });
            });
        }, () -> {
            LOGGER.error("Circuit breaker was tripped");
        }, () -> {
            return "default value";
        });

    }
}