package su.xserver.iikocon.service;

import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.jdbcclient.JDBCConnectOptions;
import io.vertx.jdbcclient.JDBCPool;
import io.vertx.sqlclient.Pool;
import io.vertx.sqlclient.PoolOptions;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.templates.SqlTemplate;
import su.xserver.iikocon.handler.AdminHandler;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class ExternalDataBaseService {
  private final Pool pool;
  private final Vertx vertx;

  public ExternalDataBaseService(Pool pool, Vertx vertx) {
    this.pool = pool;
    this.vertx = vertx;
  }

  public void handleRoute(Router router) {

    router.get("/api/admin/database-connections").handler(rc -> this.getAllDataBases().onComplete(ar -> {
      if (ar.succeeded()) {
        rc.response()
          .putHeader("Content-Type", "application/json")
          .end(ar.result().encode());
      } else {
        rc.response().setStatusCode(500).end(ar.cause().getMessage());
      }
    }));

    router.get("/api/admin/database-connections/:id/test").handler(AdminHandler::requireAdmin).handler(rc -> {
      int id = Integer.parseInt(rc.pathParam("id"));

      this.testConnection(id)
        .onSuccess(result -> rc.response()
          .setStatusCode(200)
          .putHeader("Content-Type", "application/json")
          .end(result.encode()))
        .onFailure(err -> rc.response()
          .setStatusCode(500)
          .putHeader("Content-Type", "application/json")
          .end(new JsonObject()
            .put("success", false)
            .put("error", err.getMessage())
            .encode()));
    });

    router.post("/api/admin/database-connections").handler(AdminHandler::requireAdmin).handler(rc -> {
      JsonObject body = rc.body().asJsonObject();
      String name = body.getString("name");
      String type = body.getString("type");
      String host = body.getString("host");
      int port = body.getInteger("port");
      String database = body.getString("database");
      String user = body.getString("user");
      String password = body.getString("password");
      if (name == null || type == null || host == null || port < 1 || database == null || user == null || password == null) {
        rc.response().setStatusCode(400).end("Missing fields");
        return;
      }
      this.createDataBase(name, type, host, port, database, user, password)
        .onSuccess(v -> rc.response().setStatusCode(201).end())
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.put("/api/admin/database-connections/:id").handler(AdminHandler::requireAdmin).handler(rc -> {
      int id = Integer.parseInt(rc.pathParam("id"));
      JsonObject body = rc.body().asJsonObject();
      String name = body.getString("name");
      String type = body.getString("type");
      String host = body.getString("host");
      int port = body.getInteger("port");
      String database = body.getString("database");
      String user = body.getString("user");
      String password = body.getString("password");
      if (name == null || type == null || host == null || port < 1 || database == null || user == null) {
        rc.response().setStatusCode(400).end("Missing fields");
        return;
      }
      this.updateDataBase(id, name, type, host, port, database, user, password)
        .onSuccess(v -> rc.response().end())
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });

    router.delete("/api/admin/database-connections/:id").handler(AdminHandler::requireAdmin).handler(rc -> {
      int id = Integer.parseInt(rc.pathParam("id"));
      this.deleteDataBase(id)
        .onSuccess(v -> rc.response().end())
        .onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
    });
  }

  public Future<Void> initDatabase() {
    String createTable = """
      CREATE TABLE IF NOT EXISTS external_database (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(255) UNIQUE NOT NULL,
        type VARCHAR(40) UNIQUE NOT NULL,
        host VARCHAR(255) NOT NULL,
        port INT NOT NULL,
        database VARCHAR(255) NOT NULL,
        user VARCHAR(255) NOT NULL,
        password VARCHAR(255) NOT NULL,
        created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
      )
      """;
    return pool.query(createTable).execute().mapEmpty();
  }

  public Future<Void> createDataBase(String name, String type, String host, int port, String database, String user, String password) {
    Map<String, Object> params = Map.of(
      "name", name,
      "type", type,
      "host", host,
      "port", port,
      "database", database,
      "user", user,
      "password", password
    );
    return SqlTemplate.forUpdate(pool,
        "INSERT INTO external_database (name, type, host, port, database, user, password) VALUES (#{name}, #{type}, #{host}, #{port}, #{database}, #{user}, #{password})")
      .execute(params)
      .mapEmpty();
  }

  public Future<JsonArray> getAllDataBases() {
    return pool.query("SELECT id, name, type, host, port, database, user, password, created, updated FROM external_database ORDER BY id")
      .execute()
      .map(rows -> {
        JsonArray array = new JsonArray();
        for (Row row : rows) {
          array.add(new JsonObject()
            .put("id", row.getInteger("id"))
            .put("name", row.getString("name"))
            .put("type", row.getString("type"))
            .put("host", row.getString("host"))
            .put("port", row.getInteger("port"))
            .put("database", row.getString("database"))
            .put("user", row.getString("user"))
            .put("created", row.getLocalDateTime("created") != null ?
              row.getLocalDateTime("created").toString() : null)
            .put("updated", row.getLocalDateTime("updated") != null ?
              row.getLocalDateTime("updated").toString() : null));
        }
        return array;
      });
  }

  public Future<JsonObject> findById(int id) {
    return SqlTemplate.forQuery(pool,
        "SELECT id, name, type, host, port, database, user, password, created, updated FROM external_database WHERE id = #{id}")
      .mapTo(row -> new JsonObject()
        .put("id", row.getInteger("id"))
        .put("name", row.getString("name"))
        .put("type", row.getString("type"))
        .put("host", row.getString("host"))
        .put("port", row.getInteger("port"))
        .put("database", row.getString("database"))
        .put("user", row.getString("user"))
        .put("password", row.getString("password"))
        .put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null)
        .put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null))
      .execute(Collections.singletonMap("id", id))
      .map(rows -> rows.iterator().hasNext() ? rows.iterator().next() : null);
  }

  public Future<Void> updateDataBase(int id, String name, String type, String host, int port, String database, String user, String password) {
    Map<String, Object> params = new HashMap<>();
    params.put("id", id);
    params.put("name", name);
    params.put("type", type);
    params.put("host", host);
    params.put("port", port);
    params.put("database", database);
    params.put("user", user);
    String sql;
    if (password != null && !password.isEmpty()) {
      params.put("password", password);
      sql = "UPDATE external_database SET name = #{name}, type = #{type}, host = #{host}, port = #{port}, database = #{database}, user = #{user}, password = #{password} WHERE id = #{id}";
    } else {
      sql = "UPDATE external_database SET name = #{name}, type = #{type}, host = #{host}, port = #{port}, database = #{database}, user = #{user} WHERE id = #{id}";
    }
    return SqlTemplate.forUpdate(pool, sql).execute(params).mapEmpty();
  }

  public Future<Void> deleteDataBase(int id) {
    return SqlTemplate.forUpdate(pool, "DELETE FROM external_database WHERE id = #{id}")
      .execute(Collections.singletonMap("id", id))
      .mapEmpty();
  }

  public Future<JsonObject> testConnection(int id) {
    Promise<JsonObject> promise = Promise.promise();

    this.findById(id)
      .onSuccess(conn -> {
        String jdbcUrl = buildJdbcUrl(conn);
        if (jdbcUrl == null) {
          promise.fail("Unsupported database type: " + conn.getString("type"));
          return;
        }

        JDBCConnectOptions connectOptions = new JDBCConnectOptions()
          .setJdbcUrl(jdbcUrl)
          .setDatabase(conn.getString("database"))
          .setUser(conn.getString("user"))
          .setPassword(conn.getString("password"));

        PoolOptions poolOptions = new PoolOptions()
          .setMaxSize(1);

        Pool pool = JDBCPool.pool(vertx, connectOptions, poolOptions);

        long startTime = System.currentTimeMillis();

        pool
          .query("SELECT 1")
          .execute()
          .onSuccess(rows -> {
            long latency = System.currentTimeMillis() - startTime;
            JsonObject result = new JsonObject()
              .put("success", true)
              .put("latency_ms", latency);
            promise.complete(result);
            pool.close();
          })
          .onFailure(err -> promise.fail("Connection failed: " + err.getMessage()));
      })
      .onFailure(promise::fail);

    return promise.future();
  }

  private String buildJdbcUrl(JsonObject conn) {
    return switch (conn.getString("type").toLowerCase()) {
      case "mysql" -> String.format("jdbc:mysql://%s:%d",
        conn.getString("host"), conn.getInteger("port"));
      case "postgres" -> String.format("jdbc:postgresql://%s:%d",
        conn.getString("host"), conn.getInteger("port"));
      case "clickhouse" ->
        String.format("jdbc:clickhouse://%s:%d",
          conn.getString("host"), conn.getInteger("port"));
      default -> null;
    };
  }

}
