/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.datastax.dse.driver.internal.core.insights;

import static com.datastax.dse.driver.internal.core.insights.ExecutionProfileMockUtil.DEFAULT_LOCAL_DC;
import static com.datastax.dse.driver.internal.core.insights.ExecutionProfileMockUtil.SPECEX_DELAY_DEFAULT;
import static com.datastax.dse.driver.internal.core.insights.ExecutionProfileMockUtil.SPECEX_MAX_DEFAULT;
import static com.datastax.dse.driver.internal.core.insights.ExecutionProfileMockUtil.mockDefaultExecutionProfile;
import static com.datastax.dse.driver.internal.core.insights.ExecutionProfileMockUtil.mockNonDefaultConsistency;
import static com.datastax.dse.driver.internal.core.insights.ExecutionProfileMockUtil.mockNonDefaultGraphOptions;
import static com.datastax.dse.driver.internal.core.insights.ExecutionProfileMockUtil.mockNonDefaultLoadBalancingExecutionProfile;
import static com.datastax.dse.driver.internal.core.insights.ExecutionProfileMockUtil.mockNonDefaultRequestTimeoutExecutionProfile;
import static com.datastax.dse.driver.internal.core.insights.ExecutionProfileMockUtil.mockNonDefaultSerialConsistency;
import static com.datastax.dse.driver.internal.core.insights.ExecutionProfileMockUtil.mockNonDefaultSpeculativeExecutionInfo;
import static com.datastax.dse.driver.internal.core.insights.ExecutionProfileMockUtil.mockUndefinedLocalDcExecutionProfile;
import static com.datastax.dse.driver.internal.core.insights.PackageUtil.DEFAULT_LOAD_BALANCING_PACKAGE;
import static com.datastax.dse.driver.internal.core.insights.PackageUtil.DEFAULT_SPECULATIVE_EXECUTION_PACKAGE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import com.datastax.dse.driver.internal.core.insights.schema.LoadBalancingInfo;
import com.datastax.dse.driver.internal.core.insights.schema.SpecificExecutionProfile;
import com.datastax.dse.driver.internal.core.insights.schema.SpeculativeExecutionInfo;
import com.datastax.oss.driver.api.core.config.DriverConfig;
import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
import com.datastax.oss.driver.internal.core.context.InternalDriverContext;
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;

@RunWith(DataProviderRunner.class)
public class ExecutionProfilesInfoFinderTest {

  @Test
  public void should_include_info_about_default_profile() {
    // given
    DriverExecutionProfile defaultExecutionProfile = mockDefaultExecutionProfile();
    Map<String, DriverExecutionProfile> profiles =
        ImmutableMap.of("default", defaultExecutionProfile);

    InternalDriverContext context =
        mockDriverContextWithProfiles(defaultExecutionProfile, profiles);

    // when
    Map<String, SpecificExecutionProfile> executionProfilesInfo =
        new ExecutionProfilesInfoFinder().getExecutionProfilesInfo(context);

    // then
    assertThat(executionProfilesInfo)
        .isEqualTo(
            ImmutableMap.of(
                "default",
                new SpecificExecutionProfile(
                    100,
                    new LoadBalancingInfo(
                        "LoadBalancingPolicyImpl",
                        ImmutableMap.of("localDataCenter", "local-dc", "filterFunction", true),
                        DEFAULT_LOAD_BALANCING_PACKAGE),
                    new SpeculativeExecutionInfo(
                        "SpeculativeExecutionImpl",
                        ImmutableMap.of("maxSpeculativeExecutions", 100, "delay", 20),
                        DEFAULT_SPECULATIVE_EXECUTION_PACKAGE),
                    "LOCAL_ONE",
                    "SERIAL",
                    ImmutableMap.of("source", "src-graph"))));
  }

