package io.delphi.qa.auto.awsmfrw.managers.helpers.db.pg;

import io.delphi.qa.auto.awsmfrw.common_baby.IsaCommonMethods;
import io.delphi.qa.auto.awsmfrw.common_baby.IsaLogging;
import io.delphi.qa.auto.awsmfrw.managers.helpers.db.BsDbSql;
import io.delphi.qa.auto.awsmfrw.models.db.BsSqlTable;
import io.qameta.allure.Step;
import org.apache.commons.dbutils.DbUtils;

import java.sql.*;
import java.util.*;

public class BsPostgres extends BsDbSql implements IsaCommonMethods {

  private final String STR_PG_DB_URL;
  private final Properties OBJ_PG_PROPS;

  // TODO by alexeyisakov @1/20/21#9:34 AM: Extract the code duplication to the BsSql.class
  public BsPostgres(final Map<String, String> secretMap) {
    OBJ_PG_PROPS = new Properties();
    //    OBJ_PG_PROPS.setProperty("useTimezone", "true");
    OBJ_PG_PROPS.setProperty("serverTimezone", "UTC");
    OBJ_PG_PROPS.setProperty("user", secretMap.get("username"));
    OBJ_PG_PROPS.setProperty("password", secretMap.get("password"));
    STR_PG_DB_URL =
        IsaCommonMethods.resolvePattern(
            "jdbc:{engine}://{host}{port}/{database}",
            new HashMap<String, String>() {
              {
                put(
                    "engine",
                    secretMap.get("engine").equals("postgresql")
                        ? secretMap.get("engine")
                        : "postgresql");
                put("host", secretMap.get("host"));
                put("port", secretMap.get("port") != null ? ":" + secretMap.get("port") : "");
                put(
                    "database",
                    envConfig.isAutomationDb()
                        ? String.format("%s_%s", secretMap.get("database"), "test")
                        : secretMap.get("database"));
              }
            });
  }

  // Db::Postgres: cast object field to table column
  protected <T> String cast(T obj) {
    if (obj.getClass().isInstance("")) {
      return String.format("%s", obj);
    } else if (obj.getClass().isInstance(new Double("0.0"))) {
      return String.format("%s", Double.parseDouble(obj.toString()));
    } else if (obj.getClass().isInstance(Integer.valueOf("0"))) {
      return String.format("%s", Integer.parseInt(obj.toString()));
    } else if (obj.getClass().isInstance(Boolean.TRUE)) {
      return String.format("%s", Boolean.parseBoolean(obj.toString()));
    } else {
      throw new RuntimeException(
          "\n- Not implemented object type. Db helper could not cast object to string.");
    }
  }

  protected Connection getConnection() {
    try {
      return DriverManager.getConnection(STR_PG_DB_URL, OBJ_PG_PROPS);
    } catch (SQLException e) {
      throw new RuntimeException(
          String.format(
              "\n- exception:%s;"
                  + "\n\t- problem: unable to connect to db.pg;"
                  + "\n\t- solution: tern on delphi.dev.vpn OR check the aws.secret,"
                  + " user and password pair could be changed after rotation.",
              e));
    }
  }

  @Deprecated
  protected <T> T getObjectValue(T valueObject) {
    return valueObject;
  }

  @Step("Db::Postgres: execute query")
  public <T extends BsSqlTable> Set<T> executeQuery(String queryText, T sourceObj) {
    StringBuilder msg = new StringBuilder();
    final Set<T> resultObjectSet = new HashSet<>();
    try (Connection conn = getConnection();
        Statement st = conn.createStatement();
        ResultSet rs = Objects.requireNonNull(st).executeQuery(queryText)) {
      msg.append("\n- query text:\n").append(queryText);
      while (true) {
        if (rs.next()) {
          T obj = (T) sourceObj.builder(rs);
          resultObjectSet.add(obj);
        } else break;
      }
    } catch (Throwable e) {
      msg.append("\n- sql exception: ").append(e);
    } finally {
      msg.append(String.format("\n- processed records=%s", resultObjectSet.size()));
      IsaLogging.log(this.getClass(), String.valueOf(msg));
    }
    return resultObjectSet;
  }

  @Deprecated
  @Step("Db::Postgres: execute custom query")
  public List<String> executeCustomQuery(String queryText) {
    List<String> resultObjectSet = new ArrayList<>();
    try (Connection conn = this.getConnection();
        Statement st = conn.createStatement();
        ResultSet rs = Objects.requireNonNull(st).executeQuery(queryText)) {
      IsaLogging.log(this.getClass(), "\n- query text:\n" + queryText);
      while (true) {
        try {
          if (rs.next()) {
            resultObjectSet.add(rs.getString(1));
            //            IsaLogging.logLocal("\n\t- item:" + rs.getString(1));
          } else {
            break;
          }
        } catch (SQLException e) {
          IsaLogging.log(this.getClass(), "\n- sql exception: " + e);
        }
      }
    } catch (SQLException e) {
      IsaLogging.log(this.getClass(), "\n- sql exception: " + e);
    } finally {
      IsaLogging.log(
          this.getClass(), String.format("\n- processed records=%s", resultObjectSet.size()));
    }
    return resultObjectSet;
  }

  @Step("Db::Postgres: execute update")
  protected int executeUpdate(String queryText) {
    StringBuilder msg = new StringBuilder();
    int affectedRows = 0;
    Connection conn = this.getConnection();
    Statement st = null;
    try {
      st = conn.createStatement();
      msg.append("\n- query text:\n").append(queryText);
      affectedRows = st.executeUpdate(queryText);
    } catch (SQLException e) {
      msg.append("\n- sql exception:\n").append(e);
      throw new RuntimeException(e);
    } finally {
      msg.append(String.format("\n- processed records=%d", affectedRows));
      IsaLogging.log(this.getClass(), String.valueOf(msg));
      DbUtils.closeQuietly(st);
      DbUtils.closeQuietly(conn);
    }
    return affectedRows;
  }
}
