package com.atlassian.bitbucket.internal.crowd.sso;

import com.atlassian.cache.Supplier;
import com.atlassian.cache.memory.MemoryCacheManager;
import com.atlassian.failurecache.util.date.Clock;
import com.atlassian.fugue.Option;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.Date;
import java.util.concurrent.TimeUnit;

import static com.atlassian.fugue.Option.some;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class SsoConfigurationCacheTest {

    @Mock
    private SsoConfiguration config;
    @Mock
    private Supplier<Option<SsoConfiguration>> supplier;
    private SsoConfigurationCache cache;
    private MutableClock clock;

    @Before
    public void setUp() throws Exception {
        clock = new MutableClock();
        cache = new SsoConfigurationCache(new MemoryCacheManager(), supplier, 100, TimeUnit.SECONDS,
                100, TimeUnit.MILLISECONDS, clock);
    }

    @Test
    public void testCaching() {
        Option<SsoConfiguration> value = some(config);
        when(supplier.get()).thenReturn(value);

        assertSame(value, cache.get());
        assertSame(value, cache.get());
        assertSame(value, cache.get());
        assertSame(value, cache.get());

        verify(supplier, times(1)).get();

        cache.reset();
        assertSame(value, cache.get());
        assertSame(value, cache.get());

        verify(supplier, times(2)).get();
    }

    @Test
    public void testRetry() throws Exception {
        when(supplier.get()).thenThrow(new RuntimeException("oh nooos"));

        Option<SsoConfiguration> failValue = Option.none();
        assertEquals(failValue, cache.get());
        verify(supplier, times(1)).get();

        // subsequent calls should not trigger calls to delegate.get() until at least 1s has passed
        assertEquals(failValue, cache.get());
        assertEquals(failValue, cache.get());
        assertEquals(failValue, cache.get());
        verify(supplier, times(1)).get();

        clock.tick(100);
        assertEquals(failValue, cache.get());
        verify(supplier, times(2)).get();

        // Failures cache should exponentially back off
        clock.tick(100);
        assertEquals(failValue, cache.get());
        verify(supplier, times(2)).get();

        clock.tick(50);
        assertEquals(failValue, cache.get());
        verify(supplier, times(3)).get();
    }

    private static class MutableClock implements Clock {

        private long currentTime;

        @Override
        public Date getCurrentDate() {
            return new Date(currentTime);
        }

        public void tick(long delta) {
            currentTime += delta;
        }
    }
}
