package com.ctrip.framework.apollo.configservice.controller;

import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;

import com.ctrip.framework.apollo.biz.entity.ReleaseMessage;
import com.ctrip.framework.apollo.biz.grayReleaseRule.GrayReleaseRulesHolder;
import com.ctrip.framework.apollo.biz.message.Topics;
import com.ctrip.framework.apollo.configservice.util.NamespaceUtil;
import com.ctrip.framework.apollo.configservice.util.WatchKeysUtil;
import com.ctrip.framework.apollo.core.dto.ApolloConfig;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;

import java.lang.reflect.Type;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * @author Jason Song(song_s@ctrip.com)
 */
@RunWith(MockitoJUnitRunner.class)
public class ConfigFileControllerTest {
  @Mock
  private ConfigController configController;
  @Mock
  private WatchKeysUtil watchKeysUtil;
  @Mock
  private NamespaceUtil namespaceUtil;
  @Mock
  private GrayReleaseRulesHolder grayReleaseRulesHolder;
  private ConfigFileController configFileController;
  private String someAppId;
  private String someClusterName;
  private String someNamespace;
  private String someDataCenter;
  private String someClientIp;
  @Mock
  private HttpServletResponse someResponse;
  @Mock
  private HttpServletRequest someRequest;
  Multimap<String, String> watchedKeys2CacheKey;
  Multimap<String, String> cacheKey2WatchedKeys;

  @Before
  public void setUp() throws Exception {
    configFileController = new ConfigFileController();
    ReflectionTestUtils.setField(configFileController, "configController", configController);
    ReflectionTestUtils.setField(configFileController, "watchKeysUtil", watchKeysUtil);
    ReflectionTestUtils.setField(configFileController, "namespaceUtil", namespaceUtil);
    ReflectionTestUtils.setField(configFileController, "grayReleaseRulesHolder", grayReleaseRulesHolder);

    someAppId = "someAppId";
    someClusterName = "someClusterName";
    someNamespace = "someNamespace";
    someDataCenter = "someDataCenter";
    someClientIp = "10.1.1.1";

    when(namespaceUtil.filterNamespaceName(someNamespace)).thenReturn(someNamespace);
    when(grayReleaseRulesHolder.hasGrayReleaseRule(anyString(), anyString(), anyString()))
        .thenReturn(false);

    watchedKeys2CacheKey =
        (Multimap<String, String>) ReflectionTestUtils
            .getField(configFileController, "watchedKeys2CacheKey");
    cacheKey2WatchedKeys =
        (Multimap<String, String>) ReflectionTestUtils
            .getField(configFileController, "cacheKey2WatchedKeys");
  }

  @Test
  public void testQueryConfigAsProperties() throws Exception {
    String someKey = "someKey";
    String someValue = "someValue";
    String anotherKey = "anotherKey";
    String anotherValue = "anotherValue";

    String someWatchKey = "someWatchKey";
    String anotherWatchKey = "anotherWatchKey";
    Set<String> watchKeys = Sets.newHashSet(someWatchKey, anotherWatchKey);

    String cacheKey =
        configFileController
            .assembleCacheKey(ConfigFileController.ConfigFileOutputFormat.PROPERTIES, someAppId, someClusterName, someNamespace, someDataCenter);

    Map<String, String> configurations =
        ImmutableMap.of(someKey, someValue, anotherKey, anotherValue);
    ApolloConfig someApolloConfig = mock(ApolloConfig.class);
    when(someApolloConfig.getConfigurations()).thenReturn(configurations);
    when(configController
        .queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
            someRequest, someResponse)).thenReturn(someApolloConfig);
    when(watchKeysUtil
        .assembleAllWatchKeys(someAppId, someClusterName, someNamespace, someDataCenter))
        .thenReturn(watchKeys);

    ResponseEntity<String> response =
        configFileController
            .queryConfigAsProperties(someAppId, someClusterName, someNamespace, someDataCenter,
                someClientIp, someRequest, someResponse);

    assertEquals(2, watchedKeys2CacheKey.size());
    assertEquals(2, cacheKey2WatchedKeys.size());
    assertTrue(watchedKeys2CacheKey.containsEntry(someWatchKey, cacheKey));
    assertTrue(watchedKeys2CacheKey.containsEntry(anotherWatchKey, cacheKey));
    assertTrue(cacheKey2WatchedKeys.containsEntry(cacheKey, someWatchKey));
    assertTrue(cacheKey2WatchedKeys.containsEntry(cacheKey, anotherWatchKey));

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertTrue(response.getBody().contains(String.format("%s=%s", someKey, someValue)));
    assertTrue(response.getBody().contains(String.format("%s=%s", anotherKey, anotherValue)));

