package nl.topicus.jdbc;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.mockito.internal.stubbing.answers.Returns;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.Operation;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
import nl.topicus.jdbc.CloudSpannerConnection.CloudSpannerDatabaseSpecification;
import nl.topicus.jdbc.test.category.UnitTest;

@Category(UnitTest.class)
public class CustomStatementsTest {
  private static final List<String> CONNECTION_PROPERTIES =
      Arrays.asList("AllowExtendedMode", "AsyncDdlOperations", "AutoBatchDdlOperations",
          "ReportDefaultSchemaAsNull", "BatchReadOnlyMode");

  private Connection connection;

  @Before
  public void setup() throws SQLException {
    connection = new CloudSpannerConnection(new CloudSpannerDatabaseSpecification("test", "test"));
  }

  @Test
  public void testGetConnectionProperty() throws SQLException {
    int count = 0;
    Statement statement = connection.createStatement();
    try (ResultSet rs = statement.executeQuery("GET_CONNECTION_PROPERTY")) {
      while (rs.next()) {
        assertTrue(CONNECTION_PROPERTIES.contains(rs.getString("NAME")));
        count++;
      }
    }
    assertEquals(CONNECTION_PROPERTIES.size(), count);

    for (String prop : CONNECTION_PROPERTIES) {
      try (ResultSet rs = statement.executeQuery("GET_CONNECTION_PROPERTY " + prop)) {
        assertTrue(rs.next());
        assertFalse(rs.next());
      }
    }
  }

  @Test
  public void testSetConnectionProperty() throws SQLException {
    connection.setAutoCommit(false);
    Statement statement = connection.createStatement();
    for (String prop : CONNECTION_PROPERTIES) {
      for (Boolean value : new Boolean[] {Boolean.TRUE, Boolean.FALSE}) {
        int count = statement.executeUpdate("SET_CONNECTION_PROPERTY " + prop + "=" + value);
        assertEquals(1, count);
        try (ResultSet rs = statement.executeQuery("GET_CONNECTION_PROPERTY " + prop)) {
          assertTrue(rs.next());
          assertEquals(value.toString(), rs.getString("VALUE"));
          assertFalse(rs.next());
        }
      }
    }
  }

  @Test
  public void testShowDDLOperations() throws SQLException, NoSuchFieldException, SecurityException,
      IllegalArgumentException, IllegalAccessException {
    final String ddl =
        "CREATE TABLE FOO (ID INT64 NOT NULL, NAME STRING(100) NOT NULL) PRIMARY KEY (ID)";

    Field adminClientField = CloudSpannerConnection.class.getDeclaredField("adminClient");
    adminClientField.setAccessible(true);
    DatabaseAdminClient adminClient = mock(DatabaseAdminClient.class);
    @SuppressWarnings("unchecked")
    Operation<Void, UpdateDatabaseDdlMetadata> operation = mock(Operation.class);
    when(operation.reload()).then(new Returns(operation));
    when(operation.getName()).then(new Returns("test"));
    when(adminClient.updateDatabaseDdl(any(), any(), any(), any())).then(new Returns(operation));
    adminClientField.set(connection, adminClient);
    Statement statement = connection.createStatement();
    assertFalse(statement.execute("SET_CONNECTION_PROPERTY AsyncDdlOperations=true"));
    assertEquals(1, statement.getUpdateCount());
    try (ResultSet rs = statement.executeQuery("SHOW_DDL_OPERATIONS")) {
      assertFalse(rs.next());
    }
    statement.execute(ddl);
    try (ResultSet rs = statement.executeQuery("SHOW_DDL_OPERATIONS")) {
      assertTrue(rs.next());
      assertEquals("test", rs.getString("NAME"));
      assertNotNull(rs.getTimestamp("TIME_STARTED"));
      assertEquals(ddl, rs.getString("STATEMENT"));
      assertFalse(rs.getBoolean("DONE"));
      assertNull(rs.getString("EXCEPTION"));
      assertFalse(rs.next());
    }
  }

}