/*
 * Copyright (c) 2005, PostgreSQL Global Development Group
 * See the LICENSE file in the project root for more information.
 */

package org.postgresql.test.jdbc3;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import org.postgresql.core.ServerVersion;
import org.postgresql.jdbc.PreferQueryMode;
import org.postgresql.test.TestUtil;
import org.postgresql.test.jdbc2.BaseTest4;
import org.postgresql.util.PSQLState;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedClass;
import org.junit.jupiter.params.provider.MethodSource;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Properties;

@ParameterizedClass
@MethodSource("data")
public class StringTypeParameterTest extends BaseTest4 {
  private static final String UNSPECIFIED_STRING_TYPE = "unspecified";

  private final String stringType;

  public StringTypeParameterTest(String stringType) {
    this.stringType = stringType;
  }

  @BeforeAll
  static void createTables() throws Exception {
    try (Connection con = TestUtil.openDB()) {
      TestUtil.createEnumType(con, "mood", "'happy', 'sad'");
      TestUtil.createTable(con, "stringtypetest", "m mood");
    }
  }

  @AfterAll
  static void dropTables() throws Exception {
    try (Connection con = TestUtil.openDB()) {
      TestUtil.dropTable(con, "stringtypetest");
      TestUtil.dropType(con, "mood");
    }
  }

  @Override
  public void setUp() throws Exception {
    super.setUp();
    // Assume enum supported
    assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v8_3));
    TestUtil.execute(con, "TRUNCATE stringtypetest");
  }

  @Override
  protected void updateProperties(Properties props) {
    super.updateProperties(props);
    if (stringType != null) {
      props.put("stringtype", stringType);
    }
  }

  public static Iterable<Object[]> data() {
    Collection<Object[]> ids = new ArrayList<>();
    for (String stringType : new String[]{null, "varchar", UNSPECIFIED_STRING_TYPE}) {
      ids.add(new Object[]{stringType});
    }
    return ids;
  }

  @Test
  public void testVarcharAsEnum() throws Exception {
    assumeFalse(UNSPECIFIED_STRING_TYPE.equals(stringType));
    assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE);

    PreparedStatement update = con.prepareStatement("insert into stringtypetest (m) values (?)");
    for (int i = 0; i < 2; i++) {
      update.clearParameters();
      if (i == 0) {
        update.setString(1, "sad");
      } else {
        update.setObject(1, "sad", Types.VARCHAR);
      }
      try {
        update.executeUpdate();
        fail("Expected 'column \"m\" is of type mood but expression is of type character varying', "
            + (i == 0 ? "setString(1, \"sad\")" : "setObject(1, \"sad\", Types.VARCHAR)"));
      } catch (SQLException e) {
        // Exception exception is
        // ERROR: column "m" is of type mood but expression is of type character varying
        if (!PSQLState.DATATYPE_MISMATCH.getState().equals(e.getSQLState())) {
          throw e;
        }
      }
    }
    TestUtil.closeQuietly(update);
  }

  @Test
  public void testOtherAsEnum() throws Exception {
    PreparedStatement update = con.prepareStatement("insert into stringtypetest (m) values (?)");
    update.setObject(1, "happy", Types.OTHER);
    update.executeUpdate();
    // all good
    TestUtil.closeQuietly(update);
  }

  @Test
  public void testMultipleEnumBinds() throws Exception {
    assumeFalse(UNSPECIFIED_STRING_TYPE.equals(stringType));
    assumeTrue(preferQueryMode != PreferQueryMode.SIMPLE);

    PreparedStatement query =
        con.prepareStatement("select * from stringtypetest where m = ? or m = ?");
    query.setString(1, "sad");
    query.setObject(2, "sad", Types.VARCHAR);
    try {
      query.executeQuery();
      fail("Expected 'operator does not exist: mood = character varying'");
    } catch (SQLException e) {
      // Exception exception is
      // ERROR: operator does not exist: mood = character varying
      if (!PSQLState.UNDEFINED_FUNCTION.getState().equals(e.getSQLState())) {
        throw e;
      }
    }
    TestUtil.closeQuietly(query);
  }

  @Test
  public void testParameterUnspecified() throws Exception {
    assumeTrue(UNSPECIFIED_STRING_TYPE.equals(stringType));

    PreparedStatement update = con.prepareStatement("insert into stringtypetest (m) values (?)");
    update.setString(1, "happy");
    update.executeUpdate();
    // all good

    update.clearParameters();
    update.setObject(1, "happy", Types.VARCHAR);
    update.executeUpdate();
    // all good
    update.close();

    PreparedStatement query = con.prepareStatement("select * from stringtypetest where m = ?");
    query.setString(1, "happy");
    ResultSet rs = query.executeQuery();
    assertTrue(rs.next());
    assertEquals("happy", rs.getObject("m"));
    rs.close();

    query.clearParameters();
    query.setObject(1, "happy", Types.VARCHAR);
    rs = query.executeQuery();
    assertTrue(rs.next());
    assertEquals("happy", rs.getObject("m"));

    // all good
    rs.close();
    query.close();
  }
}
