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

import com.google.common.base.Joiner;

import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.dto.ApolloConfig;

import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.web.client.HttpStatusCodeException;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.junit.Assert.assertEquals;

/**
 * @author Jason Song(song_s@ctrip.com)
 */
public class ConfigControllerIntegrationTest extends AbstractBaseIntegrationTest {
  private String someAppId;
  private String somePublicAppId;
  private String someCluster;
  private String someNamespace;
  private String somePublicNamespace;
  private String someDC;
  private String someDefaultCluster;
  private String someClientIp;
  private ExecutorService executorService;

  @Before
  public void setUp() throws Exception {
    someAppId = "someAppId";
    someCluster = "someCluster";
    someNamespace = "someNamespace";
    somePublicAppId = "somePublicAppId";
    somePublicNamespace = "somePublicNamespace";
    someDC = "someDC";
    someDefaultCluster = ConfigConsts.CLUSTER_NAME_DEFAULT;
    someClientIp = "1.1.1.1";
    executorService = Executors.newSingleThreadExecutor();
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryConfigWithDefaultClusterAndDefaultNamespaceOK() throws Exception {
    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}", ApolloConfig.class,
            getHostUrl(), someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, ConfigConsts.NAMESPACE_APPLICATION);
    ApolloConfig result = response.getBody();

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertEquals("TEST-RELEASE-KEY1", result.getReleaseKey());
    assertEquals("v1", result.getConfigurations().get("k1"));
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/test-gray-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryGrayConfigWithDefaultClusterAndDefaultNamespaceOK() throws Exception {
    AtomicBoolean stop = new AtomicBoolean();
    periodicSendMessage(executorService, assembleKey(someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, ConfigConsts.NAMESPACE_APPLICATION),
        stop);

    TimeUnit.MILLISECONDS.sleep(500);

    stop.set(true);

    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?ip={clientIp}", ApolloConfig.class,
            getHostUrl(), someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, ConfigConsts.NAMESPACE_APPLICATION, someClientIp);
    ApolloConfig result = response.getBody();

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertEquals("TEST-GRAY-RELEASE-KEY1", result.getReleaseKey());
    assertEquals("v1-gray", result.getConfigurations().get("k1"));
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryConfigFileWithDefaultClusterAndDefaultNamespaceOK() throws Exception {
    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}", ApolloConfig.class,
            getHostUrl(), someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, ConfigConsts.NAMESPACE_APPLICATION + ".properties");
    ApolloConfig result = response.getBody();

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertEquals("TEST-RELEASE-KEY1", result.getReleaseKey());
    assertEquals("v1", result.getConfigurations().get("k1"));
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryConfigWithNamespaceOK() throws Exception {
    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}", ApolloConfig.class,
            getHostUrl(), someAppId, someCluster, someNamespace);
    ApolloConfig result = response.getBody();

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertEquals("TEST-RELEASE-KEY2", result.getReleaseKey());
    assertEquals("v2", result.getConfigurations().get("k2"));
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryConfigFileWithNamespaceOK() throws Exception {
    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}", ApolloConfig.class,
            getHostUrl(), someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, someNamespace + ".xml");
    ApolloConfig result = response.getBody();

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertEquals("TEST-RELEASE-KEY5", result.getReleaseKey());
    assertEquals("v1-file", result.getConfigurations().get("k1"));
    assertEquals("v2-file", result.getConfigurations().get("k2"));
  }

  @Test
  public void testQueryConfigError() throws Exception {
    String someNamespaceNotExists = "someNamespaceNotExists";

    HttpStatusCodeException httpException = null;
    try {
      ResponseEntity<ApolloConfig> response = restTemplate
          .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}", ApolloConfig.class,
              getHostUrl(), someAppId, someCluster, someNamespaceNotExists);
    } catch (HttpStatusCodeException ex) {
      httpException = ex;
    }

    assertEquals(HttpStatus.NOT_FOUND, httpException.getStatusCode());
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryConfigNotModified() throws Exception {
    String releaseKey = "TEST-RELEASE-KEY2";
    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?releaseKey={releaseKey}",
            ApolloConfig.class,
            getHostUrl(), someAppId, someCluster, someNamespace, releaseKey);

    assertEquals(HttpStatus.NOT_MODIFIED, response.getStatusCode());
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/test-gray-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryPublicGrayConfigWithNoOverride() throws Exception {
    AtomicBoolean stop = new AtomicBoolean();
    periodicSendMessage(executorService, assembleKey(somePublicAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, somePublicNamespace),
        stop);

    TimeUnit.MILLISECONDS.sleep(500);

    stop.set(true);

    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?ip={clientIp}", ApolloConfig.class,
            getHostUrl(), someAppId, someCluster, somePublicNamespace, someClientIp);
    ApolloConfig result = response.getBody();

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertEquals("TEST-GRAY-RELEASE-KEY2", result.getReleaseKey());
    assertEquals("gray-v1", result.getConfigurations().get("k1"));
    assertEquals("gray-v2", result.getConfigurations().get("k2"));
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryPublicConfigWithDataCenterFoundAndNoOverride() throws Exception {
    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?dataCenter={dateCenter}",
            ApolloConfig.class,
            getHostUrl(), someAppId, someCluster, somePublicNamespace, someDC);
    ApolloConfig result = response.getBody();

    assertEquals("TEST-RELEASE-KEY4", result.getReleaseKey());
    assertEquals(someAppId, result.getAppId());
    assertEquals(someCluster, result.getCluster());
    assertEquals(somePublicNamespace, result.getNamespaceName());
    assertEquals("someDC-v1", result.getConfigurations().get("k1"));
    assertEquals("someDC-v2", result.getConfigurations().get("k2"));
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/test-release-public-dc-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryPublicConfigWithDataCenterFoundAndOverride() throws Exception {
    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?dataCenter={dateCenter}",
            ApolloConfig.class,
            getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace, someDC);
    ApolloConfig result = response.getBody();

    assertEquals(
        "TEST-RELEASE-KEY6" + ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR + "TEST-RELEASE-KEY4",
        result.getReleaseKey());
    assertEquals(someAppId, result.getAppId());
    assertEquals(someDC, result.getCluster());
    assertEquals(somePublicNamespace, result.getNamespaceName());
    assertEquals("override-someDC-v1", result.getConfigurations().get("k1"));
    assertEquals("someDC-v2", result.getConfigurations().get("k2"));
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryPublicConfigWithDataCenterNotFoundAndNoOverride() throws Exception {
    String someDCNotFound = "someDCNotFound";
    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?dataCenter={dateCenter}",
            ApolloConfig.class,
            getHostUrl(), someAppId, someCluster, somePublicNamespace, someDCNotFound);
    ApolloConfig result = response.getBody();

    assertEquals("TEST-RELEASE-KEY3", result.getReleaseKey());
    assertEquals(someAppId, result.getAppId());
    assertEquals(someCluster, result.getCluster());
    assertEquals(somePublicNamespace, result.getNamespaceName());
    assertEquals("default-v1", result.getConfigurations().get("k1"));
    assertEquals("default-v2", result.getConfigurations().get("k2"));
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/test-release-public-default-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryPublicConfigWithDataCenterNotFoundAndOverride() throws Exception {
    String someDCNotFound = "someDCNotFound";
    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?dataCenter={dateCenter}",
            ApolloConfig.class,
            getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace, someDCNotFound);
    ApolloConfig result = response.getBody();

    assertEquals(
        "TEST-RELEASE-KEY5" + ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR + "TEST-RELEASE-KEY3",
        result.getReleaseKey());
    assertEquals(someAppId, result.getAppId());
    assertEquals(someDefaultCluster, result.getCluster());
    assertEquals(somePublicNamespace, result.getNamespaceName());
    assertEquals("override-v1", result.getConfigurations().get("k1"));
    assertEquals("default-v2", result.getConfigurations().get("k2"));
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/test-release-public-default-override.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/test-gray-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryPublicGrayConfigWithOverride() throws Exception {
    AtomicBoolean stop = new AtomicBoolean();
    periodicSendMessage(executorService, assembleKey(somePublicAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, somePublicNamespace),
        stop);

    TimeUnit.MILLISECONDS.sleep(500);

    stop.set(true);

    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?ip={clientIp}", ApolloConfig.class,
            getHostUrl(), someAppId, someDefaultCluster, somePublicNamespace, someClientIp);
    ApolloConfig result = response.getBody();

    assertEquals(HttpStatus.OK, response.getStatusCode());
    assertEquals(
        "TEST-RELEASE-KEY5" + ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR + "TEST-GRAY-RELEASE-KEY2",
        result.getReleaseKey());
    assertEquals("override-v1", result.getConfigurations().get("k1"));
    assertEquals("gray-v2", result.getConfigurations().get("k2"));
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryPrivateConfigFileWithPublicNamespaceExists() throws Exception {
    String namespaceName = "anotherNamespace";
    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}",
            ApolloConfig.class,
            getHostUrl(), someAppId, ConfigConsts.CLUSTER_NAME_DEFAULT, namespaceName);
    ApolloConfig result = response.getBody();

    assertEquals("TEST-RELEASE-KEY6", result.getReleaseKey());
    assertEquals(someAppId, result.getAppId());
    assertEquals(ConfigConsts.CLUSTER_NAME_DEFAULT, result.getCluster());
    assertEquals(namespaceName, result.getNamespaceName());
    assertEquals("v1-file", result.getConfigurations().get("k1"));
    assertEquals(null, result.getConfigurations().get("k2"));
  }

  @Test
  public void testQueryConfigForNoAppIdPlaceHolderWithPrivateNamespace() throws Exception {

    HttpStatusCodeException httpException = null;
    try {
      ResponseEntity<ApolloConfig> response = restTemplate
          .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}", ApolloConfig.class,
              getHostUrl(), ConfigConsts.NO_APPID_PLACEHOLDER, someCluster, ConfigConsts.NAMESPACE_APPLICATION);
    } catch (HttpStatusCodeException ex) {
      httpException = ex;
    }

    assertEquals(HttpStatus.NOT_FOUND, httpException.getStatusCode());
  }

  @Test
  @Sql(scripts = "/integration-test/test-release.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
  @Sql(scripts = "/integration-test/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
  public void testQueryPublicConfigForNoAppIdPlaceHolder() throws Exception {
    ResponseEntity<ApolloConfig> response = restTemplate
        .getForEntity("{baseurl}/configs/{appId}/{clusterName}/{namespace}?dataCenter={dateCenter}",
            ApolloConfig.class,
            getHostUrl(), ConfigConsts.NO_APPID_PLACEHOLDER, someCluster, somePublicNamespace, someDC);
    ApolloConfig result = response.getBody();

    assertEquals("TEST-RELEASE-KEY4", result.getReleaseKey());
    assertEquals(ConfigConsts.NO_APPID_PLACEHOLDER, result.getAppId());
    assertEquals(someCluster, result.getCluster());
    assertEquals(somePublicNamespace, result.getNamespaceName());
    assertEquals("someDC-v1", result.getConfigurations().get("k1"));
    assertEquals("someDC-v2", result.getConfigurations().get("k2"));
  }

  private String assembleKey(String appId, String cluster, String namespace) {
    return Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR).join(appId, cluster, namespace);
  }
}
