package org.zowe.apiml.util;/*
 * This program and the accompanying materials are made available under the terms of the
 * Eclipse Public License v2.0 which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-v20.html
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Copyright Contributors to the Zowe Project.
 */

import net.sf.ehcache.Element;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.runner.RunWith;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.zowe.apiml.cache.CompositeKey;

import java.io.Serializable;
import java.util.*;

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

@RunWith(PowerMockRunner.class)
@PrepareForTest(net.sf.ehcache.Cache.class)
public class CacheUtilsTest {

    private CacheUtils underTest;
    private int removeCounter;

    @Before
    public void setUp() {
        underTest = new CacheUtils();
    }

    @Test
    public void testEvictSubset() {
        CacheManager cacheManager = mock(CacheManager.class);

        // cache1 is not ehCache
        Cache cache1 = mock(Cache.class);
        when(cacheManager.getCache("cache1")).thenReturn(cache1);
        when(cache1.getNativeCache()).thenReturn(Object.class);

        Cache cache2 = mock(Cache.class);
        when(cacheManager.getCache("cache2")).thenReturn(cache2);
        net.sf.ehcache.Cache ehCache2 = PowerMockito.mock(net.sf.ehcache.Cache.class);

        when(cache2.getNativeCache()).thenReturn(ehCache2);
        List<Object> keys = Arrays.asList(
            "abc", // not composite key
            new CompositeKey("test", 5),
            new CompositeKey("next", 10),
            new CompositeKey("next", 15)
        );
        when(ehCache2.getKeys()).thenReturn(keys);

        try {
            underTest.evictSubset(cacheManager, "missing", x -> true);
            fail();
        } catch (IllegalArgumentException e) {
            assertTrue(e.getMessage().contains("Unknown cache"));
            assertTrue(e.getMessage().contains("missing"));
        }

        // not EhCache - clean all, dont use keyPredicate
        verify(cache1, never()).clear();
        underTest.evictSubset(cacheManager, "cache1", x -> false);
        verify(cache1, times(1)).clear();

        final Answer<Boolean> answer = invocation -> {
            removeCounter++;
            return true;
        };

        doAnswer(answer).when(ehCache2).remove(any(Serializable.class));
        doAnswer(answer).when(ehCache2).remove((Object) any());

        assertEquals(0, removeCounter);
        // in all cases remove entries without CompositeKey
        underTest.evictSubset(cacheManager, "cache2", x -> false);
        assertEquals(1, removeCounter);
        verify(ehCache2, times(1)).remove(keys.get(0));

        underTest.evictSubset(cacheManager, "cache2", x -> x.equals(0, "test"));
        assertEquals(3, removeCounter);
        verify(ehCache2, times(2)).remove(keys.get(0));
        verify(ehCache2, times(1)).remove(keys.get(1));

        underTest.evictSubset(cacheManager, "cache2", x -> (Integer) x.get(1) > 10);
        assertEquals(5, removeCounter);
        verify(ehCache2, times(3)).remove(keys.get(0));
        verify(ehCache2, times(1)).remove(keys.get(3));
    }

    @Test
    public void givenUnknownCacheName_whenGetAllRecords_thenThrowsException() {
        CacheManager cacheManager = mock(CacheManager.class);
        IllegalArgumentException iae = Assertions.assertThrows(
            IllegalArgumentException.class,
            () -> underTest.getAllRecords(cacheManager, "unknownCacheName")
        );
        assertEquals("Unknown cache unknownCacheName", iae.getMessage());
    }

    @Test
    public void givenUnsupportedCacheManager_whenGetAllRecords_thenThrowsException() {
        CacheManager cacheManager = mock(CacheManager.class);
        Cache cache = mock(Cache.class);
        when(cacheManager.getCache("knownCacheName")).thenReturn(cache);
        when(cache.getNativeCache()).thenReturn(new Object());
        IllegalArgumentException iae = Assertions.assertThrows(
            IllegalArgumentException.class,
            () -> underTest.getAllRecords(cacheManager, "knownCacheName")
        );
        assertTrue(iae.getMessage().startsWith("Unsupported type of cache : "));
    }

    private Map<Object, Element> convert(Map<Integer, String> in) {
        Map<Object, Element> out = new HashMap<>();
        for (Map.Entry<Integer, String> entry : in.entrySet()) {
            out.put(entry.getKey(), new Element(entry.getKey(), entry.getValue()));
        }
        return out;
    }

    @Test
    public void givenValidCacheManager_whenGetAllRecords_thenReadAllStoredRecords() {
        CacheManager cacheManager = mock(CacheManager.class);
        Cache cache = mock(Cache.class);
        net.sf.ehcache.Cache ehCache = PowerMockito.mock(net.sf.ehcache.Cache.class);

        Map<Integer, String> entries = new HashMap<>();
        entries.put(1, "a");
        entries.put(2, "b");
        entries.put(3, "c");
        List<Object> keys = new ArrayList<>(entries.keySet());

        when(cacheManager.getCache("knownCacheName")).thenReturn(cache);
        when(cache.getNativeCache()).thenReturn(ehCache);
        when(ehCache.getKeys()).thenReturn(keys);
        when(ehCache.getAll(keys)).thenReturn(convert(entries));

        Collection<String> values = underTest.getAllRecords(cacheManager, "knownCacheName");
        assertNotNull(values);
        assertEquals(3, values.size());
        assertTrue(values.contains("a"));
        assertTrue(values.contains("b"));
        assertTrue(values.contains("c"));
    }

}