package su.xserver.iikocon;

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.Collections;
import java.util.HashMap;
import java.util.Map;

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,
          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 hash = BCrypt.hashpw(password, BCrypt.gensalt());
    Map<String, Object> params = Map.of(
      "login", login,
      "email", email,
      "password", hash,
      "ip", ip,
      "active", active
    );
    return SqlTemplate.forUpdate(pool,
        "INSERT INTO users (login, email, password, ip, active) VALUES (#{login}, #{email}, #{password}, #{ip}, #{active})")
      .execute(params)
      .mapEmpty();
  }

  // Существующий метод оставляем, но он будет создавать неактивного пользователя (active = false)
  public Future<Void> createUser(String login, String email, String password, String ip) {
    return createUser(login, email, password, ip, false);
  }

  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, ip, created, updated FROM users WHERE login = ? OR email = ?";
    return pool.preparedQuery(sql)
      .execute(Tuple.of(loginOrEmail, loginOrEmail))
      .map(rows -> {
        if (rows.size() == 0) {
          System.out.println("User not found: " + loginOrEmail);
          return null;
        }
        Row row = rows.iterator().next();
        System.out.println("User found, active=" + row.getBoolean("active"));
        return toJson(row);
      });
  }

  public Future<JsonObject> findByEmail(String email) {
    return SqlTemplate.forQuery(pool, "SELECT id, login, email, password, active, ip, created, updated FROM users WHERE email = #{email}")
      .mapTo(this::toJson)
      .execute(Map.of("email", email))
      .map(rows -> rows.iterator().hasNext() ? rows.iterator().next() : null);
  }

  public Future<JsonObject> findByLogin(String login) {
    return SqlTemplate.forQuery(pool,
        "SELECT id, login, password, created, updated, ip FROM users WHERE login = #{login}")
      .mapTo(row -> new JsonObject()
        .put("id", row.getInteger("id"))
        .put("login", row.getString("login"))
        .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)
        .put("ip", row.getString("ip")))
      .execute(Collections.singletonMap("login", login))
      .map(rows -> rows.iterator().hasNext() ? rows.iterator().next() : null);
  }

  public Future<JsonArray> getAllUsers() {
    return pool.query("SELECT id, login, email, active, 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("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) {
    Map<String, Object> params = new HashMap<>();
    params.put("id", id);
    params.put("login", login);
    params.put("email", email);
    params.put("ip", ip);
    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} WHERE id = #{id}";
    } else {
      sql = "UPDATE users SET login = #{login}, email = #{email}, ip = #{ip} WHERE id = #{id}";
    }
    return SqlTemplate.forUpdate(pool, sql).execute(params).mapEmpty();
  }

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