  @Test
  @UseDataProvider("executionProfileProvider")
  public void should_include_info_about_default_profile_and_only_difference_for_specific_profile(
      DriverExecutionProfile nonDefaultExecutionProfile, SpecificExecutionProfile expected) {
    // given

    DriverExecutionProfile defaultExecutionProfile = mockDefaultExecutionProfile();
    Map<String, DriverExecutionProfile> profiles =
        ImmutableMap.of(
            "default", defaultExecutionProfile, "non-default", nonDefaultExecutionProfile);
    InternalDriverContext context =
        mockDriverContextWithProfiles(defaultExecutionProfile, profiles);
    // when
    Map<String, SpecificExecutionProfile> executionProfilesInfo =
        new ExecutionProfilesInfoFinder().getExecutionProfilesInfo(context);

    // then
    assertThat(executionProfilesInfo)
        .isEqualTo(
            ImmutableMap.of(
                "default",
                new SpecificExecutionProfile(
                    100,
                    new LoadBalancingInfo(
                        "LoadBalancingPolicyImpl",
                        ImmutableMap.of("localDataCenter", "local-dc", "filterFunction", true),
                        DEFAULT_LOAD_BALANCING_PACKAGE),
                    new SpeculativeExecutionInfo(
                        "SpeculativeExecutionImpl",
                        ImmutableMap.of("maxSpeculativeExecutions", 100, "delay", 20),
                        DEFAULT_SPECULATIVE_EXECUTION_PACKAGE),
                    "LOCAL_ONE",
                    "SERIAL",
                    ImmutableMap.of("source", "src-graph")),
                "non-default",
                expected));
  }

  @DataProvider
  public static Object[][] executionProfileProvider() {
    return new Object[][] {
      {
        mockNonDefaultRequestTimeoutExecutionProfile(),
        new SpecificExecutionProfile(50, null, null, null, null, null)
      },
      {
        mockNonDefaultLoadBalancingExecutionProfile(),
        new SpecificExecutionProfile(
            null,
            new LoadBalancingInfo(
                "NonDefaultLoadBalancing",
                ImmutableMap.of("localDataCenter", DEFAULT_LOCAL_DC, "filterFunction", true),
                DEFAULT_LOAD_BALANCING_PACKAGE),
            null,
            null,
            null,
            null)
      },
      {
        mockUndefinedLocalDcExecutionProfile(),
        new SpecificExecutionProfile(
            null,
            new LoadBalancingInfo(
                "NonDefaultLoadBalancing",
                ImmutableMap.of("filterFunction", true),
                DEFAULT_LOAD_BALANCING_PACKAGE),
            null,
            null,
            null,
            null)
      },
      {
        mockNonDefaultSpeculativeExecutionInfo(),
        new SpecificExecutionProfile(
            null,
            null,
            new SpeculativeExecutionInfo(
                "NonDefaultSpecexPolicy",
                ImmutableMap.of(
                    "maxSpeculativeExecutions", SPECEX_MAX_DEFAULT, "delay", SPECEX_DELAY_DEFAULT),
                DEFAULT_SPECULATIVE_EXECUTION_PACKAGE),
            null,
            null,
            null)
      },
      {
        mockNonDefaultConsistency(),
        new SpecificExecutionProfile(null, null, null, "ALL", null, null)
      },
      {
        mockNonDefaultSerialConsistency(),
        new SpecificExecutionProfile(null, null, null, null, "ONE", null)
      },
      {
        mockNonDefaultGraphOptions(),
        new SpecificExecutionProfile(
            null, null, null, null, null, ImmutableMap.of("source", "non-default-graph"))
      },
      {
        mockDefaultExecutionProfile(),
        new SpecificExecutionProfile(null, null, null, null, null, null)
      }
    };
  }

  @Test
  public void should_not_include_null_fields_in_json() throws JsonProcessingException {
    // given
    SpecificExecutionProfile specificExecutionProfile =
        new SpecificExecutionProfile(50, null, null, "ONE", null, ImmutableMap.of("a", "b"));

    // when
    String result = new ObjectMapper().writeValueAsString(specificExecutionProfile);

    // then
    assertThat(result)
        .isEqualTo("{\"readTimeout\":50,\"consistency\":\"ONE\",\"graphOptions\":{\"a\":\"b\"}}");
  }

  @Test
  public void should_include_empty_execution_profile_if_has_all_nulls()
      throws JsonProcessingException {
    // given
    Map<String, SpecificExecutionProfile> executionProfiles =
        ImmutableMap.of("p", new SpecificExecutionProfile(null, null, null, null, null, null));

    // when
    String result = new ObjectMapper().writeValueAsString(executionProfiles);

    // then
    assertThat(result).isEqualTo("{\"p\":{}}");
  }

  private InternalDriverContext mockDriverContextWithProfiles(
      DriverExecutionProfile defaultExecutionProfile,
      Map<String, DriverExecutionProfile> profiles) {
    InternalDriverContext context = mock(InternalDriverContext.class);
    DriverConfig driverConfig = mock(DriverConfig.class);
    Mockito.<Map<String, ? extends DriverExecutionProfile>>when(driverConfig.getProfiles())
        .thenReturn(profiles);
    when(driverConfig.getDefaultProfile()).thenReturn(defaultExecutionProfile);
    when(context.getConfig()).thenReturn(driverConfig);
    return context;
  }
}
