package danielcortes.xyz.models.version;

import danielcortes.xyz.data.ConnectionHolder;
import danielcortes.xyz.data.SQLiteConnectionHolder;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class SQLiteVersionDAO implements VersionDAO {

  private static final Logger log = LogManager.getLogger(SQLiteVersionDAO.class);
  private ConnectionHolder connectionHolder;

  public SQLiteVersionDAO() {
    this.connectionHolder = new SQLiteConnectionHolder();
  }

  public SQLiteVersionDAO(ConnectionHolder connectionHolder) {
    this.connectionHolder = connectionHolder;
  }

  private boolean tableVersionsExists() {
    try {
      DatabaseMetaData md = this.connectionHolder.getConnection().getMetaData();
      try (ResultSet rs = md.getTables(null, null, "version", null)) {
        return rs.next();
      }
    } catch (SQLException e) {
      e.printStackTrace();
      return false;
    }
  }

  private int getCurrentVersion() throws SQLException{
    if (tableVersionsExists()) {
      String query = "SELECT version FROM version LIMIT 1";
      try (Connection conn = this.connectionHolder.getConnection()) {
        try (PreparedStatement ps = conn.prepareStatement(query)) {
          try (ResultSet rs = ps.executeQuery()) {
            rs.next();
            return rs.getInt(1);
          }
        }
      } catch (SQLException e) {
        log.error("Error al obtener la version desde la base de datos, se enviara una exepcion");
        throw e;
      }
    }else{
      return 0;
    }
  }

  private InputStream unzipVersionScript(int version) throws IOException {
    ZipFile zipFile = new ZipFile(new File("data/version_scripts"));
    ZipEntry zipEntry = zipFile.getEntry(version + ".sql");
    return zipFile.getInputStream(zipEntry);
  }

  private ArrayList<String> getVersionScript(int version) throws IOException {
    ArrayList<String> script = new ArrayList<>();
    try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(unzipVersionScript(version)))) {
      String line;
      while ((line = reader.readLine()) != null) {
        if(line.trim().length() > 0) {
          script.add(line);
        }
      }
    }
    return script;
  }

  private void executeVersionScript(int version) {
    try (Connection conn = this.connectionHolder.getConnection()) {
      conn.setAutoCommit(false);
      try (Statement statement = conn.createStatement()) {
        try{
          ArrayList<String> script = getVersionScript(version);
          for (String query : script) {
            log.debug("Se agrega query '" + query + "' size " + query.length());
            statement.addBatch(query);
          }
          statement.executeBatch();
        } catch (IOException e) {
          log.error("Error al leer al obtener el script", e);
        }
        conn.commit();
      } catch (SQLException e) {
        log.error("Error al ejecutar el script, se pudo realizar un rollback", e);
        conn.rollback();
      }
    } catch (SQLException e) {
      log.error("Error de sql relacionado con la conexion, no se puede realizar rollback", e);
    }
  }

  @Override
  public void updateTo(int version) {
    try {
      log.warn("Se intentara actualizar la base de datos a la version " + version);
      int currentVersion = this.getCurrentVersion();
      log.warn("La version actual es " + currentVersion);
      while (currentVersion < version) {
        currentVersion++;
        log.warn("Actualizando a version: " + currentVersion);
        executeVersionScript(currentVersion);
      }
      log.warn("Se termino de actualizar a la version " + version);
    } catch (SQLException e){
      log.error("Error al intentar realizar una actualizacion", e);
    }
  }
}
