/*
 * 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.oss.driver.internal.core.metadata.schema.queries;

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.api.core.metadata.DseNodeProperties;
import com.datastax.oss.driver.api.core.Version;
import com.datastax.oss.driver.api.core.config.DriverConfig;
import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
import com.datastax.oss.driver.api.core.metadata.Node;
import com.datastax.oss.driver.internal.core.channel.DriverChannel;
import com.datastax.oss.driver.internal.core.context.InternalDriverContext;
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableList;
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(DataProviderRunner.class)
public class DefaultSchemaQueriesFactoryTest {

  enum Expected {
    CASS_21(Cassandra21SchemaQueries.class),
    CASS_22(Cassandra22SchemaQueries.class),
    CASS_3(Cassandra3SchemaQueries.class),
    CASS_4(Cassandra4SchemaQueries.class),
    DSE_6_8(Dse68SchemaQueries.class);

    final Class<? extends SchemaQueries> clz;

    Expected(Class<? extends SchemaQueries> clz) {
      this.clz = clz;
    }

    public Class<? extends SchemaQueries> getClz() {
      return clz;
    }
  }

  private static ImmutableList<ImmutableList<Object>> cassandraVersions =
      ImmutableList.<ImmutableList<Object>>builder()
          .add(ImmutableList.of("2.1.0", Optional.empty(), Expected.CASS_21))
          .add(ImmutableList.of("2.2.0", Optional.empty(), Expected.CASS_22))
          .add(ImmutableList.of("2.2.1", Optional.empty(), Expected.CASS_22))
          // Not a real version, just documenting behaviour of existing impl
          .add(ImmutableList.of("2.3.0", Optional.empty(), Expected.CASS_22))
          // We now return you to real versions
          .add(ImmutableList.of("3.0.0", Optional.empty(), Expected.CASS_3))
          .add(ImmutableList.of("3.0.1", Optional.empty(), Expected.CASS_3))
          .add(ImmutableList.of("3.1.0", Optional.empty(), Expected.CASS_3))
          .add(ImmutableList.of("4.0.0", Optional.empty(), Expected.CASS_4))
          .add(ImmutableList.of("4.0.1", Optional.empty(), Expected.CASS_4))
          .add(ImmutableList.of("4.1.0", Optional.empty(), Expected.CASS_4))
          .build();

  private static ImmutableList<ImmutableList<Object>> dseVersions =
      ImmutableList.<ImmutableList<Object>>builder()
          // DSE 6.0.0
          .add(ImmutableList.of("4.0.0.2284", Optional.of("6.0.0"), Expected.CASS_3))
          // DSE 6.0.1
          .add(ImmutableList.of("4.0.0.2349", Optional.of("6.0.1"), Expected.CASS_3))
          // DSE 6.0.2 moved to DSE version (minus dots) in an extra element
          .add(ImmutableList.of("4.0.0.602", Optional.of("6.0.2"), Expected.CASS_3))
          // DSE 6.7.0 continued with the same idea
          .add(ImmutableList.of("4.0.0.670", Optional.of("6.7.0"), Expected.CASS_4))
          // DSE 6.8.0 does the same
          .add(ImmutableList.of("4.0.0.680", Optional.of("6.8.0"), Expected.DSE_6_8))
          .build();

  private static ImmutableList<ImmutableList<Object>> allVersions =
      ImmutableList.<ImmutableList<Object>>builder()
          .addAll(cassandraVersions)
          .addAll(dseVersions)
          .build();

  @DataProvider(format = "%m %p[1] => %p[0]")
  public static Iterable<?> expected() {

    return allVersions;
  }

  @Test
  @UseDataProvider("expected")
  public void should_return_correct_schema_queries_impl(
      String cassandraVersion, Optional<String> dseVersion, Expected expected) {

    final Node mockNode = mock(Node.class);
    when(mockNode.getCassandraVersion()).thenReturn(Version.parse(cassandraVersion));
    dseVersion.ifPresent(
        versionStr -> {
          when(mockNode.getExtras())
              .thenReturn(
                  ImmutableMap.<String, Object>of(
                      DseNodeProperties.DSE_VERSION, Version.parse(versionStr)));
        });

    DefaultSchemaQueriesFactory factory = buildFactory();

    @SuppressWarnings("unchecked")
    SchemaQueries queries = factory.newInstance(mockNode, mock(DriverChannel.class));

    assertThat(queries.getClass()).isEqualTo(expected.getClz());
  }

  private DefaultSchemaQueriesFactory buildFactory() {

    final DriverExecutionProfile mockProfile = mock(DriverExecutionProfile.class);
    final DriverConfig mockConfig = mock(DriverConfig.class);
    when(mockConfig.getDefaultProfile()).thenReturn(mockProfile);
    final InternalDriverContext mockInternalCtx = mock(InternalDriverContext.class);
    when(mockInternalCtx.getConfig()).thenReturn(mockConfig);

    return new DefaultSchemaQueriesFactory(mockInternalCtx);
  }
}