    ResponseEntity<String> anotherResponse =
        configFileController
            .queryConfigAsProperties(someAppId, someClusterName, someNamespace, someDataCenter,
                someClientIp, someRequest, someResponse);

    assertEquals(response, anotherResponse);

    verify(configController, times(1))
        .queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
            someRequest, someResponse);
  }

  @Test
  public void testQueryConfigAsJson() throws Exception {
    String someKey = "someKey";
    String someValue = "someValue";
    Gson gson = new Gson();
    Type responseType = new TypeToken<Map<String, String>>(){}.getType();

    String someWatchKey = "someWatchKey";
    Set<String> watchKeys = Sets.newHashSet(someWatchKey);

    Map<String, String> configurations =
        ImmutableMap.of(someKey, someValue);
    ApolloConfig someApolloConfig = mock(ApolloConfig.class);
    when(configController
        .queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
            someRequest, someResponse)).thenReturn(someApolloConfig);
    when(someApolloConfig.getConfigurations()).thenReturn(configurations);
    when(watchKeysUtil
        .assembleAllWatchKeys(someAppId, someClusterName, someNamespace, someDataCenter))
        .thenReturn(watchKeys);

    ResponseEntity<String> response =
        configFileController
            .queryConfigAsJson(someAppId, someClusterName, someNamespace, someDataCenter,
                someClientIp, someRequest, someResponse);

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertEquals(configurations, gson.fromJson(response.getBody(), responseType));
  }

  @Test
  public void testQueryConfigWithGrayRelease() throws Exception {
    String someKey = "someKey";
    String someValue = "someValue";
    Gson gson = new Gson();
    Type responseType = new TypeToken<Map<String, String>>(){}.getType();

    Map<String, String> configurations =
        ImmutableMap.of(someKey, someValue);

    when(grayReleaseRulesHolder.hasGrayReleaseRule(someAppId, someClientIp, someNamespace))
        .thenReturn(true);

    ApolloConfig someApolloConfig = mock(ApolloConfig.class);
    when(someApolloConfig.getConfigurations()).thenReturn(configurations);
    when(configController
        .queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
            someRequest, someResponse)).thenReturn(someApolloConfig);

    ResponseEntity<String> response =
        configFileController
            .queryConfigAsJson(someAppId, someClusterName, someNamespace, someDataCenter,
                someClientIp, someRequest, someResponse);

    ResponseEntity<String> anotherResponse =
        configFileController
            .queryConfigAsJson(someAppId, someClusterName, someNamespace, someDataCenter,
                someClientIp, someRequest, someResponse);

    verify(configController, times(2))
        .queryConfig(someAppId, someClusterName, someNamespace, someDataCenter, "-1", someClientIp,
            someRequest, someResponse);

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertEquals(configurations, gson.fromJson(response.getBody(), responseType));
    assertTrue(watchedKeys2CacheKey.isEmpty());
    assertTrue(cacheKey2WatchedKeys.isEmpty());
  }

  @Test
  public void testHandleMessage() throws Exception {
    String someWatchKey = "someWatchKey";
    String anotherWatchKey = "anotherWatchKey";
    String someCacheKey = "someCacheKey";
    String anotherCacheKey = "anotherCacheKey";
    String someValue = "someValue";

    ReleaseMessage someReleaseMessage = mock(ReleaseMessage.class);
    when(someReleaseMessage.getMessage()).thenReturn(someWatchKey);

    Cache<String, String> cache =
        (Cache<String, String>) ReflectionTestUtils.getField(configFileController, "localCache");
    cache.put(someCacheKey, someValue);
    cache.put(anotherCacheKey, someValue);

    watchedKeys2CacheKey.putAll(someWatchKey, Lists.newArrayList(someCacheKey, anotherCacheKey));
    watchedKeys2CacheKey.putAll(anotherWatchKey, Lists.newArrayList(someCacheKey, anotherCacheKey));

    cacheKey2WatchedKeys.putAll(someCacheKey, Lists.newArrayList(someWatchKey, anotherWatchKey));
    cacheKey2WatchedKeys.putAll(anotherCacheKey, Lists.newArrayList(someWatchKey, anotherWatchKey));

    configFileController.handleMessage(someReleaseMessage, Topics.APOLLO_RELEASE_TOPIC);

    assertTrue(watchedKeys2CacheKey.isEmpty());
    assertTrue(cacheKey2WatchedKeys.isEmpty());
  }
}
