package su.xserver.iikocon.service;

import io.vertx.core.Future;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.sqlclient.Pool;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.Tuple;
import io.vertx.sqlclient.templates.SqlTemplate;
import org.mindrot.jbcrypt.BCrypt;

import java.util.*;

public class UserService {
  private final Pool pool;

  public UserService(Pool pool) {
    this.pool = pool;
  }

  public Future<Void> initDatabase() {
    String createTable = """
        CREATE TABLE IF NOT EXISTS users (
          id INT AUTO_INCREMENT PRIMARY KEY,
          login VARCHAR(255) UNIQUE NOT NULL,
          email VARCHAR(255) UNIQUE NOT NULL,
          password VARCHAR(255) NOT NULL,
          active BOOLEAN DEFAULT FALSE,
          role VARCHAR(50) DEFAULT 'user',
          language VARCHAR(5) DEFAULT 'en',
          ip VARCHAR(45),
          created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
          updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
        )
        """;
    return pool.query(createTable).execute().mapEmpty();
  }

  public Future<Long> countUsers() {
    return pool.query("SELECT COUNT(*) AS cnt FROM users")
      .execute()
      .map(rows -> rows.iterator().next().getLong("cnt"));
  }

  public Future<Void> createUser(String login, String email, String password, String ip, boolean active, String role) {
    String hash = BCrypt.hashpw(password, BCrypt.gensalt());
    Map<String, Object> params = Map.of(
      "login", login,
      "email", email,
      "password", hash,
      "ip", ip,
      "active", active,
      "role", role
    );
    return SqlTemplate.forUpdate(pool,
        "INSERT INTO users (login, email, password, ip, active, role) VALUES (#{login}, #{email}, #{password}, #{ip}, #{active}, #{role})")
      .execute(params)
      .mapEmpty();
  }

  public Future<Void> createUser(String login, String email, String password, String ip, boolean active) {
    return createUser(login, email, password, ip, active, "user");
  }

  public Future<Void> createUser(String login, String email, String password, String ip) {
    return createUser(login, email, password, ip, false, "user");
  }

  public Future<Void> setActive(int id, boolean active) {
    return SqlTemplate.forUpdate(pool, "UPDATE users SET active = #{active} WHERE id = #{id}")
      .execute(Map.of("id", id, "active", active)).mapEmpty();
  }

  public Future<JsonObject> findByLoginOrEmail(String loginOrEmail) {
    String sql = "SELECT id, login, email, password, active, role, language, ip, created, updated FROM users WHERE login = ? OR email = ?";
    return pool.preparedQuery(sql)
      .execute(Tuple.of(loginOrEmail, loginOrEmail))
      .map(rows -> {
        if (rows.size() == 0) {
          return null;
        }
        Row row = rows.iterator().next();
        return toJson(row);
      });
  }

  public Future<JsonArray> getAllUsers() {
    return pool.query("SELECT id, login, email, active, role, language, ip, created, updated FROM users ORDER BY id")
      .execute()
      .map(rows -> {
        JsonArray array = new JsonArray();
        for (Row row : rows) {
          array.add(new JsonObject()
            .put("id", row.getInteger("id"))
            .put("login", row.getString("login"))
            .put("email", row.getString("email"))
            .put("active", row.getBoolean("active"))
            .put("role", row.getString("role"))
            .put("language", row.getString("language"))
            .put("ip", row.getString("ip"))
            .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<Void> updateUser(int id, String login, String email, String password, String ip, String role) {
    Map<String, Object> params = new HashMap<>();
    params.put("id", id);
    params.put("login", login);
    params.put("email", email);
    params.put("ip", ip);
    if (role != null) params.put("role", role);

    String sql;
    if (password != null && !password.isEmpty()) {
      String hash = BCrypt.hashpw(password, BCrypt.gensalt());
      params.put("password", hash);
      sql = "UPDATE users SET login = #{login}, email = #{email}, password = #{password}, ip = #{ip}"
        + (role != null ? ", role = #{role}" : "") + " WHERE id = #{id}";
    } else {
      sql = "UPDATE users SET login = #{login}, email = #{email}, ip = #{ip}"
        + (role != null ? ", role = #{role}" : "") + " WHERE id = #{id}";
    }
    return SqlTemplate.forUpdate(pool, sql).execute(params).mapEmpty();
  }

  public Future<Void> updateUserIp(int userId, String ip) {
    return pool.preparedQuery("UPDATE users SET ip = ? WHERE id = ?")
      .execute(Tuple.of(ip, userId))
      .mapEmpty();
  }

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

  public Future<JsonObject> getProfile(int userId) {
    return SqlTemplate.forQuery(pool,
        "SELECT id, login, email, role, language, ip, created, updated FROM users WHERE id = #{id}")
      .mapTo(row -> new JsonObject()
        .put("id", row.getInteger("id"))
        .put("login", row.getString("login"))
        .put("email", row.getString("email"))
        .put("role", row.getString("role"))
        .put("language", row.getString("language"))
        .put("ip", row.getString("ip"))
        .put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null)
        .put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null))
      .execute(Map.of("id", userId))
      .map(rows -> rows.iterator().hasNext() ? rows.iterator().next() : null);
  }

  public Future<Void> updateProfile(int userId, String email, String password, String language) {
    Map<String, Object> params = new HashMap<>();
    params.put("id", userId);
    List<String> setClauses = new ArrayList<>();

    if (email != null) {
      setClauses.add("email = #{email}");
      params.put("email", email);
    }

    if (password != null && !password.isEmpty()) {
      String hash = BCrypt.hashpw(password, BCrypt.gensalt());
      setClauses.add("password = #{password}");
      params.put("password", hash);
    }

    if (language != null) {
      setClauses.add("language = #{language}");
      params.put("language", language);
    }

    if (setClauses.isEmpty()) {
      return Future.succeededFuture();
    }

    String sql = "UPDATE users SET " + String.join(", ", setClauses) + " WHERE id = #{id}";
    return SqlTemplate.forUpdate(pool, sql).execute(params).mapEmpty();
  }

  public boolean checkPassword(String plain, String hash) {
    try {
      return BCrypt.checkpw(plain, hash);
    } catch (Exception e) {
      return false;
    }
  }

  private JsonObject toJson(Row row) {
    return new JsonObject()
      .put("id", row.getInteger("id"))
      .put("login", row.getString("login"))
      .put("email", row.getString("email"))
      .put("password", row.getString("password"))
      .put("active", row.getBoolean("active"))
      .put("role", row.getString("role"))
      .put("language", row.getString("language"))
      .put("ip", row.getString("ip"))
      .put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null)
      .put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null);
  }
}
