package com.atlassian.bitbucket.scm.cache.internal;

import com.atlassian.bitbucket.scm.cache.ScmCacheConfig;
import com.atlassian.bitbucket.scm.cache.ScmRequestType;
import com.atlassian.util.contentcache.CacheEntry;
import com.atlassian.util.contentcache.CacheEntryStatistics;
import com.atlassian.util.contentcache.ContentCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Sets;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;

import java.util.Date;
import java.util.Set;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class ScmCacheExpiryStrategyTest {

    @Mock
    private ContentCache cache;
    @Mock
    private CacheEntry entry;
    @Mock
    private ScmCacheConfig config;
    @InjectMocks
    private ScmCacheExpiryStrategy strategy;


    @Before
    public void setup() {
        when(config.getMaxCount(ScmRequestType.REFS)).thenReturn(1);
        when(config.getMaxCount(ScmRequestType.UPLOAD_PACK)).thenReturn(2);
    }

    @Test
    public void testIsExpired() {
        // mock entry that has expired 10s ago and one that expires 10s from now
        when(entry.getExpiryDate()).thenReturn(secsFromNow(-10), secsFromNow(10));

        assertTrue(strategy.isExpired(entry));
        assertFalse(strategy.isExpired(entry));
    }

    @Test
    public void testGetExpiredEntriesBasedOnTtl() {
        when(entry.getExpiryDate()).thenReturn(secsFromNow(-10));
        when(cache.getEntries()).thenReturn(ImmutableMap.of("key", entry));

        Set<CacheEntry> expired = Sets.newHashSet(strategy.getExpiredEntries(cache));
        assertEquals(1, expired.size());
        assertTrue(expired.contains(entry));
    }

    @Test
    public void testGetExpiredEntriesBasedOnSize() {
        CacheEntry expired1 = mockEntry("clone|3", secsFromNow(10), secsFromNow(-300));
        CacheEntry expired2 = mockEntry("clone|4", secsFromNow(10), secsFromNow(-200));
        CacheEntry expiredTtl = mockEntry("clone|5", secsFromNow(-10), secsFromNow(-400));
        ImmutableMap<String, CacheEntry> entries = ImmutableMap.<String, CacheEntry>builder()
                .put("refs|", mockEntry("refs|", secsFromNow(10), secsFromNow(-100)))
                .put("clone|1", mockEntry("clone|1", secsFromNow(10), secsFromNow(-100)))
                .put("clone|2", mockEntry("clone|2", secsFromNow(10), secsFromNow(-100)))
                .put("clone|3", expired1)
                .put("clone|4", expired2)
                .put("clone|5", expiredTtl)
                .put("negotiation|", mockEntry("negotiation|asdja", secsFromNow(10), secsFromNow(-1000))) //should be ignored
                .build();

        when(cache.getEntries()).thenReturn(entries);

        Set<CacheEntry> expired = Sets.newHashSet(strategy.getExpiredEntries(cache));

        assertTrue(expired.contains(expired1));
        assertTrue(expired.contains(expired2));
        assertTrue(expired.contains(expiredTtl));
        assertEquals(3, expired.size());
    }

    private CacheEntry mockEntry(String key, Date expiryDate, Date lastAccessedDate) {
        CacheEntry entry = mock(CacheEntry.class);
        CacheEntryStatistics statistics = mock(CacheEntryStatistics.class);
        when(statistics.getLastAccessedDate()).thenReturn(lastAccessedDate);
        when(statistics.getKey()).thenReturn(key);
        when(entry.getStatistics()).thenReturn(statistics);
        when(entry.getExpiryDate()).thenReturn(expiryDate);
        return entry;
    }

    private Date secsFromNow(int offsetSeconds) {
        return new Date(System.currentTimeMillis() + 1000 * offsetSeconds);
    }
}
